« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/cacheable-object.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/cacheable-object.js')
-rw-r--r--src/data/cacheable-object.js394
1 files changed, 210 insertions, 184 deletions
diff --git a/src/data/cacheable-object.js b/src/data/cacheable-object.js
index 4afb0368..fe1817f6 100644
--- a/src/data/cacheable-object.js
+++ b/src/data/cacheable-object.js
@@ -1,3 +1,7 @@
+/**
+ * @format
+ */
+
 // Generally extendable class for caching properties and handling dependencies,
 // with a few key properties:
 //
@@ -74,21 +78,21 @@
 //      function, which provides a mapping of exposed property names to whether
 //      or not their dependencies are yet met.
 
-import { color, ENABLE_COLOR } from '../util/cli.js';
+import {color, ENABLE_COLOR} from '../util/cli.js';
 
-import { inspect as nodeInspect } from 'util';
+import {inspect as nodeInspect} from 'util';
 
 function inspect(value) {
-    return nodeInspect(value, {colors: ENABLE_COLOR});
+  return nodeInspect(value, {colors: ENABLE_COLOR});
 }
 
 export default class CacheableObject {
-    static instance = Symbol('CacheableObject `this` instance');
+  static instance = Symbol('CacheableObject `this` instance');
 
-    #propertyUpdateValues = Object.create(null);
-    #propertyUpdateCacheInvalidators = Object.create(null);
+  #propertyUpdateValues = Object.create(null);
+  #propertyUpdateCacheInvalidators = Object.create(null);
 
-    /*
+  /*
     // Note the constructor doesn't take an initial data source. Due to a quirk
     // of JavaScript, private members can't be accessed before the superclass's
     // constructor is finished processing - so if we call the overridden
@@ -99,211 +103,233 @@ export default class CacheableObject {
     // after constructing the new instance of the Thing (sub)class.
     */
 
-    constructor() {
-        this.#defineProperties();
-        this.#initializeUpdatingPropertyValues();
-
-        if (CacheableObject.DEBUG_SLOW_TRACK_INVALID_PROPERTIES) {
-            return new Proxy(this, {
-                get: (obj, key) => {
-                    if (!Object.hasOwn(obj, key)) {
-                        if (key !== 'constructor') {
-                            CacheableObject._invalidAccesses.add(`(${obj.constructor.name}).${key}`);
-                        }
-                    }
-                    return obj[key];
-                }
-            });
-        }
-    }
-
-    #initializeUpdatingPropertyValues() {
-        for (const [ property, descriptor ] of Object.entries(this.constructor.propertyDescriptors)) {
-            const { flags, update } = descriptor;
-
-            if (!flags.update) {
-                continue;
-            }
-
-            if (update?.default) {
-                this[property] = update?.default;
-            } else {
-                this[property] = null;
+  constructor() {
+    this.#defineProperties();
+    this.#initializeUpdatingPropertyValues();
+
+    if (CacheableObject.DEBUG_SLOW_TRACK_INVALID_PROPERTIES) {
+      return new Proxy(this, {
+        get: (obj, key) => {
+          if (!Object.hasOwn(obj, key)) {
+            if (key !== 'constructor') {
+              CacheableObject._invalidAccesses.add(
+                `(${obj.constructor.name}).${key}`
+              );
             }
-        }
+          }
+          return obj[key];
+        },
+      });
     }
-
-    #defineProperties() {
-        if (!this.constructor.propertyDescriptors) {
-            throw new Error(`Expected constructor ${this.constructor.name} to define propertyDescriptors`);
-        }
-
-        for (const [ property, descriptor ] of Object.entries(this.constructor.propertyDescriptors)) {
-            const { flags } = descriptor;
-
-            const definition = {
-                configurable: false,
-                enumerable: true
-            };
-
-            if (flags.update) {
-                definition.set = this.#getUpdateObjectDefinitionSetterFunction(property);
-            }
-
-            if (flags.expose) {
-                definition.get = this.#getExposeObjectDefinitionGetterFunction(property);
-            }
-
-            Object.defineProperty(this, property, definition);
-        }
-
-        Object.seal(this);
+  }
+
+  #initializeUpdatingPropertyValues() {
+    for (const [property, descriptor] of Object.entries(
+      this.constructor.propertyDescriptors
+    )) {
+      const {flags, update} = descriptor;
+
+      if (!flags.update) {
+        continue;
+      }
+
+      if (update?.default) {
+        this[property] = update?.default;
+      } else {
+        this[property] = null;
+      }
     }
+  }
 
-    #getUpdateObjectDefinitionSetterFunction(property) {
-        const { update } = this.#getPropertyDescriptor(property);
-        const validate = update?.validate;
-        const allowNull = update?.allowNull;
-
-        return (newValue) => {
-            const oldValue = this.#propertyUpdateValues[property];
+  #defineProperties() {
+    if (!this.constructor.propertyDescriptors) {
+      throw new Error(
+        `Expected constructor ${this.constructor.name} to define propertyDescriptors`
+      );
+    }
 
-            if (newValue === undefined) {
-                throw new TypeError(`Properties cannot be set to undefined`);
-            }
+    for (const [property, descriptor] of Object.entries(
+      this.constructor.propertyDescriptors
+    )) {
+      const {flags} = descriptor;
 
-            if (newValue === oldValue) {
-                return;
-            }
+      const definition = {
+        configurable: false,
+        enumerable: true,
+      };
 
-            if (newValue !== null && validate) {
-                try {
-                    const result = validate(newValue);
-                    if (result === undefined) {
-                        throw new TypeError(`Validate function returned undefined`);
-                    } else if (result !== true) {
-                        throw new TypeError(`Validation failed for value ${newValue}`);
-                    }
-                } catch (error) {
-                    error.message = `Property ${color.green(property)} (${inspect(this[property])} -> ${inspect(newValue)}): ${error.message}`;
-                    throw error;
-                }
-            }
+      if (flags.update) {
+        definition.set =
+          this.#getUpdateObjectDefinitionSetterFunction(property);
+      }
 
-            this.#propertyUpdateValues[property] = newValue;
-            this.#invalidateCachesDependentUpon(property);
-        };
-    }
+      if (flags.expose) {
+        definition.get =
+          this.#getExposeObjectDefinitionGetterFunction(property);
+      }
 
-    #getUpdatePropertyValidateFunction(property) {
-        const descriptor = this.#getPropertyDescriptor(property);
+      Object.defineProperty(this, property, definition);
     }
 
-    #getPropertyDescriptor(property) {
-        return this.constructor.propertyDescriptors[property];
-    }
-
-    #invalidateCachesDependentUpon(property) {
-        for (const invalidate of this.#propertyUpdateCacheInvalidators[property] || []) {
-            invalidate();
-        }
-    }
-
-    #getExposeObjectDefinitionGetterFunction(property) {
-        const { flags } = this.#getPropertyDescriptor(property);
-        const compute = this.#getExposeComputeFunction(property);
-
-        if (compute) {
-            let cachedValue;
-            const checkCacheValid = this.#getExposeCheckCacheValidFunction(property);
-            return () => {
-                if (checkCacheValid()) {
-                    return cachedValue;
-                } else {
-                    return (cachedValue = compute());
-                }
-            };
-        } else if (!flags.update && !compute) {
-            throw new Error(`Exposed property ${property} does not update and is missing compute function`);
-        } else {
-            return () => this.#propertyUpdateValues[property];
+    Object.seal(this);
+  }
+
+  #getUpdateObjectDefinitionSetterFunction(property) {
+    const {update} = this.#getPropertyDescriptor(property);
+    const validate = update?.validate;
+
+    return (newValue) => {
+      const oldValue = this.#propertyUpdateValues[property];
+
+      if (newValue === undefined) {
+        throw new TypeError(`Properties cannot be set to undefined`);
+      }
+
+      if (newValue === oldValue) {
+        return;
+      }
+
+      if (newValue !== null && validate) {
+        try {
+          const result = validate(newValue);
+          if (result === undefined) {
+            throw new TypeError(`Validate function returned undefined`);
+          } else if (result !== true) {
+            throw new TypeError(`Validation failed for value ${newValue}`);
+          }
+        } catch (error) {
+          error.message = `Property ${color.green(property)} (${inspect(
+            this[property]
+          )} -> ${inspect(newValue)}): ${error.message}`;
+          throw error;
         }
-    }
-
-    #getExposeComputeFunction(property) {
-        const { flags, expose } = this.#getPropertyDescriptor(property);
-
-        const compute = expose?.compute;
-        const transform = expose?.transform;
+      }
 
-        if (flags.update && !transform) {
-            return null;
-        } else if (flags.update && compute) {
-            throw new Error(`Updating property ${property} has compute function, should be formatted as transform`);
-        } else if (!flags.update && !compute) {
-            throw new Error(`Exposed property ${property} does not update and is missing compute function`);
-        }
+      this.#propertyUpdateValues[property] = newValue;
+      this.#invalidateCachesDependentUpon(property);
+    };
+  }
 
-        const dependencyKeys = expose.dependencies || [];
-        const dependencyGetters = dependencyKeys.map(key => () => [key, this.#propertyUpdateValues[key]]);
-        const getAllDependencies = () => Object.fromEntries(dependencyGetters.map(f => f())
-            .concat([[this.constructor.instance, this]]));
+  #getPropertyDescriptor(property) {
+    return this.constructor.propertyDescriptors[property];
+  }
 
-        if (flags.update) {
-            return () => transform(this.#propertyUpdateValues[property], getAllDependencies());
+  #invalidateCachesDependentUpon(property) {
+    for (const invalidate of this.#propertyUpdateCacheInvalidators[property] ||
+      []) {
+      invalidate();
+    }
+  }
+
+  #getExposeObjectDefinitionGetterFunction(property) {
+    const {flags} = this.#getPropertyDescriptor(property);
+    const compute = this.#getExposeComputeFunction(property);
+
+    if (compute) {
+      let cachedValue;
+      const checkCacheValid = this.#getExposeCheckCacheValidFunction(property);
+      return () => {
+        if (checkCacheValid()) {
+          return cachedValue;
         } else {
-            return () => compute(getAllDependencies());
+          return (cachedValue = compute());
         }
+      };
+    } else if (!flags.update && !compute) {
+      throw new Error(
+        `Exposed property ${property} does not update and is missing compute function`
+      );
+    } else {
+      return () => this.#propertyUpdateValues[property];
+    }
+  }
+
+  #getExposeComputeFunction(property) {
+    const {flags, expose} = this.#getPropertyDescriptor(property);
+
+    const compute = expose?.compute;
+    const transform = expose?.transform;
+
+    if (flags.update && !transform) {
+      return null;
+    } else if (flags.update && compute) {
+      throw new Error(
+        `Updating property ${property} has compute function, should be formatted as transform`
+      );
+    } else if (!flags.update && !compute) {
+      throw new Error(
+        `Exposed property ${property} does not update and is missing compute function`
+      );
     }
 
-    #getExposeCheckCacheValidFunction(property) {
-        const { flags, expose } = this.#getPropertyDescriptor(property);
-
-        let valid = false;
+    const dependencyKeys = expose.dependencies || [];
+    const dependencyGetters = dependencyKeys.map((key) => () => [
+      key,
+      this.#propertyUpdateValues[key],
+    ]);
+    const getAllDependencies = () =>
+      Object.fromEntries(
+        dependencyGetters
+          .map((f) => f())
+          .concat([[this.constructor.instance, this]])
+      );
+
+    if (flags.update) {
+      return () =>
+        transform(this.#propertyUpdateValues[property], getAllDependencies());
+    } else {
+      return () => compute(getAllDependencies());
+    }
+  }
 
-        const invalidate = () => {
-            valid = false;
-        };
+  #getExposeCheckCacheValidFunction(property) {
+    const {flags, expose} = this.#getPropertyDescriptor(property);
 
-        const dependencyKeys = new Set(expose?.dependencies);
+    let valid = false;
 
-        if (flags.update) {
-            dependencyKeys.add(property);
-        }
+    const invalidate = () => {
+      valid = false;
+    };
 
-        for (const key of dependencyKeys) {
-            if (this.#propertyUpdateCacheInvalidators[key]) {
-                this.#propertyUpdateCacheInvalidators[key].push(invalidate);
-            } else {
-                this.#propertyUpdateCacheInvalidators[key] = [invalidate];
-            }
-        }
+    const dependencyKeys = new Set(expose?.dependencies);
 
-        return () => {
-            if (!valid) {
-                valid = true;
-                return false;
-            } else {
-                return true;
-            }
-        };
+    if (flags.update) {
+      dependencyKeys.add(property);
     }
 
-    static DEBUG_SLOW_TRACK_INVALID_PROPERTIES = false;
-    static _invalidAccesses = new Set();
+    for (const key of dependencyKeys) {
+      if (this.#propertyUpdateCacheInvalidators[key]) {
+        this.#propertyUpdateCacheInvalidators[key].push(invalidate);
+      } else {
+        this.#propertyUpdateCacheInvalidators[key] = [invalidate];
+      }
+    }
 
-    static showInvalidAccesses() {
-        if (!this.DEBUG_SLOW_TRACK_INVALID_PROPERTIES) {
-            return;
-        }
+    return () => {
+      if (!valid) {
+        valid = true;
+        return false;
+      } else {
+        return true;
+      }
+    };
+  }
+
+  static DEBUG_SLOW_TRACK_INVALID_PROPERTIES = false;
+  static _invalidAccesses = new Set();
+
+  static showInvalidAccesses() {
+    if (!this.DEBUG_SLOW_TRACK_INVALID_PROPERTIES) {
+      return;
+    }
 
-        if (!this._invalidAccesses.size) {
-            return;
-        }
+    if (!this._invalidAccesses.size) {
+      return;
+    }
 
-        console.log(`${this._invalidAccesses.size} unique invalid accesses:`);
-        for (const line of this._invalidAccesses) {
-            console.log(` - ${line}`);
-        }
+    console.log(`${this._invalidAccesses.size} unique invalid accesses:`);
+    for (const line of this._invalidAccesses) {
+      console.log(` - ${line}`);
     }
+  }
 }