« get me outta code hell

cacheable-object, data: depend on computed values; initial compat - 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:
author(quasar) nebula <qznebula@protonmail.com>2025-11-25 12:03:13 -0400
committer(quasar) nebula <qznebula@protonmail.com>2025-11-25 12:04:49 -0400
commit95c7c7032556d3adfc3107d11a3e14ab0f4c9145 (patch)
tree800d4f7dfe0997d9be1a16cd9586228b1a832dbd /src/data/cacheable-object.js
parent3afd5e8f8d6b08ccb9e49e53b0da4423a7d23542 (diff)
cacheable-object, data: depend on computed values; initial compat
reaches live-dev-server serve with no errors and serves
homepage at all and apparently correctly

no page navigation performed
no full build performed

aimed for preserving existing logic
should be no subsequent changes to this commit (amend)
Diffstat (limited to 'src/data/cacheable-object.js')
-rw-r--r--src/data/cacheable-object.js86
1 files changed, 54 insertions, 32 deletions
diff --git a/src/data/cacheable-object.js b/src/data/cacheable-object.js
index 3aa3ecf7..dead009a 100644
--- a/src/data/cacheable-object.js
+++ b/src/data/cacheable-object.js
@@ -120,18 +120,14 @@ export default class CacheableObject {
 
           const dependencies = Object.create(null);
           for (const key of expose.dependencies ?? []) {
-            switch (key) {
-              case 'this':
-                dependencies.this = this;
-                break;
-
-              case 'thisProperty':
-                dependencies.thisProperty = property;
-                break;
-
-              default:
-                dependencies[key] = this[CacheableObject.updateValue][key];
-                break;
+            if (key === 'this') {
+              dependencies.this = this;
+            } else if (key === 'thisProperty') {
+              dependencies.thisProperty = property;
+            } else if (key.startsWith('_')) {
+              dependencies[key] = this[CacheableObject.updateValue][key.slice(1)];
+            } else {
+              dependencies[key] = this[key];
             }
           }
 
@@ -150,27 +146,11 @@ export default class CacheableObject {
       if (flags.expose) recordAsDependant: {
         const dependantsMap = this[CacheableObject.propertyDependants];
 
-        if (flags.update && expose?.transform) {
-          if (dependantsMap[property]) {
-            dependantsMap[property].push(property);
+        for (const dependency of dependenciesOf(property, propertyDescriptors)) {
+          if (dependantsMap[dependency]) {
+            dependantsMap[dependency].push(dependency);
           } else {
-            dependantsMap[property] = [property];
-          }
-        }
-
-        for (const dependency of expose?.dependencies ?? []) {
-          switch (dependency) {
-            case 'this':
-            case 'thisProperty':
-              continue;
-
-            default: {
-              if (dependantsMap[dependency]) {
-                dependantsMap[dependency].push(property);
-              } else {
-                dependantsMap[dependency] = [property];
-              }
-            }
+            dependantsMap[dependency] = [dependency];
           }
         }
       }
@@ -261,6 +241,7 @@ export class CacheableObjectPropertyValueError extends Error {
 }
 
 // good ol' module-scope utility functions
+
 function validatePropertyValue(property, oldValue, newValue, update) {
   try {
     const result = update.validate(newValue);
@@ -274,3 +255,44 @@ function validatePropertyValue(property, oldValue, newValue, update) {
       property, oldValue, newValue, {cause: caughtError});
   }
 }
+
+function* dependenciesOf(property, propertyDescriptors, cycle = []) {
+  const descriptor = propertyDescriptors[property];
+
+  if (descriptor?.flags?.update && descriptor?.expose?.transform) {
+    yield property;
+  }
+
+  const dependencies = descriptor?.expose?.dependencies;
+  if (!dependencies) return;
+
+  for (const dependency of dependencies) {
+    if (dependency === 'this') continue;
+    if (dependency === 'thisProperty') continue;
+
+    if (dependency.startsWith('_')) {
+      yield dependency.slice(1);
+      continue;
+    }
+
+    if (dependency === property) {
+      throw new Error(
+        `property ${dependency} directly depends on its own computed value`);
+    }
+
+    if (cycle.includes(dependency)) {
+      const subcycle = cycle.slice(cycle.indexOf(dependency));
+      const supercycle = cycle.slice(0, cycle.indexOf(dependency));
+      throw new Error(
+        `property ${dependency} indirectly depends on its own computed value\n` +
+        `  via: ` + subcycle.map(p => p + ' -> ').join('') + property + ' -> ' + dependency +
+        (supercycle.length
+          ? '\n   in: ' + supercycle.join(' -> ')
+          : ''));
+    }
+
+    cycle.push(property);
+    yield* dependenciesOf(dependency, propertyDescriptors, cycle);
+    cycle.pop();
+  }
+}