--- /dev/null
+'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,
+};