initial commit
[squeep-lazy-property] / index.js
diff --git a/index.js b/index.js
new file mode 100644 (file)
index 0000000..7259810
--- /dev/null
+++ b/index.js
@@ -0,0 +1,82 @@
+'use strict';
+
+/**
+ * Establish some immutable descriptor Shapes with our preferred defaults.
+ * We want normally-behaving properties unless told otherwise.
+ */
+function defaultShape(proto, ...objs) {
+  const newObject = Object.create(proto);
+  Object.assign(newObject, ...objs);
+  return newObject;
+}
+
+const defaultDataDescriptor = defaultShape(null, {
+  configurable: true,
+  enumerable: true,
+  writable: true,
+  value: undefined,
+});
+
+const defaultAccessorDescriptor = defaultShape(null, {
+  configurable: true,
+  enumerable: true,
+  get: undefined,
+  set: undefined,
+});
+
+/**
+ * Create a new descriptor which inherits our preferred defaults.
+ * @param {Object} defaultDescriptor
+ * @param  {...any} objs
+ * @returns {Object}
+ */
+function descriptorFromDefault(defaultDescriptor, ...objs) {
+  const newObject = Object.create(defaultDataDescriptor);
+  Object.assign(newObject, ...objs);
+  return Object.assign(Object.create(defaultDescriptor), ...objs);
+}
+
+/**
+ * Defer calling initializer to set value for name until name is read.
+ * @param {Object} obj
+ * @param {String} name
+ * @param {() => {*}} initializer
+ * @param {Object=} descriptor
+ * @param {Boolean} descriptor.configurable
+ * @param {Boolean} descriptor.enumerable
+ * @param {Boolean} descriptor.writable
+ */
+function lazy(obj, name, initializer, descriptor) {
+  if (typeof initializer !== 'function') {
+    throw new TypeError('initialize is not callable');
+  }
+
+  // Establish the descriptor for the eventually-initialized property.
+  const finalDescriptor = descriptorFromDefault(defaultDataDescriptor, descriptor);
+
+  // The descriptor defining the interim accessor property.
+  const lazyDescriptor = descriptorFromDefault(defaultAccessorDescriptor, {
+    // The accessor replaces itself with the value returned by invoking the initializer.
+    get: function () {
+      finalDescriptor.value = initializer.apply(obj);
+      Object.defineProperty(obj, name, finalDescriptor);
+      return finalDescriptor.value;
+    },
+  
+    // An explicit set overrides the lazy descriptor with a default data property.
+    set: function (value) {
+      Object.defineProperty(obj, name, descriptorFromDefault(defaultDataDescriptor, {
+        value,
+      }))
+    },
+  
+    configurable: true, // Ensure we can replace ourself.
+    enumerable: finalDescriptor.enumerable, // Use requested visibility.
+  });
+
+  Object.defineProperty(obj, name, lazyDescriptor);
+}
+
+module.exports = {
+  lazy,
+};