From c56a1eb78b76b52cbeeed6fe16d3511c1fd41147 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 8 Feb 2022 09:46:22 -0400 Subject: super basic album ↔ track linking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/thing/album.js | 42 +++++++++++++++++++++------------ src/thing/cacheable-object.js | 36 ++++++++++++++++++++++++++++- src/thing/track.js | 54 +++++++++++++++++++++++++++++++++++++++++++ src/thing/validators.js | 8 +++++-- 4 files changed, 122 insertions(+), 18 deletions(-) (limited to 'src/thing') 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; -- cgit 1.3.0-6-gf8a5