diff options
Diffstat (limited to 'src/data/composite/wiki-data')
-rw-r--r-- | src/data/composite/wiki-data/helpers/withDirectoryFromName.js (renamed from src/data/composite/wiki-data/withDirectoryFromName.js) | 3 | ||||
-rw-r--r-- | src/data/composite/wiki-data/helpers/withReverseList-template.js | 189 | ||||
-rw-r--r-- | src/data/composite/wiki-data/helpers/withSimpleDirectory.js | 52 | ||||
-rw-r--r-- | src/data/composite/wiki-data/index.js | 4 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withDirectory.js | 41 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withParsedCommentaryEntries.js | 84 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withRecontextualizedContributionList.js | 28 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withResolvedArtworkReferenceList.js | 125 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withResolvedContribs.js | 8 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withResolvedSeriesList.js | 131 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withReverseContributionList.js | 139 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withReverseReferenceList.js | 140 | ||||
-rw-r--r-- | src/data/composite/wiki-data/withReverseSingleReferenceList.js | 50 |
13 files changed, 702 insertions, 292 deletions
diff --git a/src/data/composite/wiki-data/withDirectoryFromName.js b/src/data/composite/wiki-data/helpers/withDirectoryFromName.js index 034464e4..f85dae16 100644 --- a/src/data/composite/wiki-data/withDirectoryFromName.js +++ b/src/data/composite/wiki-data/helpers/withDirectoryFromName.js @@ -1,4 +1,4 @@ -// Compute a directory from a name - by default the current thing's own name. +// Compute a directory from a name. import {input, templateCompositeFrom} from '#composite'; @@ -13,7 +13,6 @@ export default templateCompositeFrom({ inputs: { name: input({ validate: isName, - defaultDependency: 'name', acceptsNull: true, }), }, diff --git a/src/data/composite/wiki-data/helpers/withReverseList-template.js b/src/data/composite/wiki-data/helpers/withReverseList-template.js new file mode 100644 index 00000000..5ed0e32e --- /dev/null +++ b/src/data/composite/wiki-data/helpers/withReverseList-template.js @@ -0,0 +1,189 @@ +// Baseline implementation shared by or underlying reverse lists. +// +// This is a very rudimentary "these compositions have basically the same +// shape but slightly different guts midway through" kind of solution, +// and should use compositional subroutines instead, once those are ready. +// +// But, until then, this has the same effect of avoiding code duplication +// and clearly identifying differences. +// +// --- +// +// This implementation uses a global cache (via WeakMap) to attempt to speed +// up subsequent similar accesses. +// +// This has absolutely not been rigorously tested with altering properties of +// data objects in a wiki data array which is reused. If a new wiki data array +// is used, a fresh cache will always be created. +// + +import {input, templateCompositeFrom} from '#composite'; +import {sortByDate} from '#sort'; +import {stitchArrays} from '#sugar'; + +import {exitWithoutDependency, raiseOutputWithoutDependency} + from '#composite/control-flow'; +import {withFlattenedList, withMappedList} from '#composite/data'; + +import inputWikiData from '../inputWikiData.js'; + +export default function withReverseList_template({ + annotation, + + propertyInputName, + outputName, + + customCompositionSteps, +}) { + // Mapping of reference list property to WeakMap. + // Each WeakMap maps a wiki data array to another weak map, + // which in turn maps each referenced thing to an array of + // things referencing it. + const caches = new Map(); + + return templateCompositeFrom({ + annotation, + + inputs: { + data: inputWikiData({ + allowMixedTypes: false, + }), + + [propertyInputName]: input({ + type: 'string', + }), + }, + + outputs: [outputName], + + steps: () => [ + // Early exit with an empty array if the data list isn't available. + exitWithoutDependency({ + dependency: input('data'), + value: input.value([]), + }), + + // Raise an empty array (don't early exit) if the data list is empty. + raiseOutputWithoutDependency({ + dependency: input('data'), + mode: input.value('empty'), + output: input.value({[outputName]: []}), + }), + + // Check for an existing cache record which corresponds to this + // property input and input('data'). If it exists, query it for the + // current thing, and raise that; if it doesn't, create it, put it + // where it needs to be, and provide it so the next steps can fill + // it in. + { + dependencies: [input(propertyInputName), input('data'), input.myself()], + + compute: (continuation, { + [input(propertyInputName)]: property, + [input('data')]: data, + [input.myself()]: myself, + }) => { + if (!caches.has(property)) { + const cache = new WeakMap(); + caches.set(property, cache); + + const cacheRecord = new WeakMap(); + cache.set(data, cacheRecord); + + return continuation({ + ['#cacheRecord']: cacheRecord, + }); + } + + const cache = caches.get(property); + + if (!cache.has(data)) { + const cacheRecord = new WeakMap(); + cache.set(data, cacheRecord); + + return continuation({ + ['#cacheRecord']: cacheRecord, + }); + } + + return continuation.raiseOutput({ + [outputName]: + cache.get(data).get(myself) ?? [], + }); + }, + }, + + ...customCompositionSteps(), + + // Actually fill in the cache record. Since we're building up a *reverse* + // reference list, track connections in terms of the referenced thing. + // Although we gather all referenced things into a set and provide that + // for sorting purposes in the next step, we *don't* reprovide the cache + // record, because we're mutating that in-place - we'll just reuse its + // existing '#cacheRecord' dependency. + { + dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'], + compute: (continuation, { + ['#cacheRecord']: cacheRecord, + ['#referencingThings']: referencingThings, + ['#referencedThings']: referencedThings, + }) => { + const allReferencedThings = new Set(); + + stitchArrays({ + referencingThing: referencingThings, + referencedThings: referencedThings, + }).forEach(({referencingThing, referencedThings}) => { + for (const referencedThing of referencedThings) { + if (cacheRecord.has(referencedThing)) { + cacheRecord.get(referencedThing).push(referencingThing); + } else { + cacheRecord.set(referencedThing, [referencingThing]); + allReferencedThings.add(referencedThing); + } + } + }); + + return continuation({ + ['#allReferencedThings']: + allReferencedThings, + }); + }, + }, + + // Sort the entries in the cache records, too, just by date - the rest of + // sorting should be handled outside of this composition, either preceding + // (changing the 'data' input) or following (sorting the output). + // Again we're mutating in place, so no need to reprovide '#cacheRecord' + // here. + { + dependencies: ['#cacheRecord', '#allReferencedThings'], + compute: (continuation, { + ['#cacheRecord']: cacheRecord, + ['#allReferencedThings']: allReferencedThings, + }) => { + for (const referencedThing of allReferencedThings) { + if (cacheRecord.has(referencedThing)) { + const referencingThings = cacheRecord.get(referencedThing); + sortByDate(referencingThings); + } + } + + return continuation(); + }, + }, + + // Then just pluck out the current object from the now-filled cache record! + { + dependencies: ['#cacheRecord', input.myself()], + compute: (continuation, { + ['#cacheRecord']: cacheRecord, + [input.myself()]: myself, + }) => continuation({ + [outputName]: + cacheRecord.get(myself) ?? [], + }), + }, + ], + }); +} diff --git a/src/data/composite/wiki-data/helpers/withSimpleDirectory.js b/src/data/composite/wiki-data/helpers/withSimpleDirectory.js new file mode 100644 index 00000000..08ca3bfc --- /dev/null +++ b/src/data/composite/wiki-data/helpers/withSimpleDirectory.js @@ -0,0 +1,52 @@ +// A "simple" directory, based only on the already-provided directory, if +// available, or the provided name. + +import {input, templateCompositeFrom} from '#composite'; + +import {isDirectory, isName} from '#validators'; + +import {withResultOfAvailabilityCheck} from '#composite/control-flow'; + +import withDirectoryFromName from './withDirectoryFromName.js'; + +export default templateCompositeFrom({ + annotation: `withSimpleDirectory`, + + inputs: { + directory: input({ + validate: isDirectory, + defaultDependency: 'directory', + acceptsNull: true, + }), + + name: input({ + validate: isName, + acceptsNull: true, + }), + }, + + outputs: ['#directory'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: input('directory'), + }), + + { + dependencies: ['#availability', input('directory')], + compute: (continuation, { + ['#availability']: availability, + [input('directory')]: directory, + }) => + (availability + ? continuation.raiseOutput({ + ['#directory']: directory + }) + : continuation()), + }, + + withDirectoryFromName({ + name: input('name'), + }), + ], +}); diff --git a/src/data/composite/wiki-data/index.js b/src/data/composite/wiki-data/index.js index 5f17ca3a..f99a1a14 100644 --- a/src/data/composite/wiki-data/index.js +++ b/src/data/composite/wiki-data/index.js @@ -10,14 +10,16 @@ export {default as withClonedThings} from './withClonedThings.js'; export {default as withContributionListSums} from './withContributionListSums.js'; export {default as withCoverArtDate} from './withCoverArtDate.js'; export {default as withDirectory} from './withDirectory.js'; -export {default as withDirectoryFromName} from './withDirectoryFromName.js'; export {default as withParsedCommentaryEntries} from './withParsedCommentaryEntries.js'; export {default as withRecontextualizedContributionList} from './withRecontextualizedContributionList.js'; export {default as withRedatedContributionList} from './withRedatedContributionList.js'; +export {default as withResolvedArtworkReferenceList} from './withResolvedArtworkReferenceList.js'; export {default as withResolvedContribs} from './withResolvedContribs.js'; export {default as withResolvedReference} from './withResolvedReference.js'; export {default as withResolvedReferenceList} from './withResolvedReferenceList.js'; +export {default as withResolvedSeriesList} from './withResolvedSeriesList.js'; export {default as withReverseContributionList} from './withReverseContributionList.js'; export {default as withReverseReferenceList} from './withReverseReferenceList.js'; +export {default as withReverseSingleReferenceList} from './withReverseSingleReferenceList.js'; export {default as withThingsSortedAlphabetically} from './withThingsSortedAlphabetically.js'; export {default as withUniqueReferencingThing} from './withUniqueReferencingThing.js'; diff --git a/src/data/composite/wiki-data/withDirectory.js b/src/data/composite/wiki-data/withDirectory.js index b08b6153..f3bedf2e 100644 --- a/src/data/composite/wiki-data/withDirectory.js +++ b/src/data/composite/wiki-data/withDirectory.js @@ -7,9 +7,9 @@ import {input, templateCompositeFrom} from '#composite'; import {isDirectory, isName} from '#validators'; -import {withResultOfAvailabilityCheck} from '#composite/control-flow'; +import {raiseOutputWithoutDependency} from '#composite/control-flow'; -import withDirectoryFromName from './withDirectoryFromName.js'; +import withSimpleDirectory from './helpers/withSimpleDirectory.js'; export default templateCompositeFrom({ annotation: `withDirectory`, @@ -26,30 +26,37 @@ export default templateCompositeFrom({ defaultDependency: 'name', acceptsNull: true, }), + + suffix: input({ + validate: isDirectory, + defaultValue: null, + }), }, outputs: ['#directory'], steps: () => [ - withResultOfAvailabilityCheck({ - from: input('directory'), + withSimpleDirectory({ + directory: input('directory'), + name: input('name'), + }), + + raiseOutputWithoutDependency({ + dependency: '#directory', + output: input.value({['#directory']: null}), }), { - dependencies: ['#availability', input('directory')], + dependencies: ['#directory', input('suffix')], compute: (continuation, { - ['#availability']: availability, - [input('directory')]: directory, - }) => - (availability - ? continuation.raiseOutput({ - ['#directory']: directory - }) - : continuation()), + ['#directory']: directory, + [input('suffix')]: suffix, + }) => continuation({ + ['#directory']: + (suffix + ? directory + '-' + suffix + : directory), + }), }, - - withDirectoryFromName({ - name: input('name'), - }), ], }); diff --git a/src/data/composite/wiki-data/withParsedCommentaryEntries.js b/src/data/composite/wiki-data/withParsedCommentaryEntries.js index f0404a5d..144781a8 100644 --- a/src/data/composite/wiki-data/withParsedCommentaryEntries.js +++ b/src/data/composite/wiki-data/withParsedCommentaryEntries.js @@ -95,6 +95,10 @@ export default templateCompositeFrom({ 'artistDisplayText', 'annotation', 'date', + 'secondDate', + 'dateKind', + 'accessDate', + 'accessKind', ]), }), @@ -140,12 +144,78 @@ export default templateCompositeFrom({ }), { + dependencies: ['#entries.annotation'], + compute: (continuation, { + ['#entries.annotation']: annotation, + }) => continuation({ + ['#entries.webArchiveDate']: + annotation + .map(text => text?.match(/https?:\/\/web.archive.org\/web\/([0-9]{8,8})[0-9]*\//)) + .map(match => match?.[1]) + .map(dateText => + (dateText + ? dateText.slice(0, 4) + '/' + + dateText.slice(4, 6) + '/' + + dateText.slice(6, 8) + : null)), + }), + }, + + { dependencies: ['#entries.date'], compute: (continuation, { ['#entries.date']: date, }) => continuation({ ['#entries.date']: - date.map(date => date ? new Date(date) : null), + date + .map(date => date ? new Date(date) : null), + }), + }, + + { + dependencies: ['#entries.secondDate'], + compute: (continuation, { + ['#entries.secondDate']: secondDate, + }) => continuation({ + ['#entries.secondDate']: + secondDate + .map(date => date ? new Date(date) : null), + }), + }, + + fillMissingListItems({ + list: '#entries.dateKind', + fill: input.value(null), + }), + + { + dependencies: ['#entries.accessDate', '#entries.webArchiveDate'], + compute: (continuation, { + ['#entries.accessDate']: accessDate, + ['#entries.webArchiveDate']: webArchiveDate, + }) => continuation({ + ['#entries.accessDate']: + stitchArrays({accessDate, webArchiveDate}) + .map(({accessDate, webArchiveDate}) => + accessDate ?? + webArchiveDate ?? + null) + .map(date => date ? new Date(date) : date), + }), + }, + + { + dependencies: ['#entries.accessKind', '#entries.webArchiveDate'], + compute: (continuation, { + ['#entries.accessKind']: accessKind, + ['#entries.webArchiveDate']: webArchiveDate, + }) => continuation({ + ['#entries.accessKind']: + stitchArrays({accessKind, webArchiveDate}) + .map(({accessKind, webArchiveDate}) => + accessKind ?? + (webArchiveDate && 'captured') ?? + null), }), }, @@ -155,6 +225,10 @@ export default templateCompositeFrom({ '#entries.artistDisplayText', '#entries.annotation', '#entries.date', + '#entries.secondDate', + '#entries.dateKind', + '#entries.accessDate', + '#entries.accessKind', '#entries.body', ], @@ -163,6 +237,10 @@ export default templateCompositeFrom({ ['#entries.artistDisplayText']: artistDisplayText, ['#entries.annotation']: annotation, ['#entries.date']: date, + ['#entries.secondDate']: secondDate, + ['#entries.dateKind']: dateKind, + ['#entries.accessDate']: accessDate, + ['#entries.accessKind']: accessKind, ['#entries.body']: body, }) => continuation({ ['#parsedCommentaryEntries']: @@ -171,6 +249,10 @@ export default templateCompositeFrom({ artistDisplayText, annotation, date, + secondDate, + dateKind, + accessDate, + accessKind, body, }), }), diff --git a/src/data/composite/wiki-data/withRecontextualizedContributionList.js b/src/data/composite/wiki-data/withRecontextualizedContributionList.js index 06c997b5..d2401eac 100644 --- a/src/data/composite/wiki-data/withRecontextualizedContributionList.js +++ b/src/data/composite/wiki-data/withRecontextualizedContributionList.js @@ -1,12 +1,14 @@ // Clones all the contributions in a list, with thing and thingProperty both // updated to match the current thing. Overwrites the provided dependency. -// Doesn't do anything if the provided dependency is null. +// Optionally updates artistProperty as well. Doesn't do anything if +// the provided dependency is null. // // See also: // - withRedatedContributionList // import {input, templateCompositeFrom} from '#composite'; +import {isStringNonEmpty} from '#validators'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; import {withClonedThings} from '#composite/wiki-data'; @@ -19,6 +21,11 @@ export default templateCompositeFrom({ type: 'array', acceptsNull: true, }), + + artistProperty: input({ + validate: isStringNonEmpty, + defaultValue: null, + }), }, outputs: ({ @@ -47,16 +54,25 @@ export default templateCompositeFrom({ }, { - dependencies: [input.myself(), input.thisProperty()], + dependencies: [ + input.myself(), + input.thisProperty(), + input('artistProperty'), + ], compute: (continuation, { [input.myself()]: myself, [input.thisProperty()]: thisProperty, + [input('artistProperty')]: artistProperty, }) => continuation({ - ['#assignment']: { - thing: myself, - thingProperty: thisProperty, - }, + ['#assignment']: + Object.assign( + {thing: myself}, + {thingProperty: thisProperty}, + + (artistProperty + ? {artistProperty} + : {})), }), }, diff --git a/src/data/composite/wiki-data/withResolvedArtworkReferenceList.js b/src/data/composite/wiki-data/withResolvedArtworkReferenceList.js new file mode 100644 index 00000000..e9c6a590 --- /dev/null +++ b/src/data/composite/wiki-data/withResolvedArtworkReferenceList.js @@ -0,0 +1,125 @@ +import {input, templateCompositeFrom} from '#composite'; +import {stitchArrays} from '#sugar'; +import {is, isString, optional, validateArrayItems, validateProperties} + from '#validators'; + +import {withFilteredList, withMappedList, withPropertiesFromList} + from '#composite/data'; + +import inputWikiData from './inputWikiData.js'; +import withResolvedReferenceList from './withResolvedReferenceList.js'; + +export default templateCompositeFrom({ + annotation: `withResolvedArtworkReferenceList`, + + inputs: { + list: input({ + validate: + validateArrayItems( + validateProperties({ + reference: isString, + annotation: optional(isString), + })), + + acceptsNull: true, + }), + + data: inputWikiData({allowMixedTypes: false}), + find: input({type: 'function'}), + + notFoundMode: input({ + validate: is('exit', 'filter', 'null'), + defaultValue: 'filter', + }), + }, + + steps: () => [ + withPropertiesFromList({ + list: input('list'), + properties: input.value([ + 'reference', + 'annotation', + ]), + }), + + withResolvedReferenceList({ + list: '#list.reference', + data: input('data'), + find: input('find'), + notFoundMode: input.value('null'), + }), + + { + dependencies: [ + '#resolvedReferenceList', + '#list.annotation', + ], + + compute: (continuation, { + ['#resolvedReferenceList']: thing, + ['#list.annotation']: annotation, + }) => continuation({ + ['#matches']: + stitchArrays({ + thing, + annotation, + }), + }), + }, + + { + dependencies: ['#matches'], + compute: (continuation, {'#matches': matches}) => + (matches.every(match => match) + ? continuation.raiseOutput({ + ['#resolvedArtworkReferenceList']: + matches, + }) + : continuation()), + }, + + { + dependencies: [input('notFoundMode')], + compute: (continuation, { + [input('notFoundMode')]: notFoundMode, + }) => + (notFoundMode === 'exit' + ? continuation.exit([]) + : continuation()), + }, + + { + dependencies: ['#matches', input('notFoundMode')], + compute: (continuation, { + ['#matches']: matches, + [input('notFoundMode')]: notFoundMode, + }) => + (notFoundMode === 'null' + ? continuation.raiseOutput({ + ['#resolvedArtworkReferenceList']: + matches, + }) + : continuation()), + }, + + withMappedList({ + list: '#resolvedReferenceList', + map: input.value(thing => thing !== null), + }), + + withFilteredList({ + list: '#matches', + filter: '#mappedList', + }), + + { + dependencies: ['#filteredList'], + compute: (continuation, { + ['#filteredList']: filteredList, + }) => continuation({ + ['#resolvedArtworkReferenceList']: + filteredList, + }), + }, + ], +}) diff --git a/src/data/composite/wiki-data/withResolvedContribs.js b/src/data/composite/wiki-data/withResolvedContribs.js index 23b91691..b5d7255b 100644 --- a/src/data/composite/wiki-data/withResolvedContribs.js +++ b/src/data/composite/wiki-data/withResolvedContribs.js @@ -36,6 +36,11 @@ export default templateCompositeFrom({ validate: isStringNonEmpty, defaultValue: null, }), + + artistProperty: input({ + validate: isStringNonEmpty, + defaultValue: null, + }), }, outputs: ['#resolvedContribs'], @@ -103,12 +108,14 @@ export default templateCompositeFrom({ dependencies: [ '#details', '#thingProperty', + input('artistProperty'), input.myself(), ], compute: (continuation, { ['#details']: details, ['#thingProperty']: thingProperty, + [input('artistProperty')]: artistProperty, [input.myself()]: myself, }) => continuation({ ['#contributions']: @@ -119,6 +126,7 @@ export default templateCompositeFrom({ ...details, thing: myself, thingProperty: thingProperty, + artistProperty: artistProperty, }); return contrib; diff --git a/src/data/composite/wiki-data/withResolvedSeriesList.js b/src/data/composite/wiki-data/withResolvedSeriesList.js new file mode 100644 index 00000000..4ac74cc3 --- /dev/null +++ b/src/data/composite/wiki-data/withResolvedSeriesList.js @@ -0,0 +1,131 @@ +import {input, templateCompositeFrom} from '#composite'; +import find from '#find'; +import {stitchArrays} from '#sugar'; +import {isSeriesList, validateThing} from '#validators'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import { + fillMissingListItems, + withFlattenedList, + withUnflattenedList, + withPropertiesFromList, +} from '#composite/data'; + +import withResolvedReferenceList from './withResolvedReferenceList.js'; + +export default templateCompositeFrom({ + annotation: `withResolvedSeriesList`, + + inputs: { + group: input({ + validate: validateThing({referenceType: 'group'}), + }), + + list: input({ + validate: isSeriesList, + acceptsNull: true, + }), + }, + + outputs: ['#resolvedSeriesList'], + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: input('list'), + mode: input.value('empty'), + output: input.value({ + ['#resolvedSeriesList']: [], + }), + }), + + withPropertiesFromList({ + list: input('list'), + prefix: input.value('#serieses'), + properties: input.value([ + 'name', + 'description', + 'albums', + + 'showAlbumArtists', + ]), + }), + + fillMissingListItems({ + list: '#serieses.albums', + fill: input.value([]), + }), + + withFlattenedList({ + list: '#serieses.albums', + }), + + withResolvedReferenceList({ + list: '#flattenedList', + data: 'albumData', + find: input.value(find.album), + notFoundMode: input.value('null'), + }), + + withUnflattenedList({ + list: '#resolvedReferenceList', + }).outputs({ + '#unflattenedList': '#serieses.albums', + }), + + fillMissingListItems({ + list: '#serieses.description', + fill: input.value(null), + }), + + fillMissingListItems({ + list: '#serieses.showAlbumArtists', + fill: input.value(null), + }), + + { + dependencies: [ + '#serieses.name', + '#serieses.description', + '#serieses.albums', + + '#serieses.showAlbumArtists', + ], + + compute: (continuation, { + ['#serieses.name']: name, + ['#serieses.description']: description, + ['#serieses.albums']: albums, + + ['#serieses.showAlbumArtists']: showAlbumArtists, + }) => continuation({ + ['#seriesProperties']: + stitchArrays({ + name, + description, + albums, + + showAlbumArtists, + }).map(properties => ({ + ...properties, + group: input + })) + }), + }, + + { + dependencies: ['#seriesProperties', input('group')], + compute: (continuation, { + ['#seriesProperties']: seriesProperties, + [input('group')]: group, + }) => continuation({ + ['#resolvedSeriesList']: + seriesProperties + .map(properties => ({ + ...properties, + group, + })), + }), + }, + ], +}); diff --git a/src/data/composite/wiki-data/withReverseContributionList.js b/src/data/composite/wiki-data/withReverseContributionList.js index 63e712bb..dcf33c39 100644 --- a/src/data/composite/wiki-data/withReverseContributionList.js +++ b/src/data/composite/wiki-data/withReverseContributionList.js @@ -1,100 +1,18 @@ // Analogous implementation for withReverseReferenceList, for contributions. -// This is mostly duplicate code and both should be ported to the same -// underlying data form later on. -// -// This implementation uses a global cache (via WeakMap) to attempt to speed -// up subsequent similar accesses. -// -// This has absolutely not been rigorously tested with altering properties of -// data objects in a wiki data array which is reused. If a new wiki data array -// is used, a fresh cache will always be created. -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; +import withReverseList_template from './helpers/withReverseList-template.js'; -import {exitWithoutDependency, raiseOutputWithoutDependency} - from '#composite/control-flow'; -import {withFlattenedList, withMappedList} from '#composite/data'; - -import inputWikiData from './inputWikiData.js'; +import {input} from '#composite'; -// Mapping of reference list property to WeakMap. -// Each WeakMap maps a wiki data array to another weak map, -// which in turn maps each referenced thing to an array of -// things referencing it. -const caches = new Map(); +import {withFlattenedList, withMappedList} from '#composite/data'; -export default templateCompositeFrom({ +export default withReverseList_template({ annotation: `withReverseContributionList`, - inputs: { - data: inputWikiData({allowMixedTypes: false}), - list: input({type: 'string'}), - }, - - outputs: ['#reverseContributionList'], - - steps: () => [ - // Common behavior -- - - // Early exit with an empty array if the data list isn't available. - exitWithoutDependency({ - dependency: input('data'), - value: input.value([]), - }), - - // Raise an empty array (don't early exit) if the data list is empty. - raiseOutputWithoutDependency({ - dependency: input('data'), - mode: input.value('empty'), - output: input.value({'#reverseContributionList': []}), - }), - - // Check for an existing cache record which corresponds to this - // input('list') and input('data'). If it exists, query it for the - // current thing, and raise that; if it doesn't, create it, put it - // where it needs to be, and provide it so the next steps can fill - // it in. - { - dependencies: [input('list'), input('data'), input.myself()], - - compute: (continuation, { - [input('list')]: list, - [input('data')]: data, - [input.myself()]: myself, - }) => { - if (!caches.has(list)) { - const cache = new WeakMap(); - caches.set(list, cache); - - const cacheRecord = new WeakMap(); - cache.set(data, cacheRecord); - - return continuation({ - ['#cacheRecord']: cacheRecord, - }); - } - - const cache = caches.get(list); - - if (!cache.has(data)) { - const cacheRecord = new WeakMap(); - cache.set(data, cacheRecord); - - return continuation({ - ['#cacheRecord']: cacheRecord, - }); - } - - return continuation.raiseOutput({ - ['#reverseContributionList']: - cache.get(data).get(myself) ?? [], - }); - }, - }, - - // Unique behavior for contribution lists -- + propertyInputName: 'list', + outputName: '#reverseContributionList', + customCompositionSteps: () => [ { dependencies: [input('list')], compute: (continuation, { @@ -124,48 +42,5 @@ export default templateCompositeFrom({ }).outputs({ '#mappedList': '#referencedThings', }), - - // Common behavior -- - - // Actually fill in the cache record. Since we're building up a *reverse* - // reference list, track connections in terms of the referenced thing. - // No newly-provided dependencies here since we're mutating the cache - // record, which is properly in store and will probably be reused in the - // future (and certainly in the next step). - { - dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'], - compute: (continuation, { - ['#cacheRecord']: cacheRecord, - ['#referencingThings']: referencingThings, - ['#referencedThings']: referencedThings, - }) => { - stitchArrays({ - referencingThing: referencingThings, - referencedThings: referencedThings, - }).forEach(({referencingThing, referencedThings}) => { - for (const referencedThing of referencedThings) { - if (cacheRecord.has(referencedThing)) { - cacheRecord.get(referencedThing).push(referencingThing); - } else { - cacheRecord.set(referencedThing, [referencingThing]); - } - } - }); - - return continuation(); - }, - }, - - // Then just pluck out the current object from the now-filled cache record! - { - dependencies: ['#cacheRecord', input.myself()], - compute: (continuation, { - ['#cacheRecord']: cacheRecord, - [input.myself()]: myself, - }) => continuation({ - ['#reverseContributionList']: - cacheRecord.get(myself) ?? [], - }), - }, ], }); diff --git a/src/data/composite/wiki-data/withReverseReferenceList.js b/src/data/composite/wiki-data/withReverseReferenceList.js index 1f8c082f..70d9a58d 100644 --- a/src/data/composite/wiki-data/withReverseReferenceList.js +++ b/src/data/composite/wiki-data/withReverseReferenceList.js @@ -1,102 +1,19 @@ // Check out the info on reverseReferenceList! // This is its composable form. -// -// This implementation uses a global cache (via WeakMap) to attempt to speed -// up subsequent similar accesses. -// -// This has absolutely not been rigorously tested with altering properties of -// data objects in a wiki data array which is reused. If a new wiki data array -// is used, a fresh cache will always be created. -// -// Note that this implementation is mirrored in withReverseContributionList, -// so any changes should be reflected there (until these are combined). -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; +import withReverseList_template from './helpers/withReverseList-template.js'; -import {exitWithoutDependency, raiseOutputWithoutDependency} - from '#composite/control-flow'; -import {withMappedList} from '#composite/data'; - -import inputWikiData from './inputWikiData.js'; +import {input} from '#composite'; -// Mapping of reference list property to WeakMap. -// Each WeakMap maps a wiki data array to another weak map, -// which in turn maps each referenced thing to an array of -// things referencing it. -const caches = new Map(); +import {withMappedList} from '#composite/data'; -export default templateCompositeFrom({ +export default withReverseList_template({ annotation: `withReverseReferenceList`, - inputs: { - data: inputWikiData({allowMixedTypes: false}), - list: input({type: 'string'}), - }, - - outputs: ['#reverseReferenceList'], - - steps: () => [ - // Common behavior -- - - // Early exit with an empty array if the data list isn't available. - exitWithoutDependency({ - dependency: input('data'), - value: input.value([]), - }), - - // Raise an empty array (don't early exit) if the data list is empty. - raiseOutputWithoutDependency({ - dependency: input('data'), - mode: input.value('empty'), - output: input.value({'#reverseReferenceList': []}), - }), - - // Check for an existing cache record which corresponds to this - // input('list') and input('data'). If it exists, query it for the - // current thing, and raise that; if it doesn't, create it, put it - // where it needs to be, and provide it so the next steps can fill - // it in. - { - dependencies: [input('list'), input('data'), input.myself()], - - compute: (continuation, { - [input('list')]: list, - [input('data')]: data, - [input.myself()]: myself, - }) => { - if (!caches.has(list)) { - const cache = new WeakMap(); - caches.set(list, cache); - - const cacheRecord = new WeakMap(); - cache.set(data, cacheRecord); - - return continuation({ - ['#cacheRecord']: cacheRecord, - }); - } - - const cache = caches.get(list); - - if (!cache.has(data)) { - const cacheRecord = new WeakMap(); - cache.set(data, cacheRecord); - - return continuation({ - ['#cacheRecord']: cacheRecord, - }); - } - - return continuation.raiseOutput({ - ['#reverseReferenceList']: - cache.get(data).get(myself) ?? [], - }); - }, - }, - - // Unique behavior for reference lists -- + propertyInputName: 'list', + outputName: '#reverseReferenceList', + customCompositionSteps: () => [ { dependencies: [input('list')], compute: (continuation, { @@ -123,48 +40,5 @@ export default templateCompositeFrom({ data, }), }, - - // Common behavior -- - - // Actually fill in the cache record. Since we're building up a *reverse* - // reference list, track connections in terms of the referenced thing. - // No newly-provided dependencies here since we're mutating the cache - // record, which is properly in store and will probably be reused in the - // future (and certainly in the next step). - { - dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'], - compute: (continuation, { - ['#cacheRecord']: cacheRecord, - ['#referencingThings']: referencingThings, - ['#referencedThings']: referencedThings, - }) => { - stitchArrays({ - referencingThing: referencingThings, - referencedThings: referencedThings, - }).forEach(({referencingThing, referencedThings}) => { - for (const referencedThing of referencedThings) { - if (cacheRecord.has(referencedThing)) { - cacheRecord.get(referencedThing).push(referencingThing); - } else { - cacheRecord.set(referencedThing, [referencingThing]); - } - } - }); - - return continuation(); - }, - }, - - // Then just pluck out the current object from the now-filled cache record! - { - dependencies: ['#cacheRecord', input.myself()], - compute: (continuation, { - ['#cacheRecord']: cacheRecord, - [input.myself()]: myself, - }) => continuation({ - ['#reverseReferenceList']: - cacheRecord.get(myself) ?? [], - }), - }, ], }); diff --git a/src/data/composite/wiki-data/withReverseSingleReferenceList.js b/src/data/composite/wiki-data/withReverseSingleReferenceList.js new file mode 100644 index 00000000..dd97dc66 --- /dev/null +++ b/src/data/composite/wiki-data/withReverseSingleReferenceList.js @@ -0,0 +1,50 @@ +// Like withReverseReferenceList, but for finding all things which reference +// the current thing by a property that contains a single reference, rather +// than within a reference list. + +import withReverseList_template from './helpers/withReverseList-template.js'; + +import {input} from '#composite'; + +import {withMappedList} from '#composite/data'; + +export default withReverseList_template({ + annotation: `withReverseSingleReferenceList`, + + propertyInputName: 'ref', + outputName: '#reverseSingleReferenceList', + + customCompositionSteps: () => [ + { + dependencies: [input('data')], + compute: (continuation, { + [input('data')]: data, + }) => continuation({ + ['#referencingThings']: + data, + }), + }, + + // This map wraps each referenced thing in a single-item array. + // Each referencing thing references exactly one thing, if any. + { + dependencies: [input('ref')], + compute: (continuation, { + [input('ref')]: ref, + }) => continuation({ + ['#singleReferenceMap']: + thing => + (thing[ref] + ? [thing[ref]] + : []), + }), + }, + + withMappedList({ + list: '#referencingThings', + map: '#singleReferenceMap', + }).outputs({ + '#mappedList': '#referencedThings', + }), + ], +}); |