« get me outta code hell

data: Thing.clone(source, {as}) and related utilities - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2026-01-26 16:10:31 -0400
committer(quasar) nebula <qznebula@protonmail.com>2026-01-26 16:10:31 -0400
commit88e30b86922c3fe013d25dcfe0177961f1f11c77 (patch)
treea0bb4de3b00e66a710257e6b40c604f782375303 /src/data
parenta074fd54107c51c4fcbfedbbf6df6eca539d19d3 (diff)
data: Thing.clone(source, {as}) and related utilities
Diffstat (limited to 'src/data')
-rw-r--r--src/data/composite/wiki-data/withClonedThings.js36
-rw-r--r--src/data/composite/wiki-data/withRecontextualizedContributionList.js14
-rw-r--r--src/data/thing.js30
3 files changed, 70 insertions, 10 deletions
diff --git a/src/data/composite/wiki-data/withClonedThings.js b/src/data/composite/wiki-data/withClonedThings.js
index 9af6aa84..36c3ba54 100644
--- a/src/data/composite/wiki-data/withClonedThings.js
+++ b/src/data/composite/wiki-data/withClonedThings.js
@@ -3,9 +3,9 @@
 // 'assignEach' input is provided, each new thing is assigned the
 // corresponding properties.
 
-import CacheableObject from '#cacheable-object';
 import {input, templateCompositeFrom} from '#composite';
-import {isObject, sparseArrayOf} from '#validators';
+import Thing from '#thing';
+import {isObject, isThingClass, sparseArrayOf} from '#validators';
 
 import {withMappedList} from '#composite/data';
 
@@ -15,6 +15,16 @@ export default templateCompositeFrom({
   inputs: {
     things: input({type: 'array'}),
 
+    reclass: input({
+      validate: isThingClass,
+      defaultValue: null,
+    }),
+
+    reclassUnder: input({
+      validate: isThingClass,
+      defaultValue: null,
+    }),
+
     assign: input({
       type: 'object',
       defaultValue: null,
@@ -46,15 +56,29 @@ export default templateCompositeFrom({
     },
 
     {
-      dependencies: ['#assignmentMap'],
+      dependencies: [input('reclass'), input('reclassUnder')],
+      compute: (continuation, {
+        [input('reclass')]: reclass,
+        [input('reclassUnder')]: reclassUnder,
+      }) => continuation({
+        ['#cloneOperation']:
+          (reclassUnder && reclass
+            ? source => reclassUnder.clone(source, {as: reclass})
+         : reclass
+            ? source => Thing.clone(source, {as: reclass})
+            : source => Thing.clone(source)),
+      }),
+    },
+
+    {
+      dependencies: ['#assignmentMap', '#cloneOperation'],
       compute: (continuation, {
         ['#assignmentMap']: assignmentMap,
+        ['#cloneOperation']: cloneOperation,
       }) => continuation({
         ['#cloningMap']:
           (thing, index) =>
-            Object.assign(
-              CacheableObject.clone(thing),
-              assignmentMap(index)),
+            Object.assign(cloneOperation(thing), assignmentMap(index)),
       }),
     },
 
diff --git a/src/data/composite/wiki-data/withRecontextualizedContributionList.js b/src/data/composite/wiki-data/withRecontextualizedContributionList.js
index bcc6e486..66ac056a 100644
--- a/src/data/composite/wiki-data/withRecontextualizedContributionList.js
+++ b/src/data/composite/wiki-data/withRecontextualizedContributionList.js
@@ -1,14 +1,15 @@
 // Clones all the contributions in a list, with thing and thingProperty both
 // updated to match the current thing. Overwrites the provided dependency.
-// Optionally updates artistProperty as well. Doesn't do anything if
-// the provided dependency is null.
+// Optionally updates artistProperty, and optionally reclasses as another
+// kind of contribution. Does nothing if the provided dependency is null.
 //
 // See also:
 //  - withRedatedContributionList
 //
 
 import {input, templateCompositeFrom} from '#composite';
-import {isStringNonEmpty} from '#validators';
+import thingConstructors from '#thing';
+import {isStringNonEmpty, isThingClass} from '#validators';
 
 import {withClonedThings} from '#composite/wiki-data';
 
@@ -21,6 +22,11 @@ export default templateCompositeFrom({
       acceptsNull: true,
     }),
 
+    reclass: input({
+      validate: isThingClass,
+      defaultValue: null,
+    }),
+
     artistProperty: input({
       validate: isStringNonEmpty,
       defaultValue: null,
@@ -77,6 +83,8 @@ export default templateCompositeFrom({
 
     withClonedThings({
       things: input('list'),
+      reclass: input('reclass'),
+      reclassUnder: input.value(thingConstructors.Contribution),
       assign: '#assignment',
     }).outputs({
       '#clonedThings': '#newContributions',
diff --git a/src/data/thing.js b/src/data/thing.js
index cc7c82b7..0a6e3be4 100644
--- a/src/data/thing.js
+++ b/src/data/thing.js
@@ -81,9 +81,37 @@ export default class Thing extends CacheableObject {
       (reference ? ` (${reference})` : ''));
   }
 
+  static clone(source, {as = null} = {}) {
+    if (!(source instanceof this)) {
+      throw new TypeError(
+        `Passed thing is ${source.constructor.name}, ` +
+        `which is not a subclass of ${this.name}`);
+    }
+
+    if (as && !(as.prototype instanceof this)) {
+      throw new TypeError(
+        `Passed constructor is ${as.name}, ` +
+        `which is not a subclass of ${this.name}`);
+    }
+
+    let clone;
+
+    if (as) {
+      clone = Reflect.construct(as, []);
+    } else {
+      clone = Reflect.construct(source.constructor, []);
+    }
+
+    CacheableObject.copyUpdateValuesOnto(source, clone);
+
+    return clone;
+  }
+
   static getReference(thing) {
     if (!thing.constructor[Thing.referenceType]) {
-      throw TypeError(`Passed Thing is ${thing.constructor.name}, which provides no [Thing.referenceType]`);
+      throw TypeError(
+        `Passed Thing is ${thing.constructor.name}, ` +
+        `which provides no [Thing.referenceType]`);
     }
 
     if (!thing.directory) {