initial commit
[squeep-lazy-property] / index.js
1 'use strict';
2
3 /**
4 * Establish some immutable descriptor Shapes with our preferred defaults.
5 * We want normally-behaving properties unless told otherwise.
6 */
7 function defaultShape(proto, ...objs) {
8 const newObject = Object.create(proto);
9 Object.assign(newObject, ...objs);
10 return newObject;
11 }
12
13 const defaultDataDescriptor = defaultShape(null, {
14 configurable: true,
15 enumerable: true,
16 writable: true,
17 value: undefined,
18 });
19
20 const defaultAccessorDescriptor = defaultShape(null, {
21 configurable: true,
22 enumerable: true,
23 get: undefined,
24 set: undefined,
25 });
26
27 /**
28 * Create a new descriptor which inherits our preferred defaults.
29 * @param {Object} defaultDescriptor
30 * @param {...any} objs
31 * @returns {Object}
32 */
33 function descriptorFromDefault(defaultDescriptor, ...objs) {
34 const newObject = Object.create(defaultDataDescriptor);
35 Object.assign(newObject, ...objs);
36 return Object.assign(Object.create(defaultDescriptor), ...objs);
37 }
38
39 /**
40 * Defer calling initializer to set value for name until name is read.
41 * @param {Object} obj
42 * @param {String} name
43 * @param {() => {*}} initializer
44 * @param {Object=} descriptor
45 * @param {Boolean} descriptor.configurable
46 * @param {Boolean} descriptor.enumerable
47 * @param {Boolean} descriptor.writable
48 */
49 function lazy(obj, name, initializer, descriptor) {
50 if (typeof initializer !== 'function') {
51 throw new TypeError('initialize is not callable');
52 }
53
54 // Establish the descriptor for the eventually-initialized property.
55 const finalDescriptor = descriptorFromDefault(defaultDataDescriptor, descriptor);
56
57 // The descriptor defining the interim accessor property.
58 const lazyDescriptor = descriptorFromDefault(defaultAccessorDescriptor, {
59 // The accessor replaces itself with the value returned by invoking the initializer.
60 get: function () {
61 finalDescriptor.value = initializer.apply(obj);
62 Object.defineProperty(obj, name, finalDescriptor);
63 return finalDescriptor.value;
64 },
65
66 // An explicit set overrides the lazy descriptor with a default data property.
67 set: function (value) {
68 Object.defineProperty(obj, name, descriptorFromDefault(defaultDataDescriptor, {
69 value,
70 }))
71 },
72
73 configurable: true, // Ensure we can replace ourself.
74 enumerable: finalDescriptor.enumerable, // Use requested visibility.
75 });
76
77 Object.defineProperty(obj, name, lazyDescriptor);
78 }
79
80 module.exports = {
81 lazy,
82 };