diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-08-22 13:02:19 -0300 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2023-09-05 21:02:49 -0300 |
commit | 75691866ed68b9261dd920b79d4ab214df3f049b (patch) | |
tree | 0a8d328279498631bdab9eaa2afedcee5574c7fb | |
parent | 93448ef747b681d3b87b050b555311c0172b83cc (diff) |
data: filter only requested deps, require requesting 'this'
* Thing.composite.from() only provides the dependencies specified in each step and the base, and prevents '#'-prefixed keys from being specified on the main (composite) dependency list. * CacheableObject no longer provides a "reflection" dependency to every compute/transform function, and now requires the property 'this' to be specified instead of the constructor.instance symbol. (The static CacheableObject.instance, inherited by all subclasses, was also removed.) * Also minor improvements to sugar.js data processing utility functions.
-rw-r--r-- | src/data/things/art-tag.js | 4 | ||||
-rw-r--r-- | src/data/things/artist.js | 16 | ||||
-rw-r--r-- | src/data/things/cacheable-object.js | 33 | ||||
-rw-r--r-- | src/data/things/flash.js | 8 | ||||
-rw-r--r-- | src/data/things/group.js | 13 | ||||
-rw-r--r-- | src/data/things/thing.js | 48 | ||||
-rw-r--r-- | src/data/things/track.js | 85 | ||||
-rw-r--r-- | src/util/sugar.js | 24 |
8 files changed, 147 insertions, 84 deletions
diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js index c103c4d5..bb36e09e 100644 --- a/src/data/things/art-tag.js +++ b/src/data/things/art-tag.js @@ -37,8 +37,8 @@ export class ArtTag extends Thing { flags: {expose: true}, expose: { - dependencies: ['albumData', 'trackData'], - compute: ({albumData, trackData, [ArtTag.instance]: artTag}) => + dependencies: ['this', 'albumData', 'trackData'], + compute: ({this: artTag, albumData, trackData}) => sortAlbumsTracksChronologically( [...albumData, ...trackData] .filter(({artTags}) => artTags.includes(artTag)), diff --git a/src/data/things/artist.js b/src/data/things/artist.js index 4f157bc6..bde84cfa 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -66,9 +66,9 @@ export class Artist extends Thing { flags: {expose: true}, expose: { - dependencies: ['trackData'], + dependencies: ['this', 'trackData'], - compute: ({trackData, [Artist.instance]: artist}) => + compute: ({this: artist, trackData}) => trackData?.filter((track) => [ ...track.artistContribs ?? [], @@ -82,9 +82,9 @@ export class Artist extends Thing { flags: {expose: true}, expose: { - dependencies: ['trackData'], + dependencies: ['this', 'trackData'], - compute: ({trackData, [Artist.instance]: artist}) => + compute: ({this: artist, trackData}) => trackData?.filter(({commentatorArtists}) => commentatorArtists.includes(artist)) ?? [], }, @@ -103,9 +103,9 @@ export class Artist extends Thing { flags: {expose: true}, expose: { - dependencies: ['albumData'], + dependencies: [this, 'albumData'], - compute: ({albumData, [Artist.instance]: artist}) => + compute: ({this: artist, albumData}) => albumData?.filter(({commentatorArtists}) => commentatorArtists.includes(artist)) ?? [], }, @@ -148,11 +148,11 @@ export class Artist extends Thing { flags: {expose: true}, expose: { - dependencies: [thingDataProperty], + dependencies: ['this', thingDataProperty], compute: ({ + this: artist, [thingDataProperty]: thingData, - [Artist.instance]: artist }) => thingData?.filter(thing => thing[contribsProperty] diff --git a/src/data/things/cacheable-object.js b/src/data/things/cacheable-object.js index ea705a61..24a6cf01 100644 --- a/src/data/things/cacheable-object.js +++ b/src/data/things/cacheable-object.js @@ -83,8 +83,6 @@ function inspect(value) { } export default class CacheableObject { - static instance = Symbol('CacheableObject `this` instance'); - #propertyUpdateValues = Object.create(null); #propertyUpdateCacheInvalidators = Object.create(null); @@ -250,20 +248,27 @@ export default class CacheableObject { let getAllDependencies; - const dependencyKeys = expose.dependencies; - if (dependencyKeys?.length > 0) { - const reflectionEntry = [this.constructor.instance, this]; - const dependencyGetters = dependencyKeys - .map(key => () => [key, this.#propertyUpdateValues[key]]); + if (expose.dependencies?.length > 0) { + const dependencyKeys = expose.dependencies.slice(); + const shouldReflect = dependencyKeys.includes('this'); + + getAllDependencies = () => { + const dependencies = Object.create(null); + + for (const key of dependencyKeys) { + dependencies[key] = this.#propertyUpdateValues[key]; + } - getAllDependencies = () => - Object.fromEntries(dependencyGetters - .map(f => f()) - .concat([reflectionEntry])); + if (shouldReflect) { + dependencies.this = this; + } + + return dependencies; + }; } else { - const allDependencies = {[this.constructor.instance]: this}; - Object.freeze(allDependencies); - getAllDependencies = () => allDependencies; + const dependencies = Object.create(null); + Object.freeze(dependencies); + getAllDependencies = () => dependencies; } if (flags.update) { diff --git a/src/data/things/flash.js b/src/data/things/flash.js index 6eb5234f..445fd07c 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash.js @@ -77,9 +77,9 @@ export class Flash extends Thing { flags: {expose: true}, expose: { - dependencies: ['flashActData'], + dependencies: ['this', 'flashActData'], - compute: ({flashActData, [Flash.instance]: flash}) => + compute: ({this: flash, flashActData}) => flashActData.find((act) => act.flashes.includes(flash)) ?? null, }, }, @@ -88,9 +88,9 @@ export class Flash extends Thing { flags: {expose: true}, expose: { - dependencies: ['flashActData'], + dependencies: ['this', 'flashActData'], - compute: ({flashActData, [Flash.instance]: flash}) => + compute: ({this: flash, flashActData}) => flashActData.find((act) => act.flashes.includes(flash))?.color ?? null, }, }, diff --git a/src/data/things/group.js b/src/data/things/group.js index ba339b3e..f552b8f3 100644 --- a/src/data/things/group.js +++ b/src/data/things/group.js @@ -41,8 +41,8 @@ export class Group extends Thing { flags: {expose: true}, expose: { - dependencies: ['albumData'], - compute: ({albumData, [Group.instance]: group}) => + dependencies: ['this', 'albumData'], + compute: ({this: group, albumData}) => albumData?.filter((album) => album.groups.includes(group)) ?? [], }, }, @@ -51,9 +51,8 @@ export class Group extends Thing { flags: {expose: true}, expose: { - dependencies: ['groupCategoryData'], - - compute: ({groupCategoryData, [Group.instance]: group}) => + dependencies: ['this', 'groupCategoryData'], + compute: ({this: group, groupCategoryData}) => groupCategoryData.find((category) => category.groups.includes(group)) ?.color, }, @@ -63,8 +62,8 @@ export class Group extends Thing { flags: {expose: true}, expose: { - dependencies: ['groupCategoryData'], - compute: ({groupCategoryData, [Group.instance]: group}) => + dependencies: ['this', 'groupCategoryData'], + compute: ({this: group, groupCategoryData}) => groupCategoryData.find((category) => category.groups.includes(group)) ?? null, }, diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 5d14b296..bc10e06b 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, openAggregate} from '#sugar'; +import {empty, filterProperties, openAggregate} from '#sugar'; import {getKebabCase} from '#wiki-data'; import { @@ -278,6 +278,7 @@ export default class Thing extends CacheableObject { flags: {expose: true}, expose: { dependencies: [ + 'this', contribsByRefProperty, thingDataProperty, nullerProperty, @@ -285,7 +286,7 @@ export default class Thing extends CacheableObject { ].filter(Boolean), compute({ - [Thing.instance]: thing, + this: thing, [nullerProperty]: nuller, [contribsByRefProperty]: contribsByRef, [thingDataProperty]: thingData, @@ -330,9 +331,9 @@ export default class Thing extends CacheableObject { flags: {expose: true}, expose: { - dependencies: [thingDataProperty], + dependencies: ['this', thingDataProperty], - compute: ({[thingDataProperty]: thingData, [Thing.instance]: thing}) => + compute: ({this: thing, [thingDataProperty]: thingData}) => thingData?.filter(t => t[referencerRefListProperty].includes(thing)) ?? [], }, }), @@ -344,9 +345,9 @@ export default class Thing extends CacheableObject { flags: {expose: true}, expose: { - dependencies: [thingDataProperty], + dependencies: ['this', thingDataProperty], - compute: ({[thingDataProperty]: thingData, [Thing.instance]: thing}) => + compute: ({this: thing, [thingDataProperty]: thingData}) => thingData?.filter((t) => t[referencerRefListProperty] === thing) ?? [], }, }), @@ -462,15 +463,19 @@ export default class Thing extends CacheableObject { if (step.expose.dependencies) { for (const dependency of step.expose.dependencies) { + if (typeof dependency === 'string' && dependency.startsWith('#')) continue; exposeDependencies.add(dependency); } } + let fn, type; if (base.flags.update) { if (step.expose.transform) { - exposeFunctionOrder.push({type: 'transform', fn: step.expose.transform}); + type = 'transform'; + fn = step.expose.transform; } else { - exposeFunctionOrder.push({type: 'compute', fn: step.expose.compute}); + type = 'compute'; + fn = step.expose.compute; } } else { if (step.expose.transform && !step.expose.compute) { @@ -478,8 +483,15 @@ export default class Thing extends CacheableObject { break expose; } - exposeFunctionOrder.push({type: 'compute', fn: step.expose.compute}); + type = 'compute'; + fn = step.expose.compute; } + + exposeFunctionOrder.push({ + type, + fn, + ownDependencies: step.expose.dependencies, + }); } }); } @@ -509,15 +521,20 @@ export default class Thing extends CacheableObject { const dependencies = {...initialDependencies}; let valueSoFar = value; - for (const {type, fn} of exposeFunctionOrder) { + for (const {type, fn, ownDependencies} of exposeFunctionOrder) { + const filteredDependencies = + (ownDependencies + ? filterProperties(dependencies, ownDependencies) + : {}) + const result = (type === 'transform' - ? fn(valueSoFar, dependencies, (updatedValue, providedDependencies) => { + ? fn(valueSoFar, filteredDependencies, (updatedValue, providedDependencies) => { valueSoFar = updatedValue ?? null; Object.assign(dependencies, providedDependencies ?? {}); return continuationSymbol; }) - : fn(dependencies, providedDependencies => { + : fn(filteredDependencies, providedDependencies => { Object.assign(dependencies, providedDependencies ?? {}); return continuationSymbol; })); @@ -527,10 +544,13 @@ export default class Thing extends CacheableObject { } } + const filteredDependencies = + filterProperties(dependencies, base.expose.dependencies); + if (base.expose.transform) { - return base.expose.transform(valueSoFar, dependencies); + return base.expose.transform(valueSoFar, filteredDependencies); } else { - return base.expose.compute(dependencies); + return base.expose.compute(filteredDependencies); } }; } else { diff --git a/src/data/things/track.js b/src/data/things/track.js index 30c6fe58..551d9345 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -59,7 +59,8 @@ export class Track extends Thing { flags: {update: true, expose: true}, update: {validate: isColor}, expose: { - compute: ({album: {color}}) => color, + dependencies: ['#album.color'], + compute: ({'#album.color': color}) => color, }, }, ]), @@ -75,18 +76,27 @@ export class Track extends Thing { // main artwork. (It does inherit `trackCoverArtFileExtension` if present // on the album.) coverArtFileExtension: Thing.composite.from([ - Track.composite.withAlbumProperties(['trackCoverArtistContribsByRef', 'trackCoverArtFileExtension']), + Track.composite.withAlbumProperties([ + 'trackCoverArtistContribsByRef', + 'trackCoverArtFileExtension', + ]), { flags: {update: true, expose: true}, update: {validate: isFileExtension}, expose: { - dependencies: ['coverArtistContribsByRef', 'disableUniqueCoverArt'], + dependencies: [ + 'coverArtistContribsByRef', + 'disableUniqueCoverArt', + '#album.trackCoverArtistContribsByRef', + '#album.trackCoverArtFileExtension', + ], transform(coverArtFileExtension, { coverArtistContribsByRef, disableUniqueCoverArt, - album: {trackCoverArtistContribsByRef, trackCoverArtFileExtension}, + '#album.trackCoverArtistContribsByRef': trackCoverArtistContribsByRef, + '#album.trackCoverArtFileExtension': trackCoverArtFileExtension, }) { if (disableUniqueCoverArt) return null; if (empty(coverArtistContribsByRef) && empty(trackCoverArtistContribsByRef)) return null; @@ -101,18 +111,27 @@ export class Track extends Thing { // the track's own coverArtDate or its album's trackArtDate, so if neither // is specified, this value is null. coverArtDate: Thing.composite.from([ - Track.composite.withAlbumProperties(['trackArtDate', 'trackCoverArtistContribsByRef']), + Track.composite.withAlbumProperties([ + 'trackArtDate', + 'trackCoverArtistContribsByRef', + ]), { flags: {update: true, expose: true}, update: {validate: isDate}, expose: { - dependencies: ['coverArtistContribsByRef', 'disableUniqueCoverArt'], + dependencies: [ + 'coverArtistContribsByRef', + 'disableUniqueCoverArt', + '#album.trackArtDate', + '#album.trackCoverArtistContribsByRef', + ], transform(coverArtDate, { coverArtistContribsByRef, disableUniqueCoverArt, - album: {trackArtDate, trackCoverArtistContribsByRef}, + '#album.trackArtDate': trackArtDate, + '#album.trackCoverArtistContribsByRef': trackCoverArtistContribsByRef, }) { if (disableUniqueCoverArt) return null; if (empty(coverArtistContribsByRef) && empty(trackCoverArtistContribsByRef)) return null; @@ -148,8 +167,8 @@ export class Track extends Thing { flags: {expose: true}, expose: { - dependencies: ['albumData'], - compute: ({[Track.instance]: track, albumData}) => + dependencies: ['this', 'albumData'], + compute: ({this: track, albumData}) => albumData?.find((album) => album.tracks.includes(track)) ?? null, }, }, @@ -182,7 +201,8 @@ export class Track extends Thing { { flags: {expose: true}, expose: { - compute: ({album: {date}}) => date, + dependencies: ['#album.date'], + compute: ({'#album.date': date}) => date, }, }, ]), @@ -200,11 +220,16 @@ export class Track extends Thing { { flags: {expose: true}, expose: { - dependencies: ['coverArtistContribsByRef', 'disableUniqueCoverArt'], + dependencies: [ + 'coverArtistContribsByRef', + 'disableUniqueCoverArt', + '#album.trackCoverArtistContribsByRef', + ], + compute({ coverArtistContribsByRef, disableUniqueCoverArt, - album: {trackCoverArtistContribsByRef}, + '#album.trackCoverArtistContribsByRef': trackCoverArtistContribsByRef, }) { if (disableUniqueCoverArt) return false; if (!empty(coverArtistContribsByRef)) return true; @@ -225,12 +250,12 @@ export class Track extends Thing { flags: {expose: true}, expose: { - dependencies: ['originalReleaseTrackByRef', 'trackData'], + dependencies: ['this', 'originalReleaseTrackByRef', 'trackData'], compute: ({ + this: t1, originalReleaseTrackByRef: t1origRef, trackData, - [Track.instance]: t1, }) => { if (!trackData) { return []; @@ -252,15 +277,16 @@ export class Track extends Thing { artistContribs: Thing.composite.from([ Track.composite.inheritFromOriginalRelease('artistContribs'), - Thing.composite.withDynamicContribs('artistContribsByRef', 'artistContribs'), + Thing.composite.withDynamicContribs('artistContribsByRef', '#artistContribs'), Track.composite.withAlbumProperties(['artistContribs']), { flags: {expose: true}, expose: { + dependencies: ['#artistContribs', '#album.artistContribs'], compute: ({ - artistContribs: contribsFromTrack, - album: {artistContribs: contribsFromAlbum}, + '#artistContribs': contribsFromTrack, + '#album.artistContribs': contribsFromAlbum, }) => (empty(contribsFromTrack) ? contribsFromAlbum @@ -290,14 +316,15 @@ export class Track extends Thing { }, Track.composite.withAlbumProperties(['trackCoverArtistContribs']), - Thing.composite.withDynamicContribs('coverArtistContribsByRef', 'coverArtistContribs'), + Thing.composite.withDynamicContribs('coverArtistContribsByRef', '#coverArtistContribs'), { flags: {expose: true}, expose: { + dependencies: ['#coverArtistContribs', '#album.trackCoverArtistContribs'], compute: ({ - coverArtistContribs: contribsFromTrack, - album: {trackCoverArtistContribs: contribsFromAlbum}, + '#coverArtistContribs': contribsFromTrack, + '#album.trackCoverArtistContribs': contribsFromAlbum, }) => (empty(contribsFromTrack) ? contribsFromAlbum @@ -328,9 +355,9 @@ export class Track extends Thing { flags: {expose: true}, expose: { - dependencies: ['trackData'], + dependencies: ['this', 'trackData'], - compute: ({trackData, [Track.instance]: track}) => + compute: ({this: track, trackData}) => trackData ? trackData .filter((t) => !t.originalReleaseTrack) @@ -344,9 +371,9 @@ export class Track extends Thing { flags: {expose: true}, expose: { - dependencies: ['trackData'], + dependencies: ['this', 'trackData'], - compute: ({trackData, [Track.instance]: track}) => + compute: ({this: track, trackData}) => trackData ? trackData .filter((t) => !t.originalReleaseTrack) @@ -389,20 +416,20 @@ export class Track extends Thing { flags: {expose: true, compose: true}, expose: { - dependencies: ['albumData'], + dependencies: ['this', 'albumData'], - compute({albumData, [Track.instance]: track}, continuation) { + compute({this: track, albumData}, continuation) { const album = albumData?.find((album) => album.tracks.includes(track)); + const newDependencies = {}; - const filteredAlbum = Object.create(null); for (const property of albumProperties) { - filteredAlbum[property] = + newDependencies['#album.' + property] = (album ? album[property] : null); } - return continuation({album: filteredAlbum}); + return continuation(newDependencies); }, }, }), diff --git a/src/util/sugar.js b/src/util/sugar.js index 5b1f3193..1ba3f3ae 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -168,12 +168,24 @@ export function setIntersection(set1, set2) { return intersection; } -export function filterProperties(obj, properties) { - const set = new Set(properties); - return Object.fromEntries( - Object - .entries(obj) - .filter(([key]) => set.has(key))); +export function filterProperties(object, properties) { + if (typeof object !== 'object' || object === null) { + throw new TypeError(`Expected object to be an object, got ${object}`); + } + + if (!Array.isArray(properties)) { + throw new TypeError(`Expected properties to be an array, got ${properties}`); + } + + const filteredObject = {}; + + for (const property of properties) { + if (Object.hasOwn(object, property)) { + filteredObject[property] = object[property]; + } + } + + return filteredObject; } export function queue(array, max = 50) { |