« get me outta code hell

data: withReverseList_template - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/composite
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-09-04 12:44:19 -0300
committer(quasar) nebula <qznebula@protonmail.com>2024-09-04 12:46:21 -0300
commita46db43a47928bfa9bb48ee31b521f115b754566 (patch)
treefb6052136e74129201116b1e7b429ec0d7322e78 /src/data/composite
parent6285a05944eef6426b02ff4e16663dcbe3d5a8e6 (diff)
data: withReverseList_template
Diffstat (limited to 'src/data/composite')
-rw-r--r--src/data/composite/wiki-data/helpers/withReverseList-template.js189
-rw-r--r--src/data/composite/wiki-data/withReverseContributionList.js169
-rw-r--r--src/data/composite/wiki-data/withReverseReferenceList.js170
3 files changed, 203 insertions, 325 deletions
diff --git a/src/data/composite/wiki-data/helpers/withReverseList-template.js b/src/data/composite/wiki-data/helpers/withReverseList-template.js
new file mode 100644
index 00000000..5ed0e32e
--- /dev/null
+++ b/src/data/composite/wiki-data/helpers/withReverseList-template.js
@@ -0,0 +1,189 @@
+// Baseline implementation shared by or underlying reverse lists.
+//
+// This is a very rudimentary "these compositions have basically the same
+// shape but slightly different guts midway through" kind of solution,
+// and should use compositional subroutines instead, once those are ready.
+//
+// But, until then, this has the same effect of avoiding code duplication
+// and clearly identifying differences.
+//
+// ---
+//
+// This implementation uses a global cache (via WeakMap) to attempt to speed
+// up subsequent similar accesses.
+//
+// This has absolutely not been rigorously tested with altering properties of
+// data objects in a wiki data array which is reused. If a new wiki data array
+// is used, a fresh cache will always be created.
+//
+
+import {input, templateCompositeFrom} from '#composite';
+import {sortByDate} from '#sort';
+import {stitchArrays} from '#sugar';
+
+import {exitWithoutDependency, raiseOutputWithoutDependency}
+  from '#composite/control-flow';
+import {withFlattenedList, withMappedList} from '#composite/data';
+
+import inputWikiData from '../inputWikiData.js';
+
+export default function withReverseList_template({
+  annotation,
+
+  propertyInputName,
+  outputName,
+
+  customCompositionSteps,
+}) {
+  // Mapping of reference list property to WeakMap.
+  // Each WeakMap maps a wiki data array to another weak map,
+  // which in turn maps each referenced thing to an array of
+  // things referencing it.
+  const caches = new Map();
+
+  return templateCompositeFrom({
+    annotation,
+
+    inputs: {
+      data: inputWikiData({
+        allowMixedTypes: false,
+      }),
+
+      [propertyInputName]: input({
+        type: 'string',
+      }),
+    },
+
+    outputs: [outputName],
+
+    steps: () => [
+      // Early exit with an empty array if the data list isn't available.
+      exitWithoutDependency({
+        dependency: input('data'),
+        value: input.value([]),
+      }),
+
+      // Raise an empty array (don't early exit) if the data list is empty.
+      raiseOutputWithoutDependency({
+        dependency: input('data'),
+        mode: input.value('empty'),
+        output: input.value({[outputName]: []}),
+      }),
+
+      // Check for an existing cache record which corresponds to this
+      // property input and input('data'). If it exists, query it for the
+      // current thing, and raise that; if it doesn't, create it, put it
+      // where it needs to be, and provide it so the next steps can fill
+      // it in.
+      {
+        dependencies: [input(propertyInputName), input('data'), input.myself()],
+
+        compute: (continuation, {
+          [input(propertyInputName)]: property,
+          [input('data')]: data,
+          [input.myself()]: myself,
+        }) => {
+          if (!caches.has(property)) {
+            const cache = new WeakMap();
+            caches.set(property, cache);
+
+            const cacheRecord = new WeakMap();
+            cache.set(data, cacheRecord);
+
+            return continuation({
+              ['#cacheRecord']: cacheRecord,
+            });
+          }
+
+          const cache = caches.get(property);
+
+          if (!cache.has(data)) {
+            const cacheRecord = new WeakMap();
+            cache.set(data, cacheRecord);
+
+            return continuation({
+              ['#cacheRecord']: cacheRecord,
+            });
+          }
+
+          return continuation.raiseOutput({
+            [outputName]:
+              cache.get(data).get(myself) ?? [],
+          });
+        },
+      },
+
+      ...customCompositionSteps(),
+
+      // Actually fill in the cache record. Since we're building up a *reverse*
+      // reference list, track connections in terms of the referenced thing.
+      // Although we gather all referenced things into a set and provide that
+      // for sorting purposes in the next step, we *don't* reprovide the cache
+      // record, because we're mutating that in-place - we'll just reuse its
+      // existing '#cacheRecord' dependency.
+      {
+        dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'],
+        compute: (continuation, {
+          ['#cacheRecord']: cacheRecord,
+          ['#referencingThings']: referencingThings,
+          ['#referencedThings']: referencedThings,
+        }) => {
+          const allReferencedThings = new Set();
+
+          stitchArrays({
+            referencingThing: referencingThings,
+            referencedThings: referencedThings,
+          }).forEach(({referencingThing, referencedThings}) => {
+              for (const referencedThing of referencedThings) {
+                if (cacheRecord.has(referencedThing)) {
+                  cacheRecord.get(referencedThing).push(referencingThing);
+                } else {
+                  cacheRecord.set(referencedThing, [referencingThing]);
+                  allReferencedThings.add(referencedThing);
+                }
+              }
+            });
+
+          return continuation({
+            ['#allReferencedThings']:
+              allReferencedThings,
+          });
+        },
+      },
+
+      // Sort the entries in the cache records, too, just by date - the rest of
+      // sorting should be handled outside of this composition, either preceding
+      // (changing the 'data' input) or following (sorting the output).
+      // Again we're mutating in place, so no need to reprovide '#cacheRecord'
+      // here.
+      {
+        dependencies: ['#cacheRecord', '#allReferencedThings'],
+        compute: (continuation, {
+          ['#cacheRecord']: cacheRecord,
+          ['#allReferencedThings']: allReferencedThings,
+        }) => {
+          for (const referencedThing of allReferencedThings) {
+            if (cacheRecord.has(referencedThing)) {
+              const referencingThings = cacheRecord.get(referencedThing);
+              sortByDate(referencingThings);
+            }
+          }
+
+          return continuation();
+        },
+      },
+
+      // Then just pluck out the current object from the now-filled cache record!
+      {
+        dependencies: ['#cacheRecord', input.myself()],
+        compute: (continuation, {
+          ['#cacheRecord']: cacheRecord,
+          [input.myself()]: myself,
+        }) => continuation({
+          [outputName]:
+            cacheRecord.get(myself) ?? [],
+        }),
+      },
+    ],
+  });
+}
diff --git a/src/data/composite/wiki-data/withReverseContributionList.js b/src/data/composite/wiki-data/withReverseContributionList.js
index 07b085c4..dcf33c39 100644
--- a/src/data/composite/wiki-data/withReverseContributionList.js
+++ b/src/data/composite/wiki-data/withReverseContributionList.js
@@ -1,101 +1,18 @@
 // Analogous implementation for withReverseReferenceList, for contributions.
-// This is mostly duplicate code and both should be ported to the same
-// underlying data form later on.
-//
-// This implementation uses a global cache (via WeakMap) to attempt to speed
-// up subsequent similar accesses.
-//
-// This has absolutely not been rigorously tested with altering properties of
-// data objects in a wiki data array which is reused. If a new wiki data array
-// is used, a fresh cache will always be created.
 
-import {input, templateCompositeFrom} from '#composite';
-import {sortByDate} from '#sort';
-import {stitchArrays} from '#sugar';
+import withReverseList_template from './helpers/withReverseList-template.js';
 
-import {exitWithoutDependency, raiseOutputWithoutDependency}
-  from '#composite/control-flow';
-import {withFlattenedList, withMappedList} from '#composite/data';
-
-import inputWikiData from './inputWikiData.js';
+import {input} from '#composite';
 
-// Mapping of reference list property to WeakMap.
-// Each WeakMap maps a wiki data array to another weak map,
-// which in turn maps each referenced thing to an array of
-// things referencing it.
-const caches = new Map();
+import {withFlattenedList, withMappedList} from '#composite/data';
 
-export default templateCompositeFrom({
+export default withReverseList_template({
   annotation: `withReverseContributionList`,
 
-  inputs: {
-    data: inputWikiData({allowMixedTypes: false}),
-    list: input({type: 'string'}),
-  },
-
-  outputs: ['#reverseContributionList'],
-
-  steps: () => [
-    // Common behavior --
-
-    // Early exit with an empty array if the data list isn't available.
-    exitWithoutDependency({
-      dependency: input('data'),
-      value: input.value([]),
-    }),
-
-    // Raise an empty array (don't early exit) if the data list is empty.
-    raiseOutputWithoutDependency({
-      dependency: input('data'),
-      mode: input.value('empty'),
-      output: input.value({'#reverseContributionList': []}),
-    }),
-
-    // Check for an existing cache record which corresponds to this
-    // input('list') and input('data'). If it exists, query it for the
-    // current thing, and raise that; if it doesn't, create it, put it
-    // where it needs to be, and provide it so the next steps can fill
-    // it in.
-    {
-      dependencies: [input('list'), input('data'), input.myself()],
-
-      compute: (continuation, {
-        [input('list')]: list,
-        [input('data')]: data,
-        [input.myself()]: myself,
-      }) => {
-        if (!caches.has(list)) {
-          const cache = new WeakMap();
-          caches.set(list, cache);
-
-          const cacheRecord = new WeakMap();
-          cache.set(data, cacheRecord);
-
-          return continuation({
-            ['#cacheRecord']: cacheRecord,
-          });
-        }
-
-        const cache = caches.get(list);
-
-        if (!cache.has(data)) {
-          const cacheRecord = new WeakMap();
-          cache.set(data, cacheRecord);
-
-          return continuation({
-            ['#cacheRecord']: cacheRecord,
-          });
-        }
-
-        return continuation.raiseOutput({
-          ['#reverseContributionList']:
-            cache.get(data).get(myself) ?? [],
-        });
-      },
-    },
-
-    // Unique behavior for contribution lists --
+  propertyInputName: 'list',
+  outputName: '#reverseContributionList',
 
+  customCompositionSteps: () => [
     {
       dependencies: [input('list')],
       compute: (continuation, {
@@ -125,77 +42,5 @@ export default templateCompositeFrom({
     }).outputs({
       '#mappedList': '#referencedThings',
     }),
-
-    // Common behavior --
-
-    // Actually fill in the cache record. Since we're building up a *reverse*
-    // reference list, track connections in terms of the referenced thing.
-    // Although we gather all referenced things into a set and provide that
-    // for sorting purposes in the next step, we *don't* reprovide the cache
-    // record, because we're mutating that in-place - we'll just reuse its
-    // existing '#cacheRecord' dependency.
-    {
-      dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'],
-      compute: (continuation, {
-        ['#cacheRecord']: cacheRecord,
-        ['#referencingThings']: referencingThings,
-        ['#referencedThings']: referencedThings,
-      }) => {
-        const allReferencedThings = new Set();
-
-        stitchArrays({
-          referencingThing: referencingThings,
-          referencedThings: referencedThings,
-        }).forEach(({referencingThing, referencedThings}) => {
-            for (const referencedThing of referencedThings) {
-              if (cacheRecord.has(referencedThing)) {
-                cacheRecord.get(referencedThing).push(referencingThing);
-              } else {
-                cacheRecord.set(referencedThing, [referencingThing]);
-                allReferencedThings.add(referencedThing);
-              }
-            }
-          });
-
-        return continuation({
-          ['#allReferencedThings']:
-            allReferencedThings,
-        });
-      },
-    },
-
-    // Sort the entries in the cache records, too, just by date - the rest of
-    // sorting should be handled outside of withReverseContributionList, either
-    // preceding (changing the 'data' input) or following (sorting the output).
-    // Again we're mutating in place, so no need to reprovide '#cacheRecord'
-    // here.
-    {
-      dependencies: ['#cacheRecord', '#allReferencedThings'],
-      compute: (continuation, {
-        ['#cacheRecord']: cacheRecord,
-        ['#allReferencedThings']: allReferencedThings,
-      }) => {
-        for (const referencedThing of allReferencedThings) {
-          if (cacheRecord.has(referencedThing)) {
-            const referencingThings = cacheRecord.get(referencedThing);
-            sortByDate(referencingThings);
-          }
-        }
-
-        return continuation();
-      },
-    },
-
-    // Then just pluck out the current object from the now-filled cache record!
-    {
-      dependencies: ['#cacheRecord', input.myself()],
-      compute: (continuation, {
-        ['#cacheRecord']: cacheRecord,
-        [input.myself()]: myself,
-      }) => continuation({
-        ['#reverseContributionList']:
-          cacheRecord.get(myself) ?? [],
-      }),
-    },
   ],
 });
diff --git a/src/data/composite/wiki-data/withReverseReferenceList.js b/src/data/composite/wiki-data/withReverseReferenceList.js
index 2da9194d..70d9a58d 100644
--- a/src/data/composite/wiki-data/withReverseReferenceList.js
+++ b/src/data/composite/wiki-data/withReverseReferenceList.js
@@ -1,103 +1,19 @@
 // Check out the info on reverseReferenceList!
 // This is its composable form.
-//
-// This implementation uses a global cache (via WeakMap) to attempt to speed
-// up subsequent similar accesses.
-//
-// This has absolutely not been rigorously tested with altering properties of
-// data objects in a wiki data array which is reused. If a new wiki data array
-// is used, a fresh cache will always be created.
-//
-// Note that this implementation is mirrored in withReverseContributionList,
-// so any changes should be reflected there (until these are combined).
 
-import {input, templateCompositeFrom} from '#composite';
-import {sortByDate} from '#sort';
-import {stitchArrays} from '#sugar';
+import withReverseList_template from './helpers/withReverseList-template.js';
 
-import {exitWithoutDependency, raiseOutputWithoutDependency}
-  from '#composite/control-flow';
-import {withMappedList} from '#composite/data';
-
-import inputWikiData from './inputWikiData.js';
+import {input} from '#composite';
 
-// Mapping of reference list property to WeakMap.
-// Each WeakMap maps a wiki data array to another weak map,
-// which in turn maps each referenced thing to an array of
-// things referencing it.
-const caches = new Map();
+import {withMappedList} from '#composite/data';
 
-export default templateCompositeFrom({
+export default withReverseList_template({
   annotation: `withReverseReferenceList`,
 
-  inputs: {
-    data: inputWikiData({allowMixedTypes: false}),
-    list: input({type: 'string'}),
-  },
-
-  outputs: ['#reverseReferenceList'],
-
-  steps: () => [
-    // Common behavior --
-
-    // Early exit with an empty array if the data list isn't available.
-    exitWithoutDependency({
-      dependency: input('data'),
-      value: input.value([]),
-    }),
-
-    // Raise an empty array (don't early exit) if the data list is empty.
-    raiseOutputWithoutDependency({
-      dependency: input('data'),
-      mode: input.value('empty'),
-      output: input.value({'#reverseReferenceList': []}),
-    }),
-
-    // Check for an existing cache record which corresponds to this
-    // input('list') and input('data'). If it exists, query it for the
-    // current thing, and raise that; if it doesn't, create it, put it
-    // where it needs to be, and provide it so the next steps can fill
-    // it in.
-    {
-      dependencies: [input('list'), input('data'), input.myself()],
-
-      compute: (continuation, {
-        [input('list')]: list,
-        [input('data')]: data,
-        [input.myself()]: myself,
-      }) => {
-        if (!caches.has(list)) {
-          const cache = new WeakMap();
-          caches.set(list, cache);
-
-          const cacheRecord = new WeakMap();
-          cache.set(data, cacheRecord);
-
-          return continuation({
-            ['#cacheRecord']: cacheRecord,
-          });
-        }
-
-        const cache = caches.get(list);
-
-        if (!cache.has(data)) {
-          const cacheRecord = new WeakMap();
-          cache.set(data, cacheRecord);
-
-          return continuation({
-            ['#cacheRecord']: cacheRecord,
-          });
-        }
-
-        return continuation.raiseOutput({
-          ['#reverseReferenceList']:
-            cache.get(data).get(myself) ?? [],
-        });
-      },
-    },
-
-    // Unique behavior for reference lists --
+  propertyInputName: 'list',
+  outputName: '#reverseReferenceList',
 
+  customCompositionSteps: () => [
     {
       dependencies: [input('list')],
       compute: (continuation, {
@@ -124,77 +40,5 @@ export default templateCompositeFrom({
           data,
       }),
     },
-
-    // Common behavior --
-
-    // Actually fill in the cache record. Since we're building up a *reverse*
-    // reference list, track connections in terms of the referenced thing.
-    // Although we gather all referenced things into a set and provide that
-    // for sorting purposes in the next step, we *don't* reprovide the cache
-    // record, because we're mutating that in-place - we'll just reuse its
-    // existing '#cacheRecord' dependency.
-    {
-      dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'],
-      compute: (continuation, {
-        ['#cacheRecord']: cacheRecord,
-        ['#referencingThings']: referencingThings,
-        ['#referencedThings']: referencedThings,
-      }) => {
-        const allReferencedThings = new Set();
-
-        stitchArrays({
-          referencingThing: referencingThings,
-          referencedThings: referencedThings,
-        }).forEach(({referencingThing, referencedThings}) => {
-            for (const referencedThing of referencedThings) {
-              if (cacheRecord.has(referencedThing)) {
-                cacheRecord.get(referencedThing).push(referencingThing);
-              } else {
-                cacheRecord.set(referencedThing, [referencingThing]);
-                allReferencedThings.add(referencedThing);
-              }
-            }
-          });
-
-        return continuation({
-          ['#allReferencedThings']:
-            allReferencedThings,
-        });
-      },
-    },
-
-    // Sort the entries in the cache records, too, just by date - the rest of
-    // sorting should be handled outside of withReverseContributionList, either
-    // preceding (changing the 'data' input) or following (sorting the output).
-    // Again we're mutating in place, so no need to reprovide '#cacheRecord'
-    // here.
-    {
-      dependencies: ['#cacheRecord', '#allReferencedThings'],
-      compute: (continuation, {
-        ['#cacheRecord']: cacheRecord,
-        ['#allReferencedThings']: allReferencedThings,
-      }) => {
-        for (const referencedThing of allReferencedThings) {
-          if (cacheRecord.has(referencedThing)) {
-            const referencingThings = cacheRecord.get(referencedThing);
-            sortByDate(referencingThings);
-          }
-        }
-
-        return continuation();
-      },
-    },
-
-    // Then just pluck out the current object from the now-filled cache record!
-    {
-      dependencies: ['#cacheRecord', input.myself()],
-      compute: (continuation, {
-        ['#cacheRecord']: cacheRecord,
-        [input.myself()]: myself,
-      }) => continuation({
-        ['#reverseReferenceList']:
-          cacheRecord.get(myself) ?? [],
-      }),
-    },
   ],
 });