From 7cd3bdc4998ae1fc1b9ab4bb721d2727f64511e1 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 15 Sep 2023 20:03:25 -0300 Subject: data: miscellaneous composite template updates --- src/data/things/album.js | 10 +- src/data/things/composite.js | 23 +- src/data/things/homepage-layout.js | 11 +- src/data/things/thing.js | 543 ++++++++++++++++++------------------- src/data/things/track.js | 93 +++---- 5 files changed, 323 insertions(+), 357 deletions(-) diff --git a/src/data/things/album.js b/src/data/things/album.js index b134b78..805d177 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -12,7 +12,6 @@ import { withFlattenedArray, withPropertiesFromList, withUnflattenedArray, - withUpdateValueAsDependency, } from '#composite'; import Thing, { @@ -103,16 +102,15 @@ export class Album extends Thing { exitWithoutDependency({dependency: 'trackData', value: []}), exitWithoutUpdateValue({value: [], mode: 'empty'}), - withUpdateValueAsDependency({into: '#sections'}), - withPropertiesFromList({ - list: '#sections', - properties: [ + list: input.updateValue(), + prefix: input.value('#sections'), + properties: input.value([ 'tracks', 'dateOriginallyReleased', 'isDefaultTrackSection', 'color', - ], + ]), }), fillMissingListItems({list: '#sections.tracks', value: []}), diff --git a/src/data/things/composite.js b/src/data/things/composite.js index 091faa3..cd71316 100644 --- a/src/data/things/composite.js +++ b/src/data/things/composite.js @@ -1,6 +1,7 @@ import {inspect} from 'node:util'; import {colors} from '#cli'; +import {oneOf} from '#validators'; import {TupleMap} from '#wiki-data'; import { @@ -1357,7 +1358,7 @@ export const withResultOfAvailabilityCheck = templateCompositeFrom({ into: '#availability', }, - steps: [ + steps: () => [ { dependencies: [input('from'), input('mode')], @@ -1440,12 +1441,12 @@ export const exitWithoutDependency = templateCompositeFrom({ annotation: `exitWithoutDependency`, inputs: { - dependency: input.required(), + dependency: input(), mode: input(availabilityCheckMode), - value: input({defaultValue: null}), + value: input({null: true}), }, - steps: [ + steps: () => [ withResultOfAvailabilityCheck({ from: input('dependency'), mode: input('mode'), @@ -1474,7 +1475,7 @@ export const exitWithoutUpdateValue = templateCompositeFrom({ value: input({defaultValue: null}), }, - steps: [ + steps: () => [ exitWithoutDependency({ dependency: input.updateValue(), mode: input('mode'), @@ -1488,12 +1489,12 @@ export const raiseOutputWithoutDependency = templateCompositeFrom({ annotation: `raiseOutputWithoutDependency`, inputs: { - dependency: input.required(), + dependency: input(), mode: input(availabilityCheckMode), output: input({defaultValue: {}}), }, - steps: [ + steps: () => [ withResultOfAvailabilityCheck({ from: input('dependency'), mode: input('mode'), @@ -1522,7 +1523,7 @@ export const raiseOutputWithoutUpdateValue = templateCompositeFrom({ output: input({defaultValue: {}}), }, - steps: [ + steps: () => [ withResultOfAvailabilityCheck({ from: input.updateValue(), mode: input('mode'), @@ -1549,8 +1550,8 @@ export const withPropertyFromObject = templateCompositeFrom({ inputs: { object: input({type: 'object', null: true}), - property: input.required({type: 'string'}), - } + property: input({type: 'string'}), + }, outputs: { into: { @@ -1569,7 +1570,7 @@ export const withPropertyFromObject = templateCompositeFrom({ }, }, - steps: [ + steps: () => [ { dependencies: [input('object'), input('property')], compute: (continuation, { diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js index b509c1e..1d86f4d 100644 --- a/src/data/things/homepage-layout.js +++ b/src/data/things/homepage-layout.js @@ -3,7 +3,6 @@ import find from '#find'; import { compositeFrom, exposeDependency, - withUpdateValueAsDependency, } from '#composite'; import { @@ -115,22 +114,20 @@ export class HomepageLayoutAlbumsRow extends HomepageLayoutRow { : continuation(value)), }, - withUpdateValueAsDependency(), - withResolvedReference({ - ref: '#updateValue', + ref: input.updateValue(), data: 'groupData', - find: find.group, + find: input.value(find.group), }), exposeDependency({ dependency: '#resolvedReference', - update: { + update: input.value({ validate: oneOf( is('new-releases', 'new-additions'), validateReference(Group[Thing.referenceType])), - }, + }), }), ]), diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 5cfeaeb..d1a8fdc 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -7,6 +7,7 @@ import {colors} from '#cli'; import find from '#find'; import {empty, stitchArrays, unique} from '#sugar'; import {filterMultipleArrays, getKebabCase} from '#wiki-data'; +import {oneOf} from '#validators'; import { compositeFrom, @@ -14,10 +15,11 @@ import { exposeConstant, exposeDependency, exposeDependencyOrContinue, - raiseWithoutDependency, + input, + raiseOutputWithoutDependency, + templateCompositeFrom, withResultOfAvailabilityCheck, withPropertiesFromList, - withUpdateValueAsDependency, } from '#composite'; import { @@ -208,7 +210,7 @@ export function contributionList() { update: {validate: isContributionList}, - steps: [ + steps: () => [ withUpdateValueAsDependency(), withResolvedContribs({from: '#updateValue'}), exposeDependencyOrContinue({dependency: '#resolvedContribs'}), @@ -288,7 +290,7 @@ export function referenceList({ '#composition.findFunction': findFunction, }, - steps: [ + steps: () => [ withUpdateValueAsDependency(), withResolvedReferenceList({ @@ -332,7 +334,7 @@ export function singleReference({ '#composition.findFunction': findFunction, }, - steps: [ + steps: () => [ withUpdateValueAsDependency(), withResolvedReference({ @@ -358,7 +360,7 @@ export function contribsPresent({ '#composition.contribs': contribs, }, - steps: [ + steps: () => [ withResultOfAvailabilityCheck({ fromDependency: '#composition.contribs', mode: 'empty', @@ -383,7 +385,7 @@ export function reverseReferenceList({data, list}) { '#composition.list': list, }, - steps: [ + steps: () => [ withReverseReferenceList({ data: '#composition.data', list: '#composition.list', @@ -416,7 +418,7 @@ export function commentatorArtists() { '#composition.findFunction': find.artists, }, - steps: [ + steps: () => [ exitWithoutDependency({ dependency: 'commentary', mode: 'falsy', @@ -462,99 +464,97 @@ export function commentatorArtists() { // providing (named by the second argument) the result. "Resolving" // means mapping the "who" reference of each contribution to an artist // object, and filtering out those whose "who" doesn't match any artist. -export function withResolvedContribs({ - from, - into = '#resolvedContribs', -}) { - return compositeFrom({ - annotation: `withResolvedContribs`, - - mapDependencies: { - '#composition.from': from, - }, - - mapContinuation: { - '#composition.into': into, - }, - - constantDependencies: { - '#composition.findFunction': find.artist, - '#composition.notFoundMode': 'null', - }, - - steps: [ - raiseWithoutDependency({ - dependency: '#composition.from', - raise: {'#composition.into': []}, - mode: 'empty', - }), - - withPropertiesFromList({ - list: '#composition.from', - prefix: '#contribs', - properties: ['who', 'what'], - }), - - withResolvedReferenceList({ - list: '#contribs.who', - data: 'artistData', - into: '#contribs.who', - find: '#composition.findFunction', - notFoundMode: '#composition.notFoundMode', - }), - - { - dependencies: ['#contribs.who', '#contribs.what'], - compute({'#contribs.who': who, '#contribs.what': what}, continuation) { - filterMultipleArrays(who, what, (who, _what) => who); - return continuation({ - '#composition.into': stitchArrays({who, what}), - }); - }, +export const withResolvedContribs = templateCompositeFrom({ + annotation: `withResolvedContribs`, + + inputs: { + // todo: validate + from: input(), + + findFunction: input({type: 'function'}), + + notFoundMode: input({ + validate: oneOf('exit', 'filter', 'null'), + defaultValue: 'null', + }), + }, + + outputs: { + into: '#resolvedContribs', + }, + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: input('from'), + mode: input.value('empty'), + output: input.value({into: []}), + }), + + withPropertiesFromList({ + list: input('from'), + properties: input.value(['who', 'what']), + prefix: input.value('#contribs'), + }), + + withResolvedReferenceList({ + list: '#contribs.who', + data: 'artistData', + into: '#contribs.who', + find: input('find'), + notFoundMode: input('notFoundMode'), + }), + + { + dependencies: ['#contribs.who', '#contribs.what'], + + compute(continuation, { + ['#contribs.who']: who, + ['#contribs.what']: what, + }) { + filterMultipleArrays(who, what, (who, _what) => who); + return continuation({ + '#composition.into': stitchArrays({who, what}), + }); }, - ], - }); -} + }, + ], +}); // Shorthand for exiting if the contribution list (usually a property's update // value) resolves to empty - ensuring that the later computed results are only // returned if these contributions are present. -export function exitWithoutContribs({ - contribs, - value = null, -}) { - return compositeFrom({ - annotation: `exitWithoutContribs`, - - mapDependencies: { - '#composition.contribs': contribs, +export const exitWithoutContribs = templateCompositeFrom({ + annotation: `exitWithoutContribs`, + + inputs: { + // todo: validate + contribs: input(), + + value: input({null: true}), + }, + + steps: () => [ + withResolvedContribs({ + from: input('contribs'), + }), + + withResultOfAvailabilityCheck({ + from: '#resolvedContribs', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability', input('value')], + compute: (continuation, { + ['#availability']: availability, + [input('value')]: value, + }) => + (availability + ? continuation() + : continuation.exit(value)), }, - - constantDependencies: { - '#composition.value': value, - }, - - steps: [ - withResolvedContribs({from: '#composition.contribs'}), - - withResultOfAvailabilityCheck({ - fromDependency: '#resolvedContribs', - mode: 'empty', - }), - - { - dependencies: ['#availability', '#composition.value'], - compute: ({ - '#availability': availability, - '#composition.value': value, - }, continuation) => - (availability - ? continuation() - : continuation.exit(value)), - }, - ], - }); -} + ], +}); // Resolves a reference by using the provided find function to match it // within the provided thingData dependency. This will early exit if the @@ -562,204 +562,187 @@ export function exitWithoutContribs({ // function doesn't match anything for the reference. Otherwise, the data // object is provided on the output dependency; or null, if the reference // doesn't match anything or itself was null to begin with. -export function withResolvedReference({ - ref, - data, - find: findFunction, - into = '#resolvedReference', - notFoundMode, -}) { - return compositeFrom({ - annotation: `withResolvedReference`, - - mapDependencies: { - '#composition.ref': ref, - '#composition.data': data, - '#composition.findFunction': findFunction, - '#composition.notFoundMode': notFoundMode, - }, - - constantDependencies: { - '#composition.notFoundMode': 'null', - }, - - mapContinuation: { - '#composition.into': into, - }, - - steps: [ - raiseWithoutDependency({ - dependency: '#composition.ref', - raise: {'#composition.into': null}, - }), - - exitWithoutDependency({ - dependency: '#composition.data', - }), - - { - compute({ - '#composition.ref': ref, - '#composition.data': data, - '#composition.findFunction': findFunction, - '#composition.notFoundMode': notFoundMode, - }, continuation) { - const match = findFunction(ref, data, {mode: 'quiet'}); - - if (match === null && notFoundMode === 'exit') { - return continuation.exit(null); - } - - return continuation.raise({match}); - }, +export const withResolvedReference = templateCompositeFrom({ + annotation: `withResolvedReference`, + + inputs: { + // todo: validate + ref: input(), + + // todo: validate + data: input(), + + find: input({type: 'function'}), + + notFoundMode: input({ + validate: oneOf('null', 'exit'), + defaultValue: 'null', + }), + }, + + outputs: { + into: '#resolvedReference', + }, + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: input('ref'), + output: input.value({into: null}), + }), + + exitWithoutDependency({ + dependency: input('data'), + }), + + { + dependencies: [ + input('ref'), + input('data'), + input('find'), + input('notFoundMode'), + ], + + compute({ + [input('ref')]: ref, + [input('data')]: data, + [input('find')]: findFunction, + [input('notFoundMode')]: notFoundMode, + }, continuation) { + const match = findFunction(ref, data, {mode: 'quiet'}); + + if (match === null && notFoundMode === 'exit') { + return continuation.exit(null); + } + + return continuation.raise({match}); }, - ], - }); -} + }, + ], +}); // Resolves a list of references, with each reference matched with provided // data in the same way as withResolvedReference. This will early exit if the // data dependency is null (even if the reference list is empty). By default // it will filter out references which don't match, but this can be changed // to early exit ({notFoundMode: 'exit'}) or leave null in place ('null'). -export function withResolvedReferenceList({ - list, - data, - find: findFunction, - notFoundMode, - into = '#resolvedReferenceList', -}) { - if (!['filter', 'exit', 'null'].includes(notFoundMode)) { - throw new TypeError(`Expected notFoundMode to be filter, exit, or null`); - } - - return compositeFrom({ - annotation: `withResolvedReferenceList`, - - mapDependencies: { - '#composition.list': list, - '#composition.data': data, - '#composition.findFunction': findFunction, - '#composition.notFoundMode': notFoundMode, - }, - - constantDependencies: { - '#composition.notFoundMode': 'filter', +export const withResolvedReferenceList = templateCompositeFrom({ + annotation: `withResolvedReferenceList`, + + inputs: { + // todo: validate + list: input(), + + // todo: validate + data: input(), + + find: input({type: 'function'}), + + notFoundMode: input({ + validate: oneOf('exit', 'filter', 'null'), + defaultValue: 'filter', + }), + }, + + outputs: { + into: '#resolvedReferenceList', + }, + + steps: () => [ + exitWithoutDependency({ + dependency: input('data'), + value: input.value([]), + }), + + raiseOutputWithoutDependency({ + dependency: input('list'), + mode: input.value('empty'), + output: input.value({into: []}), + }), + + { + dependencies: [input('list'), input('data'), input('find')], + compute: ({ + [input('list')]: list, + [input('data')]: data, + [input('find')]: findFunction, + }, continuation) => + continuation({ + '#matches': list.map(ref => findFunction(ref, data, {mode: 'quiet'})), + }), }, - mapContinuation: { - '#composition.into': into, + { + dependencies: ['#matches'], + compute: ({'#matches': matches}, continuation) => + (matches.every(match => match) + ? continuation.raise({'#continuation.into': matches}) + : continuation()), }, - steps: [ - exitWithoutDependency({ - dependency: '#composition.data', - value: [], - }), - - raiseWithoutDependency({ - dependency: '#composition.list', - raise: {'#composition.into': []}, - mode: 'empty', - }), - - { - dependencies: [ - '#composition.list', - '#composition.data', - '#composition.findFunction', - ], - - compute: ({ - '#composition.list': list, - '#composition.data': data, - '#composition.findFunction': findFunction, - }, continuation) => - continuation({ - '#matches': list.map(ref => findFunction(ref, data, {mode: 'quiet'})), - }), + { + dependencies: ['#matches', input('notFoundMode')], + compute({ + ['#matches']: matches, + [input('notFoundMode')]: notFoundMode, + }, continuation) { + switch (notFoundMode) { + case 'exit': + return continuation.exit([]); + + case 'filter': + matches = matches.filter(match => match); + return continuation.raise({'#continuation.into': matches}); + + case 'null': + matches = matches.map(match => match ?? null); + return continuation.raise({'#continuation.into': matches}); + + default: + throw new TypeError(`Expected notFoundMode to be exit, filter, or null`); + } }, - - { - dependencies: ['#matches'], - compute: ({'#matches': matches}, continuation) => - (matches.every(match => match) - ? continuation.raise({'#continuation.into': matches}) - : continuation()), - }, - - { - dependencies: ['#matches', '#composition.notFoundMode'], - compute({ - '#matches': matches, - '#composition.notFoundMode': notFoundMode, - }, continuation) { - switch (notFoundMode) { - case 'exit': - return continuation.exit([]); - - case 'filter': - matches = matches.filter(match => match); - return continuation.raise({'#continuation.into': matches}); - - case 'null': - matches = matches.map(match => match ?? null); - return continuation.raise({'#continuation.into': matches}); - - default: - throw new TypeError(`Expected notFoundMode to be exit, filter, or null`); - } - }, - }, - ], - }); -} + }, + ], +}); // Check out the info on reverseReferenceList! // This is its composable form. -export function withReverseReferenceList({ - data, - list: refListProperty, - into = '#reverseReferenceList', -}) { - return compositeFrom({ - annotation: `withReverseReferenceList`, - - mapDependencies: { - '#composition.data': data, - }, - - constantDependencies: { - '#composition.refListProperty': refListProperty, - }, - - mapContinuation: { - '#composition.into': into, +export const withReverseReferenceList = templateCompositeFrom({ + annotation: `withReverseReferenceList`, + + inputs: { + // todo: validate + data: input(), + + list: input({type: 'string'}), + }, + + outputs: { + into: '#reverseReferenceList', + }, + + steps: () => [ + exitWithoutDependency({ + dependency: '#composition.data', + value: [], + }), + + { + dependencies: [ + 'this', + '#composition.data', + '#composition.refListProperty', + ], + + compute: ({ + this: thisThing, + '#composition.data': data, + '#composition.refListProperty': refListProperty, + }, continuation) => + continuation({ + '#composition.into': + data.filter(thing => thing[refListProperty].includes(thisThing)), + }), }, - - steps: [ - exitWithoutDependency({ - dependency: '#composition.data', - value: [], - }), - - { - dependencies: [ - 'this', - '#composition.data', - '#composition.refListProperty', - ], - - compute: ({ - this: thisThing, - '#composition.data': data, - '#composition.refListProperty': refListProperty, - }, continuation) => - continuation({ - '#composition.into': - data.filter(thing => thing[refListProperty].includes(thisThing)), - }), - }, - ], - }); -} + ], +}); diff --git a/src/data/things/track.js b/src/data/things/track.js index a8d5902..ccfbc35 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -11,9 +11,11 @@ import { exposeDependency, exposeDependencyOrContinue, exposeUpdateValueOrContinue, + input, + raiseOutputWithoutDependency, + templateCompositeFrom, withPropertyFromObject, withResultOfAvailabilityCheck, - withUpdateValueAsDependency, } from '#composite'; import { @@ -21,6 +23,7 @@ import { isContributionList, isDate, isFileExtension, + oneOf, } from '#validators'; import CacheableObject from './cacheable-object.js'; @@ -139,8 +142,10 @@ export class Track extends Thing { artistContribs: [ inheritFromOriginalRelease({property: 'artistContribs'}), - withUpdateValueAsDependency(), - withResolvedContribs({from: '#updateValue', into: '#artistContribs'}), + withResolvedContribs({ + from: input.updateValue(), + }).outputs({into: '#artistContribs'}), + exposeDependencyOrContinue({dependency: '#artistContribs'}), withPropertyFromAlbum({property: 'artistContribs'}), @@ -161,8 +166,10 @@ export class Track extends Thing { coverArtistContribs: [ exitWithoutUniqueCoverArt(), - withUpdateValueAsDependency(), - withResolvedContribs({from: '#updateValue', into: '#coverArtistContribs'}), + withResolvedContribs({ + from: input.updateValue(), + }).outputs({into: '#coverArtistContribs'}), + exposeDependencyOrContinue({dependency: '#coverArtistContribs'}), withPropertyFromAlbum({property: 'trackCoverArtistContribs'}), @@ -302,6 +309,7 @@ export class Track extends Thing { } } +/* // Early exits with a value inherited from the original release, if // this track is a rerelease, and otherwise continues with no further // dependencies provided. If allowOverride is true, then the continuation @@ -327,77 +335,55 @@ function inheritFromOriginalRelease({ }, ]); } +*/ // Gets the track's album. This will early exit if albumData is missing. // By default, if there's no album whose list of tracks includes this track, // the output dependency will be null; set {notFoundMode: 'exit'} to early // exit instead. -function withAlbum({ - into = '#album', - notFoundMode = 'null', -} = {}) { - return compositeFrom(`withAlbum`, [ - withResultOfAvailabilityCheck({ - fromDependency: 'albumData', - mode: 'empty', - into: '#albumDataAvailability', +export const withAlbum = templateCompositeFrom({ + annotation: `Track.withAlbum`, + + inputs: { + notFoundMode: input({ + validate: oneOf('exit', 'null'), + defaultValue: 'null', }), + }, - { - dependencies: ['#albumDataAvailability'], - options: {notFoundMode}, - mapContinuation: {into}, + outputs: { + into: '#album', + }, - compute: ({ - '#albumDataAvailability': albumDataAvailability, - '#options': {notFoundMode}, - }, continuation) => - (albumDataAvailability - ? continuation() - : (notFoundMode === 'exit' - ? continuation.exit(null) - : continuation.raise({into: null}))), - }, + steps: () => [ + raiseOutputWithoutDependency({ + dependency: 'albumData', + mode: input.value('empty'), + output: input.value({into: null}), + }), { dependencies: ['this', 'albumData'], - compute: ({this: track, albumData}, continuation) => + compute: (continuation, {this: track, albumData}) => continuation({ '#album': albumData.find(album => album.tracks.includes(track)), }), }, - withResultOfAvailabilityCheck({ - fromDependency: '#album', - mode: 'null', - into: '#albumAvailability', + raiseOutputWithoutDependency({ + dependency: '#album', + output: input.value({into: null}), }), - { - dependencies: ['#albumAvailability'], - options: {notFoundMode}, - mapContinuation: {into}, - - compute: ({ - '#albumAvailability': albumAvailability, - '#options': {notFoundMode}, - }, continuation) => - (albumAvailability - ? continuation() - : (notFoundMode === 'exit' - ? continuation.exit(null) - : continuation.raise({into: null}))), - }, - { dependencies: ['#album'], - mapContinuation: {into}, - compute: ({'#album': album}, continuation) => + compute: (continuation, {'#album': album}) => continuation({into: album}), }, - ]); -} + ], +}); +/* // Gets a single property from this track's album, providing it as the same // property name prefixed with '#album.' (by default). If the track's album // isn't available, then by default, the property will be provided as null; @@ -571,3 +557,4 @@ function trackReverseReferenceList({ }, ]); } +*/ -- cgit 1.3.0-6-gf8a5