« get me outta code hell

data: track: experimental Thing.compose.from() processing style - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things/thing.js
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-08-21 17:28:15 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-09-05 21:02:48 -0300
commit55e4afead38bc541cba4ae1cef183527c254f99a (patch)
tree6fac3aaed4cba2c8e934a61b913849c6239cb340 /src/data/things/thing.js
parent218a99a3164e8ae6967335190b72fd36275d1892 (diff)
data: track: experimental Thing.compose.from() processing style
Diffstat (limited to 'src/data/things/thing.js')
-rw-r--r--src/data/things/thing.js142
1 files changed, 141 insertions, 1 deletions
diff --git a/src/data/things/thing.js b/src/data/things/thing.js
index c2876f56..143c1515 100644
--- a/src/data/things/thing.js
+++ b/src/data/things/thing.js
@@ -5,7 +5,7 @@ import {inspect} from 'node:util';
 
 import {color} from '#cli';
 import find from '#find';
-import {empty} from '#sugar';
+import {empty, openAggregate} from '#sugar';
 import {getKebabCase} from '#wiki-data';
 
 import {
@@ -418,4 +418,144 @@ export default class Thing extends CacheableObject {
 
     return `${thing.constructor[Thing.referenceType]}:${thing.directory}`;
   }
+
+  static findArtistsFromContribs(contribsByRef, artistData) {
+    return (
+      contribsByRef
+        .map(({who, what}) => ({
+          who: find.artist(who, artistData),
+          what,
+        }))
+        .filter(({who}) => who));
+  }
+
+  static composite = {
+    from(composition) {
+      const base = composition.at(-1);
+      const steps = composition.slice(0, -1);
+
+      const aggregate = openAggregate({message: `Errors preparing Thing.composite.from() composition`});
+
+      if (base.flags.compose) {
+        aggregate.push(new TypeError(`Base (bottom item) must not be {compose: true}`));
+      }
+
+      const exposeFunctionOrder = [];
+      const exposeDependencies = new Set(base.expose?.dependencies);
+
+      for (let i = 0; i < steps.length; i++) {
+        const step = steps[i];
+        const message =
+          (step.annotation
+            ? `Errors in step #${i + 1} (${step.annotation})`
+            : `Errors in step #${i + 1}`);
+
+        aggregate.nest({message}, ({push}) => {
+          if (!step.flags.compose) {
+            push(new TypeError(`Steps (all but bottom item) must be {compose: true}`));
+          }
+
+          if (step.flags.update) {
+            push(new Error(`Steps which update aren't supported yet`));
+          }
+
+          if (step.flags.expose) expose: {
+            if (!step.expose.transform && !step.expose.compute) {
+              push(new TypeError(`Steps which expose must provide at least one of transform or compute`));
+              break expose;
+            }
+
+            if (step.expose.dependencies) {
+              for (const dependency of step.expose.dependencies) {
+                exposeDependencies.add(dependency);
+              }
+            }
+
+            if (base.flags.update) {
+              if (step.expose.transform) {
+                exposeFunctionOrder.push({type: 'transform', fn: step.expose.transform});
+              } else {
+                exposeFunctionOrder.push({type: 'compute', fn: step.expose.compute});
+              }
+            } else {
+              if (step.expose.transform && !step.expose.compute) {
+                push(new TypeError(`Steps which only transform can't be composed with a non-updating base`));
+                break expose;
+              }
+
+              exposeFunctionOrder.push({type: 'compute', fn: step.expose.compute});
+            }
+          }
+        });
+      }
+
+      aggregate.close();
+
+      const constructedDescriptor = {};
+
+      constructedDescriptor.flags = {
+        update: !!base.flags.update,
+        expose: !!base.flags.expose,
+        compose: false,
+      };
+
+      if (base.flags.update) {
+        constructedDescriptor.update = base.flags.update;
+      }
+
+      if (base.flags.expose) {
+        const expose = constructedDescriptor.expose = {};
+        expose.dependencies = Array.from(exposeDependencies);
+
+        const continuationSymbol = Symbol();
+
+        if (base.flags.update) {
+          expose.transform = (value, initialDependencies) => {
+            const dependencies = {...initialDependencies};
+            let valueSoFar = value;
+
+            for (const {type, fn} of exposeFunctionOrder) {
+              const result =
+                (type === 'transform'
+                  ? fn(valueSoFar, dependencies, (updatedValue, providedDependencies) => {
+                      valueSoFar = updatedValue;
+                      Object.assign(dependencies, providedDependencies ?? {});
+                      return continuationSymbol;
+                    })
+                  : fn(dependencies, providedDependencies => {
+                      Object.assign(dependencies, providedDependencies ?? {});
+                      return continuationSymbol;
+                    }));
+
+              if (result !== continuationSymbol) {
+                return result;
+              }
+            }
+
+            return base.expose.transform(valueSoFar, dependencies);
+          };
+        } else {
+          expose.compute = (initialDependencies) => {
+            const dependencies = {...initialDependencies};
+
+            for (const {fn} of exposeFunctionOrder) {
+              const result =
+                fn(valueSoFar, dependencies, providedDependencies => {
+                  Object.assign(dependencies, providedDependencies ?? {});
+                  return continuationSymbol;
+                });
+
+              if (result !== continuationSymbol) {
+                return result;
+              }
+            }
+
+            return base.expose.compute(dependencies);
+          };
+        }
+      }
+
+      return constructedDescriptor;
+    },
+  };
 }