« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/thing
diff options
context:
space:
mode:
Diffstat (limited to 'src/thing')
-rw-r--r--src/thing/album.js42
-rw-r--r--src/thing/cacheable-object.js36
-rw-r--r--src/thing/track.js54
-rw-r--r--src/thing/validators.js8
4 files changed, 122 insertions, 18 deletions
diff --git a/src/thing/album.js b/src/thing/album.js
index 8a9fde2c..426796b0 100644
--- a/src/thing/album.js
+++ b/src/thing/album.js
@@ -1,6 +1,5 @@
 import CacheableObject from './cacheable-object.js';
 import Thing from './thing.js';
-import find from '../util/find.js';
 
 import {
     isBoolean,
@@ -10,6 +9,7 @@ import {
     isDate,
     isDimensions,
     isDirectory,
+    isInstance,
     isFileExtension,
     isName,
     isURL,
@@ -20,6 +20,10 @@ import {
     validateReferenceList,
 } from './validators.js';
 
+import Track from './track.js';
+
+import find from '../util/find.js';
+
 export class TrackGroup extends CacheableObject {
     static propertyDescriptors = {
         // Update & expose
@@ -64,7 +68,12 @@ export class TrackGroup extends CacheableObject {
             expose: {
                 dependencies: ['tracksByRef', 'trackData'],
                 compute: ({ tracksByRef, trackData }) => (
-                    tracksByRef.map(ref => find.track(ref, {wikiData: {trackData}})))
+                    (tracksByRef && trackData
+                        ? (tracksByRef
+                            .map(ref => find.track(ref, {wikiData: {trackData}}))
+                            .filter(Boolean))
+                        : [])
+                )
             }
         }
     };
@@ -227,26 +236,29 @@ export default class Album extends Thing {
             update: {validate: isCommentary}
         },
 
+        // Update only
+
+        trackData: {
+            flags: {update: true},
+            update: {validate: validateArrayItems(x => x instanceof Track)}
+        },
+
         // Expose only
 
-        /*
         tracks: {
             flags: {expose: true},
 
             expose: {
-                dependencies: ['trackReferences', 'wikiData'],
-                compute: ({trackReferences, wikiData}) => (
-                    trackReferences.map(ref => find.track(ref, {wikiData})))
+                dependencies: ['trackGroups', 'trackData'],
+                compute: ({ trackGroups, trackData }) => (
+                    (trackGroups && trackData
+                        ? (trackGroups
+                            .flatMap(group => group.tracksByRef ?? [])
+                            .map(ref => find.track(ref, {wikiData: {trackData}}))
+                            .filter(Boolean))
+                        : [])
+                )
             }
         },
-        */
-
-        // Update only
-
-        /*
-        wikiData: {
-            flags: {update: true}
-        }
-        */
     };
 }
diff --git a/src/thing/cacheable-object.js b/src/thing/cacheable-object.js
index 3c14101c..9af41603 100644
--- a/src/thing/cacheable-object.js
+++ b/src/thing/cacheable-object.js
@@ -83,6 +83,8 @@ function inspect(value) {
 }
 
 export default class CacheableObject {
+    static instance = Symbol('CacheableObject `this` instance');
+
     #propertyUpdateValues = Object.create(null);
     #propertyUpdateCacheInvalidators = Object.create(null);
 
@@ -100,6 +102,19 @@ export default class CacheableObject {
     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() {
@@ -227,7 +242,8 @@ export default class CacheableObject {
 
         const dependencyKeys = expose.dependencies || [];
         const dependencyGetters = dependencyKeys.map(key => () => [key, this.#propertyUpdateValues[key]]);
-        const getAllDependencies = () => Object.fromEntries(dependencyGetters.map(f => f()));
+        const getAllDependencies = () => Object.fromEntries(dependencyGetters.map(f => f())
+            .concat([[this.constructor.instance, this]]));
 
         if (flags.update) {
             return () => transform(this.#propertyUpdateValues[property], getAllDependencies());
@@ -268,4 +284,22 @@ export default class CacheableObject {
             }
         };
     }
+
+    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;
+        }
+
+        console.log(`${this._invalidAccesses.size} unique invalid accesses:`);
+        for (const line of this._invalidAccesses) {
+            console.log(` - ${line}`);
+        }
+    }
 }
diff --git a/src/thing/track.js b/src/thing/track.js
index 75df109a..d0e88acf 100644
--- a/src/thing/track.js
+++ b/src/thing/track.js
@@ -16,6 +16,11 @@ import {
     validateReferenceList,
 } from './validators.js';
 
+import Album from './album.js';
+import ArtTag from './art-tag.js';
+
+import find from '../util/find.js';
+
 export default class Track extends Thing {
     static [Thing.referenceType] = 'track';
 
@@ -112,6 +117,55 @@ export default class Track extends Thing {
 
         // Update only
 
+        albumData: {
+            flags: {update: true},
+            update: {validate: validateArrayItems(x => x instanceof Album)}
+        },
+
+        artTagData: {
+            flags: {update: true},
+            update: {validate: validateArrayItems(x => x instanceof ArtTag)}
+        },
+
         // Expose only
+
+        album: {
+            flags: {expose: true},
+
+            expose: {
+                dependencies: ['albumData'],
+                compute: ({ [this.instance]: track, albumData }) => (
+                    albumData?.find(album => album.tracks.includes(track)) ?? null)
+            }
+        },
+
+        date: {
+            flags: {expose: true},
+
+            expose: {
+                dependencies: ['albumData', 'dateFirstReleased'],
+                compute: ({ albumData, dateFirstReleased, [this.instance]: track }) => (
+                    dateFirstReleased ??
+                    albumData?.find(album => album.tracks.includes(track))?.date ??
+                    null
+                )
+            }
+        },
+
+        artTags: {
+            flags: {expose: true},
+
+            expose: {
+                dependencies: ['artTagsByRef', 'artTagData'],
+
+                compute: ({ artTagsByRef, artTagData }) => (
+                    (artTagsByRef && artTagData
+                        ? (artTagsByRef
+                            .map(ref => find.tag(ref, {wikiData: {tagData: artTagData}}))
+                            .filter(Boolean))
+                        : [])
+                )
+            }
+        }
     };
 }
diff --git a/src/thing/validators.js b/src/thing/validators.js
index 1bc7fd72..83922229 100644
--- a/src/thing/validators.js
+++ b/src/thing/validators.js
@@ -96,7 +96,7 @@ export function isStringNonEmpty(value) {
 
 // Complex types (non-primitives)
 
-function isInstance(value, constructor) {
+export function isInstance(value, constructor) {
     isObject(value);
 
     if (!(value instanceof constructor))
@@ -133,7 +133,11 @@ export function isArray(value) {
 function validateArrayItemsHelper(itemValidator) {
     return (item, index) => {
         try {
-            itemValidator(item);
+            const value = itemValidator(item);
+
+            if (value !== true) {
+                throw new Error(`Expected validator to return true`);
+            }
         } catch (error) {
             error.message = `(index: ${color.green(index)}, item: ${inspect(item)}) ${error.message}`;
             throw error;