« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/data')
-rw-r--r--src/data/composite/wiki-data/constituteFrom.js4
-rw-r--r--src/data/composite/wiki-data/constituteOrContinue.js4
-rw-r--r--src/data/things/art-tag.js12
-rw-r--r--src/data/things/artist.js39
-rw-r--r--src/data/things/artwork.js157
-rw-r--r--src/data/things/content.js102
-rw-r--r--src/data/things/contribution.js143
-rw-r--r--src/data/things/flash.js51
-rw-r--r--src/data/things/group.js28
-rw-r--r--src/data/things/homepage-layout.js58
-rw-r--r--src/data/things/language.js22
-rw-r--r--src/data/things/news-entry.js8
-rw-r--r--src/data/things/sorting-rule.js28
-rw-r--r--src/data/things/static-page.js8
-rw-r--r--src/data/things/wiki-info.js8
15 files changed, 157 insertions, 515 deletions
diff --git a/src/data/composite/wiki-data/constituteFrom.js b/src/data/composite/wiki-data/constituteFrom.js
index 12c36c78..b919d5cd 100644
--- a/src/data/composite/wiki-data/constituteFrom.js
+++ b/src/data/composite/wiki-data/constituteFrom.js
@@ -8,8 +8,8 @@ export default templateCompositeFrom({
   annotation: `constituteFrom`,
 
   inputs: {
+    object: input({type: 'object', acceptsNull: true}),
     property: input({type: 'string', acceptsNull: true}),
-    from: input({type: 'object', acceptsNull: true}),
     else: input({defaultValue: null}),
     mode: inputAvailabilityCheckMode(),
   },
@@ -18,8 +18,8 @@ export default templateCompositeFrom({
 
   steps: () => [
     constituteOrContinue({
+      object: input('object'),
       property: input('property'),
-      from: input('from'),
       mode: input('mode'),
     }),
 
diff --git a/src/data/composite/wiki-data/constituteOrContinue.js b/src/data/composite/wiki-data/constituteOrContinue.js
index 3527976e..92b941ba 100644
--- a/src/data/composite/wiki-data/constituteOrContinue.js
+++ b/src/data/composite/wiki-data/constituteOrContinue.js
@@ -12,8 +12,8 @@ export default templateCompositeFrom({
   annotation: `constituteFrom`,
 
   inputs: {
+    object: input({type: 'object', acceptsNull: true}),
     property: input({type: 'string', acceptsNull: true}),
-    from: input({type: 'object', acceptsNull: true}),
     mode: inputAvailabilityCheckMode(),
   },
 
@@ -23,8 +23,8 @@ export default templateCompositeFrom({
     }),
 
     withPropertyFromObject({
+      object: input('object'),
       property: input('property'),
-      object: input('from'),
     }),
 
     exposeDependencyOrContinue({
diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js
index 0ae77434..9c2740ed 100644
--- a/src/data/things/art-tag.js
+++ b/src/data/things/art-tag.js
@@ -4,7 +4,7 @@ export const ART_TAG_DATA_FILE = 'tags.yaml';
 import {readFile} from 'node:fs/promises';
 import * as path from 'node:path';
 
-import {input} from '#composite';
+import {input, V} from '#composite';
 import {traverse} from '#node-utils';
 import {sortAlphabetically} from '#sort';
 import Thing from '#thing';
@@ -85,15 +85,11 @@ export class ArtTag extends Thing {
 
     // Expose only
 
-    isArtTag: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isArtTag: exposeConstant(V(true)),
 
     descriptionShort: [
-      exitWithoutDependency({
-        dependency: 'description',
+      exitWithoutDependency('description', {
+        value: input.value(null),
         mode: input.value('falsy'),
       }),
 
diff --git a/src/data/things/artist.js b/src/data/things/artist.js
index 41d8504c..ce68d42d 100644
--- a/src/data/things/artist.js
+++ b/src/data/things/artist.js
@@ -4,7 +4,7 @@ import {inspect} from 'node:util';
 
 import CacheableObject from '#cacheable-object';
 import {colors} from '#cli';
-import {input} from '#composite';
+import {input, V} from '#composite';
 import Thing from '#thing';
 import {parseArtistAliases, parseArtwork} from '#yaml';
 
@@ -56,8 +56,7 @@ export class Artist extends Thing {
     avatarFileExtension: fileExtension('jpg'),
 
     avatarArtwork: [
-      exitWithoutDependency({
-        dependency: 'hasAvatar',
+      exitWithoutDependency('hasAvatar', {
         value: input.value(null),
         mode: input.value('falsy'),
       }),
@@ -83,11 +82,7 @@ export class Artist extends Thing {
 
     // Expose only
 
-    isArtist: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isArtist: exposeConstant(V(true)),
 
     trackArtistContributions: reverseReferenceList({
       reverse: soupyReverse.input('trackArtistContributionsBy'),
@@ -202,30 +197,14 @@ export class Artist extends Thing {
     ],
 
     totalDuration: [
-      withPropertyFromList({
-        list: 'musicContributions',
-        property: input.value('thing'),
-      }),
+      withPropertyFromList('musicContributions', V('thing')),
+      withPropertyFromList('#musicContributions.thing', V('isMainRelease')),
 
-      withPropertyFromList({
-        list: '#musicContributions.thing',
-        property: input.value('isMainRelease'),
-      }),
+      withFilteredList('musicContributions', '#musicContributions.thing.isMainRelease')
+        .outputs({'#filteredList': '#mainReleaseContributions'}),
 
-      withFilteredList({
-        list: 'musicContributions',
-        filter: '#musicContributions.thing.isMainRelease',
-      }).outputs({
-        '#filteredList': '#mainReleaseContributions',
-      }),
-
-      withContributionListSums({
-        list: '#mainReleaseContributions',
-      }),
-
-      exposeDependency({
-        dependency: '#contributionListDuration',
-      }),
+      withContributionListSums('#mainReleaseContributions'),
+      exposeDependency('#contributionListDuration'),
     ],
   });
 
diff --git a/src/data/things/artwork.js b/src/data/things/artwork.js
index 4aedd256..579d2299 100644
--- a/src/data/things/artwork.js
+++ b/src/data/things/artwork.js
@@ -1,7 +1,7 @@
 import {inspect} from 'node:util';
 
 import {colors} from '#cli';
-import {input} from '#composite';
+import {input, V} from '#composite';
 import find from '#find';
 import Thing from '#thing';
 
@@ -95,10 +95,7 @@ export class Artwork extends Thing {
         validate: input.value(isDate),
       }),
 
-      constituteFrom({
-        property: 'dateFromThingProperty',
-        from: 'thing',
-      }),
+      constituteFrom('thing', 'dateFromThingProperty'),
     ],
 
     fileExtensionFromThingProperty: simpleString(),
@@ -108,9 +105,7 @@ export class Artwork extends Thing {
         validate: input.value(isFileExtension),
       }),
 
-      constituteFrom({
-        property: 'fileExtensionFromThingProperty',
-        from: 'thing',
+      constituteFrom('thing', 'fileExtensionFromThingProperty', {
         else: input.value('jpg'),
       }),
     ],
@@ -122,10 +117,7 @@ export class Artwork extends Thing {
         validate: input.value(isDimensions),
       }),
 
-      constituteFrom({
-        property: 'dimensionsFromThingProperty',
-        from: 'thing',
-      }),
+      constituteFrom('thing', 'dimensionsFromThingProperty'),
     ],
 
     attachAbove: flag(false),
@@ -141,38 +133,18 @@ export class Artwork extends Thing {
         artistProperty: 'artistContribsArtistProperty',
       }),
 
-      exposeDependencyOrContinue({
-        dependency: '#resolvedContribs',
-        mode: input.value('empty'),
-      }),
+      exposeDependencyOrContinue('#resolvedContribs', V('empty')),
 
-      withPropertyFromObject({
-        object: 'attachedArtwork',
-        property: input.value('artistContribs'),
-      }),
+      withPropertyFromObject('attachedArtwork', V('artistContribs')),
 
-      withRecontextualizedContributionList({
-        list: '#attachedArtwork.artistContribs',
-      }),
+      withRecontextualizedContributionList('#attachedArtwork.artistContribs'),
+      exposeDependencyOrContinue('#attachedArtwork.artistContribs'),
 
-      exposeDependencyOrContinue({
-        dependency: '#attachedArtwork.artistContribs',
-      }),
+      withPropertyFromObject('thing', 'artistContribsFromThingProperty')
+        .outputs({'#value': '#artistContribsFromThing'}),
 
-      withPropertyFromObject({
-        object: 'thing',
-        property: 'artistContribsFromThingProperty',
-      }).outputs({
-        '#value': '#artistContribsFromThing',
-      }),
-
-      withRecontextualizedContributionList({
-        list: '#artistContribsFromThing',
-      }),
-
-      exposeDependency({
-        dependency: '#artistContribsFromThing',
-      }),
+      withRecontextualizedContributionList('#artistContribsFromThing'),
+      exposeDependency('#artistContribsFromThing'),
     ],
 
     style: simpleString(),
@@ -188,22 +160,11 @@ export class Artwork extends Thing {
         find: soupyFind.input('artTag'),
       }),
 
-      exposeDependencyOrContinue({
-        dependency: '#resolvedReferenceList',
-        mode: input.value('empty'),
-      }),
+      exposeDependencyOrContinue('#resolvedReferenceList', V('empty')),
 
-      constituteOrContinue({
-        property: input.value('artTags'),
-        from: 'attachedArtwork',
-        mode: input.value('empty'),
-      }),
+      constituteOrContinue('attachedArtwork', V('artTags'), V('empty')),
 
-      constituteFrom({
-        property: 'artTagsFromThingProperty',
-        from: 'thing',
-        else: input.value([]),
-      }),
+      constituteFrom('thing', 'artTagsFromThingProperty', V([])),
     ],
 
     referencedArtworksFromThingProperty: simpleString(),
@@ -239,14 +200,9 @@ export class Artwork extends Thing {
         thing: input.value('artwork'),
       }),
 
-      exposeDependencyOrContinue({
-        dependency: '#resolvedAnnotatedReferenceList',
-        mode: input.value('empty'),
-      }),
+      exposeDependencyOrContinue('#resolvedAnnotatedReferenceList', V('empty')),
 
-      constituteFrom({
-        property: 'referencedArtworksFromThingProperty',
-        from: 'thing',
+      constituteFrom('thing', 'referencedArtworksFromThingProperty', {
         else: input.value([]),
       }),
     ],
@@ -263,11 +219,7 @@ export class Artwork extends Thing {
 
     // Expose only
 
-    isArtwork: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isArtwork: exposeConstant(V(true)),
 
     referencedByArtworks: reverseReferenceList({
       reverse: soupyReverse.input('artworksWhichReference'),
@@ -275,11 +227,7 @@ export class Artwork extends Thing {
 
     isMainArtwork: [
       withContainingArtworkList(),
-
-      exitWithoutDependency({
-        dependency: '#containingArtworkList',
-        value: input.value(null),
-      }),
+      exitWithoutDependency('#containingArtworkList'),
 
       {
         dependencies: [input.myself(), '#containingArtworkList'],
@@ -293,11 +241,7 @@ export class Artwork extends Thing {
 
     mainArtwork: [
       withContainingArtworkList(),
-
-      exitWithoutDependency({
-        dependency: '#containingArtworkList',
-        value: input.value(null),
-      }),
+      exitWithoutDependency('#containingArtworkList'),
 
       {
         dependencies: ['#containingArtworkList'],
@@ -307,23 +251,17 @@ export class Artwork extends Thing {
     ],
 
     attachedArtwork: [
-      exitWithoutDependency({
-        dependency: 'attachAbove',
+      exitWithoutDependency('attachAbove', {
+        value: input.value(null),
         mode: input.value('falsy'),
       }),
 
       withContainingArtworkList(),
 
-      withPropertyFromList({
-        list: '#containingArtworkList',
-        property: input.value('attachAbove'),
-      }),
+      withPropertyFromList('#containingArtworkList', V('attachAbove')),
 
-      flipFilter({
-        filter: '#containingArtworkList.attachAbove',
-      }).outputs({
-        '#containingArtworkList.attachAbove': '#filterNotAttached',
-      }),
+      flipFilter('#containingArtworkList.attachAbove')
+        .outputs({'#containingArtworkList.attachAbove': '#filterNotAttached'}),
 
       withNearbyItemFromList({
         list: '#containingArtworkList',
@@ -332,9 +270,7 @@ export class Artwork extends Thing {
         filter: '#filterNotAttached',
       }),
 
-      exposeDependency({
-        dependency: '#nearbyItem',
-      }),
+      exposeDependency('#nearbyItem'),
     ],
 
     attachingArtworks: reverseReferenceList({
@@ -342,46 +278,23 @@ export class Artwork extends Thing {
     }),
 
     groups: [
-      withPropertyFromObject({
-        object: 'thing',
-        property: input.value('groups'),
-      }),
-
-      exposeDependencyOrContinue({
-        dependency: '#thing.groups',
-      }),
+      withPropertyFromObject('thing', V('groups')),
+      exposeDependencyOrContinue('#thing.groups'),
 
-      exposeConstant({
-        value: input.value([]),
-      }),
+      exposeConstant(V([])),
     ],
 
     contentWarningArtTags: [
-      withPropertyFromList({
-        list: 'artTags',
-        property: input.value('isContentWarning'),
-      }),
-
-      withFilteredList({
-        list: 'artTags',
-        filter: '#artTags.isContentWarning',
-      }),
-
-      exposeDependency({
-        dependency: '#filteredList',
-      }),
+      withPropertyFromList('artTags', V('isContentWarning')),
+      withFilteredList('artTags', '#artTags.isContentWarning'),
+      exposeDependency('#filteredList'),
     ],
 
     contentWarnings: [
-      withPropertyFromList({
-        list: 'contentWarningArtTags',
-        property: input.value('name'),
-      }),
-
-      exposeDependency({
-        dependency: '#contentWarningArtTags.name',
-      }),
+      withPropertyFromList('contentWarningArtTags', V('name')),
+      exposeDependency('#contentWarningArtTags.name'),
     ],
+
   });
 
   static [Thing.yamlDocumentSpec] = {
diff --git a/src/data/things/content.js b/src/data/things/content.js
index 8a255ac3..64d03e69 100644
--- a/src/data/things/content.js
+++ b/src/data/things/content.js
@@ -1,4 +1,4 @@
-import {input} from '#composite';
+import {input, V} from '#composite';
 import {transposeArrays} from '#sugar';
 import Thing from '#thing';
 import {is, isDate, validateReferenceList} from '#validators';
@@ -39,19 +39,14 @@ export class ContentEntry extends Thing {
         }),
       }),
 
-      exitWithoutDependency({
-        dependency: '#artistReferences',
-        value: input.value([]),
-      }),
+      exitWithoutDependency('#artistReferences', V([])),
 
       withResolvedReferenceList({
         list: '#artistReferences',
         find: soupyFind.input('artist'),
       }),
 
-      exposeDependency({
-        dependency: '#resolvedReferenceList',
-      }),
+      exposeDependency('#resolvedReferenceList'),
     ],
 
     artistText: contentString(),
@@ -71,9 +66,7 @@ export class ContentEntry extends Thing {
     },
 
     accessKind: [
-      exitWithoutDependency({
-        dependency: '_accessDate',
-      }),
+      exitWithoutDependency('_accessDate'),
 
       exposeUpdateValueOrContinue({
         validate: input.value(
@@ -85,9 +78,7 @@ export class ContentEntry extends Thing {
 
       withWebArchiveDate(),
 
-      withResultOfAvailabilityCheck({
-        from: '#webArchiveDate',
-      }),
+      withResultOfAvailabilityCheck({from: '#webArchiveDate'}),
 
       {
         dependencies: ['#availability'],
@@ -97,13 +88,10 @@ export class ContentEntry extends Thing {
             : continuation()),
       },
 
-      exposeConstant({
-        value: input.value('accessed'),
-      }),
+      exposeConstant(V('accessed')),
     ],
 
     date: simpleDate(),
-
     secondDate: simpleDate(),
 
     accessDate: [
@@ -117,9 +105,7 @@ export class ContentEntry extends Thing {
         dependency: '#webArchiveDate',
       }),
 
-      exposeConstant({
-        value: input.value(null),
-      }),
+      exposeConstant(V(null)),
     ],
 
     body: contentString(),
@@ -130,11 +116,7 @@ export class ContentEntry extends Thing {
 
     // Expose only
 
-    isContentEntry: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isContentEntry: exposeConstant(V(true)),
 
     annotationParts: [
       withAnnotationPartNodeLists(),
@@ -152,19 +134,11 @@ export class ContentEntry extends Thing {
         }),
       },
 
-      withPropertyFromList({
-        list: '#firstNodes',
-        property: input.value('i'),
-      }).outputs({
-        '#firstNodes.i': '#startIndices',
-      }),
+      withPropertyFromList('#firstNodes', V('i'))
+        .outputs({'#firstNodes.i': '#startIndices'}),
 
-      withPropertyFromList({
-        list: '#lastNodes',
-        property: input.value('iEnd'),
-      }).outputs({
-        '#lastNodes.iEnd': '#endIndices',
-      }),
+      withPropertyFromList('#lastNodes', V('iEnd'))
+        .outputs({'#lastNodes.iEnd': '#endIndices'}),
 
       {
         dependencies: [
@@ -200,9 +174,7 @@ export class ContentEntry extends Thing {
         }),
       },
 
-      exitWithoutDependency({
-        dependency: '#firstPartWithExternalLink',
-      }),
+      exitWithoutDependency('#firstPartWithExternalLink'),
 
       {
         dependencies: ['annotation', '#firstPartWithExternalLink'],
@@ -232,10 +204,7 @@ export class ContentEntry extends Thing {
         }),
       },
 
-      exitWithoutDependency({
-        dependency: '#firstPartWithExternalLink',
-        value: input.value([]),
-      }),
+      exitWithoutDependency('#firstPartWithExternalLink', V([])),
 
       withMappedList({
         list: '#firstPartWithExternalLink',
@@ -254,9 +223,7 @@ export class ContentEntry extends Thing {
         map: input.value(node => node.data.href),
       }),
 
-      exposeDependency({
-        dependency: '#mappedList',
-      }),
+      exposeDependency('#mappedList'),
     ],
   });
 
@@ -307,31 +274,14 @@ export class LyricsEntry extends ContentEntry {
 
     // Expose only
 
-    isLyricsEntry: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
-
-    isWikiLyrics: hasAnnotationPart({
-      part: input.value('wiki lyrics'),
-    }),
+    isLyricsEntry: exposeConstant(V(true)),
 
-    helpNeeded: hasAnnotationPart({
-      part: input.value('help needed'),
-    }),
+    isWikiLyrics: hasAnnotationPart(V('wiki lyrics')),
+    helpNeeded: hasAnnotationPart(V('help needed')),
 
     hasSquareBracketAnnotations: [
-      exitWithoutDependency({
-        dependency: 'isWikiLyrics',
-        mode: input.value('falsy'),
-        value: input.value(false),
-      }),
-
-      exitWithoutDependency({
-        dependency: 'body',
-        value: input.value(false),
-      }),
+      exitWithoutDependency('isWikiLyrics', V(false), V('falsy')),
+      exitWithoutDependency('body', V(false)),
 
       {
         dependencies: ['body'],
@@ -354,11 +304,7 @@ export class CreditingSourcesEntry extends ContentEntry {
   static [Thing.getPropertyDescriptors] = () => ({
     // Expose only
 
-    isCreditingSourcesEntry: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isCreditingSourcesEntry: exposeConstant(V(true)),
   });
 }
 
@@ -368,10 +314,6 @@ export class ReferencingSourcesEntry extends ContentEntry {
   static [Thing.getPropertyDescriptors] = () => ({
     // Expose only
 
-    isReferencingSourceEntry: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isReferencingSourceEntry: exposeConstant(V(true)),
   });
 }
diff --git a/src/data/things/contribution.js b/src/data/things/contribution.js
index 41b57b7b..1187f75d 100644
--- a/src/data/things/contribution.js
+++ b/src/data/things/contribution.js
@@ -2,11 +2,10 @@ import {inspect} from 'node:util';
 
 import CacheableObject from '#cacheable-object';
 import {colors} from '#cli';
-import {input} from '#composite';
+import {input, V} from '#composite';
 import {empty} from '#sugar';
 import Thing from '#thing';
-import {isBoolean, isStringNonEmpty, isThing, validateReference}
-  from '#validators';
+import {isBoolean, isStringNonEmpty, isThing} from '#validators';
 
 import {simpleDate, singleReference, soupyFind}
   from '#composite/wiki-properties';
@@ -82,9 +81,7 @@ export class Contribution extends Thing {
             : continuation()),
       },
 
-      exposeConstant({
-        value: input.value(true),
-      }),
+      exposeConstant(V(true)),
     ],
 
     countInDurationTotals: [
@@ -94,15 +91,10 @@ export class Contribution extends Thing {
         validate: input.value(isBoolean),
       }),
 
-      withPropertyFromObject({
-        object: 'thing',
-        property: input.value('duration'),
-      }),
-
-      exitWithoutDependency({
-        dependency: '#thing.duration',
-        mode: input.value('falsy'),
+      withPropertyFromObject('thing', V('duration')),
+      exitWithoutDependency('#thing.duration', {
         value: input.value(false),
+        mode: input.value('falsy'),
       }),
 
       {
@@ -118,9 +110,7 @@ export class Contribution extends Thing {
             : continuation()),
       },
 
-      exposeConstant({
-        value: input.value(true),
-      }),
+      exposeConstant(V(true)),
     ],
 
     // Update only
@@ -129,11 +119,7 @@ export class Contribution extends Thing {
 
     // Expose only
 
-    isContribution: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isContribution: exposeConstant(V(true)),
 
     context: [
       withContributionContext(),
@@ -155,32 +141,24 @@ export class Contribution extends Thing {
     ],
 
     matchingPresets: [
-      withPropertyFromObject({
-        object: 'thing',
+      withPropertyFromObject('thing', {
         property: input.value('wikiInfo'),
         internal: input.value(true),
       }),
 
-      exitWithoutDependency({
-        dependency: '#thing.wikiInfo',
-        value: input.value([]),
-      }),
+      exitWithoutDependency('#thing.wikiInfo', V([])),
 
-      withPropertyFromObject({
-        object: '#thing.wikiInfo',
-        property: input.value('contributionPresets'),
-      }).outputs({
-        '#thing.wikiInfo.contributionPresets': '#contributionPresets',
-      }),
+      withPropertyFromObject('#thing.wikiInfo', V('contributionPresets'))
+        .outputs({'#thing.wikiInfo.contributionPresets': '#contributionPresets'}),
 
-      exitWithoutDependency({
-        dependency: '#contributionPresets',
-        mode: input.value('empty'),
+      exitWithoutDependency('#contributionPresets', {
         value: input.value([]),
+        mode: input.value('empty'),
       }),
 
       withContributionContext(),
 
+      // TODO: implementing this with compositional filters would be fun
       {
         dependencies: [
           '#contributionPresets',
@@ -206,7 +184,6 @@ export class Contribution extends Thing {
                 preset.annotation === annotation),
         })
       },
-
     ],
 
     // All the contributions from the list which includes this contribution.
@@ -214,27 +191,13 @@ export class Contribution extends Thing {
     // artist, but also this very contribution. It doesn't mix contributions
     // exposed on different properties.
     associatedContributions: [
-      exitWithoutDependency({
-        dependency: 'thing',
-        value: input.value([]),
-      }),
-
-      exitWithoutDependency({
-        dependency: 'thingProperty',
-        value: input.value([]),
-      }),
+      exitWithoutDependency('thing', V([])),
+      exitWithoutDependency('thingProperty', V([])),
 
-      withPropertyFromObject({
-        object: 'thing',
-        property: 'thingProperty',
-      }).outputs({
-        '#value': '#contributions',
-      }),
+      withPropertyFromObject('thing', 'thingProperty')
+        .outputs({'#value': '#contributions'}),
 
-      withPropertyFromList({
-        list: '#contributions',
-        property: input.value('annotation'),
-      }),
+      withPropertyFromList('#contributions', V('annotation')),
 
       {
         dependencies: ['#contributions.annotation', 'annotation'],
@@ -250,71 +213,37 @@ export class Contribution extends Thing {
         }),
       },
 
-      withFilteredList({
-        list: '#contributions',
-        filter: '#likeContributionsFilter',
-      }).outputs({
-        '#filteredList': '#contributions',
-      }),
+      withFilteredList('#contributions', '#likeContributionsFilter')
+        .outputs({'#filteredList': '#contributions'}),
 
-      exposeDependency({
-        dependency: '#contributions',
-      }),
+      exposeDependency('#contributions'),
     ],
 
     previousBySameArtist: [
-      withContainingReverseContributionList().outputs({
-        '#containingReverseContributionList': '#list',
-      }),
+      withContainingReverseContributionList()
+        .outputs({'#containingReverseContributionList': '#list'}),
 
-      exitWithoutDependency({
-        dependency: '#list',
-      }),
-
-      withNearbyItemFromList({
-        list: '#list',
-        item: input.myself(),
-        offset: input.value(-1),
-      }),
+      exitWithoutDependency('#list'),
 
-      exposeDependency({
-        dependency: '#nearbyItem',
-      }),
+      withNearbyItemFromList('#list', input.myself(), V(-1)),
+      exposeDependency('#nearbyItem'),
     ],
 
     nextBySameArtist: [
-      withContainingReverseContributionList().outputs({
-        '#containingReverseContributionList': '#list',
-      }),
+      withContainingReverseContributionList()
+        .outputs({'#containingReverseContributionList': '#list'}),
 
-      exitWithoutDependency({
-        dependency: '#list',
-      }),
-
-      withNearbyItemFromList({
-        list: '#list',
-        item: input.myself(),
-        offset: input.value(+1),
-      }),
+      exitWithoutDependency('#list'),
 
-      exposeDependency({
-        dependency: '#nearbyItem',
-      }),
+      withNearbyItemFromList('#list', input.myself(), V(+1)),
+      exposeDependency('#nearbyItem'),
     ],
 
     groups: [
-      withPropertyFromObject({
-        object: 'thing',
-        property: input.value('groups'),
-      }),
+      withPropertyFromObject('thing', V('groups')),
+      exposeDependencyOrContinue('#thing.groups'),
 
-      exposeDependencyOrContinue({
-        dependency: '#thing.groups',
-      }),
-
-      exposeConstant({
-        value: input.value([]),
-      }),
+      exposeConstant(V([])),
     ],
   });
 
diff --git a/src/data/things/flash.js b/src/data/things/flash.js
index efa99f36..738df937 100644
--- a/src/data/things/flash.js
+++ b/src/data/things/flash.js
@@ -1,6 +1,6 @@
 export const FLASH_DATA_FILE = 'flashes.yaml';
 
-import {input} from '#composite';
+import {input, V} from '#composite';
 import {sortFlashesChronologically} from '#sort';
 import Thing from '#thing';
 import {anyOf, isColor, isContentString, isDirectory, isNumber, isString}
@@ -21,7 +21,6 @@ import {withPropertyFromObject} from '#composite/data';
 import {
   exposeConstant,
   exposeDependency,
-  exposeDependencyOrContinue,
   exposeUpdateValueOrContinue,
 } from '#composite/control-flow';
 
@@ -99,12 +98,8 @@ export class Flash extends Thing {
         validate: input.value(isColor),
       }),
 
-      withPropertyFromObject({
-        object: 'act',
-        property: input.value('color'),
-      }),
-
-      exposeDependency({dependency: '#act.color'}),
+      withPropertyFromObject('act', V('color')),
+      exposeDependency('#act.color'),
     ],
 
     date: simpleDate(),
@@ -153,21 +148,13 @@ export class Flash extends Thing {
 
     // Expose only
 
-    isFlash: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isFlash: exposeConstant(V(true)),
 
     commentatorArtists: commentatorArtists(),
 
     side: [
-      withPropertyFromObject({
-        object: 'act',
-        property: input.value('side'),
-      }),
-
-      exposeDependency({dependency: '#act.side'}),
+      withPropertyFromObject('act', V('side')),
+      exposeDependency('#act.side'),
     ],
   });
 
@@ -297,18 +284,8 @@ export class FlashAct extends Thing {
         validate: input.value(isContentString),
       }),
 
-      withPropertyFromObject({
-        object: 'side',
-        property: input.value('listTerminology'),
-      }),
-
-      exposeDependencyOrContinue({
-        dependency: '#side.listTerminology',
-      }),
-
-      exposeConstant({
-        value: input.value(null),
-      }),
+      withPropertyFromObject('side', V('listTerminology')),
+      exposeDependency('#side.listTerminology'),
     ],
 
     flashes: thingList({
@@ -322,11 +299,7 @@ export class FlashAct extends Thing {
 
     // Expose only
 
-    isFlashAct: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isFlashAct: exposeConstant(V(true)),
   });
 
   static [Thing.findSpecs] = {
@@ -381,11 +354,7 @@ export class FlashSide extends Thing {
 
     // Expose only
 
-    isFlashSide: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isFlashSide: exposeConstant(V(true)),
   });
 
   static [Thing.yamlDocumentSpec] = {
diff --git a/src/data/things/group.js b/src/data/things/group.js
index 076f0c8f..20a74fa1 100644
--- a/src/data/things/group.js
+++ b/src/data/things/group.js
@@ -3,7 +3,7 @@ export const GROUP_DATA_FILE = 'groups.yaml';
 import {inspect} from 'node:util';
 
 import {colors} from '#cli';
-import {input} from '#composite';
+import {input, V} from '#composite';
 import Thing from '#thing';
 import {is, isBoolean} from '#validators';
 import {parseAnnotatedReferences, parseSerieses} from '#yaml';
@@ -53,18 +53,10 @@ export class Group extends Thing {
         '#uniqueReferencingThing': '#category',
       }),
 
-      withPropertyFromObject({
-        object: '#category',
-        property: input.value('excludeGroupsFromGalleryTabs'),
-      }),
-
-      exposeDependencyOrContinue({
-        dependency: '#category.excludeGroupsFromGalleryTabs',
-      }),
+      withPropertyFromObject('#category', V('excludeGroupsFromGalleryTabs')),
+      exposeDependencyOrContinue('#category.excludeGroupsFromGalleryTabs'),
 
-      exposeConstant({
-        value: input.value(false),
-      }),
+      exposeConstant(V(false)),
     ],
 
     divideAlbumsByStyle: flag(false),
@@ -97,11 +89,7 @@ export class Group extends Thing {
 
     // Expose only
 
-    isGroup: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isGroup: exposeConstant(V(true)),
 
     descriptionShort: {
       flags: {expose: true},
@@ -276,11 +264,7 @@ export class GroupCategory extends Thing {
 
     // Expose only
 
-    isGroupCategory: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isGroupCategory: exposeConstant(V(true)),
   });
 
   static [Thing.reverseSpecs] = {
diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js
index e1b29362..70505af6 100644
--- a/src/data/things/homepage-layout.js
+++ b/src/data/things/homepage-layout.js
@@ -3,7 +3,7 @@ export const HOMEPAGE_LAYOUT_DATA_FILE = 'homepage.yaml';
 import {inspect} from 'node:util';
 
 import {colors} from '#cli';
-import {input} from '#composite';
+import {input, V} from '#composite';
 import Thing from '#thing';
 import {empty} from '#sugar';
 
@@ -52,11 +52,7 @@ export class HomepageLayout extends Thing {
 
     // Expose only
 
-    isHomepageLayout: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isHomepageLayout: exposeConstant(V(true)),
   });
 
   static [Thing.yamlDocumentSpec] = {
@@ -167,11 +163,7 @@ export class HomepageLayoutSection extends Thing {
 
     // Expose only
 
-    isHomepageLayoutSection: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isHomepageLayoutSection: exposeConstant(V(true)),
   });
 
   static [Thing.yamlDocumentSpec] = {
@@ -198,11 +190,7 @@ export class HomepageLayoutRow extends Thing {
 
     // Expose only
 
-    isHomepageLayoutRow: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isHomepageLayoutRow: exposeConstant(V(true)),
 
     type: {
       flags: {expose: true},
@@ -253,16 +241,8 @@ export class HomepageLayoutActionsRow extends HomepageLayoutRow {
 
     // Expose only
 
-    isHomepageLayoutActionsRow: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
-
-    type: {
-      flags: {expose: true},
-      expose: {compute: () => 'actions'},
-    },
+    isHomepageLayoutActionsRow: exposeConstant(V(true)),
+    type: exposeConstant(V('actions')),
   });
 
   static [Thing.yamlDocumentSpec] = {
@@ -285,16 +265,8 @@ export class HomepageLayoutAlbumCarouselRow extends HomepageLayoutRow {
 
     // Expose only
 
-    isHomepageLayoutAlbumCarouselRow: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
-
-    type: {
-      flags: {expose: true},
-      expose: {compute: () => 'album carousel'},
-    },
+    isHomepageLayoutAlbumCarouselRow: exposeConstant(V(true)),
+    type: exposeConstant(V('album carousel')),
   });
 
   static [Thing.yamlDocumentSpec] = {
@@ -334,7 +306,7 @@ export class HomepageLayoutAlbumGridRow extends HomepageLayoutRow {
         find: soupyFind.input('group'),
       }),
 
-      exposeDependency({dependency: '#resolvedReference'}),
+      exposeDependency('#resolvedReference'),
     ],
 
     sourceAlbums: referenceList({
@@ -349,16 +321,8 @@ export class HomepageLayoutAlbumGridRow extends HomepageLayoutRow {
 
     // Expose only
 
-    isHomepageLayoutAlbumGridRow: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
-
-    type: {
-      flags: {expose: true},
-      expose: {compute: () => 'album grid'},
-    },
+    isHomepageLayoutAlbumGridRow: exposeConstant(V(true)),
+    type: exposeConstant(V('album grid')),
   });
 
   static [Thing.yamlDocumentSpec] = {
diff --git a/src/data/things/language.js b/src/data/things/language.js
index afda258c..1e22ead6 100644
--- a/src/data/things/language.js
+++ b/src/data/things/language.js
@@ -1,7 +1,8 @@
 import {Temporal, toTemporalInstant} from '@js-temporal/polyfill';
 
 import {withAggregate} from '#aggregate';
-import {input} from '#composite';
+import {logWarn} from '#cli';
+import {input, V} from '#composite';
 import * as html from '#html';
 import {accumulateSum, empty, withEntries} from '#sugar';
 import {isLanguageCode, isObject} from '#validators';
@@ -16,7 +17,7 @@ import {
   isExternalLinkStyle,
 } from '#external-links';
 
-import {exitWithoutDependency, exposeConstant, exposeDependency}
+import {exitWithoutDependency, exposeConstant}
   from '#composite/control-flow';
 import {flag, name} from '#composite/wiki-properties';
 
@@ -126,18 +127,9 @@ export class Language extends Thing {
 
     // Expose only
 
-    isLanguage: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isLanguage: exposeConstant(V(true)),
 
-    onlyIfOptions: {
-      flags: {expose: true},
-      expose: {
-        compute: () => Symbol.for(`language.onlyIfOptions`),
-      },
-    },
+    onlyIfOptions: exposeConstant(V(Symbol.for(`language.onlyIfOptions`))),
 
     intl_date: this.#intlHelper(Intl.DateTimeFormat, {full: true}),
     intl_dateYear: this.#intlHelper(Intl.DateTimeFormat, {year: 'numeric'}),
@@ -167,9 +159,7 @@ export class Language extends Thing {
 
     // TODO: This currently isn't used. Is it still needed?
     strings_htmlEscaped: [
-      exitWithoutDependency({
-        dependency: 'strings',
-      }),
+      exitWithoutDependency('strings'),
 
       {
         dependencies: ['strings'],
diff --git a/src/data/things/news-entry.js b/src/data/things/news-entry.js
index e5467a46..54362d06 100644
--- a/src/data/things/news-entry.js
+++ b/src/data/things/news-entry.js
@@ -1,6 +1,6 @@
 export const NEWS_DATA_FILE = 'news.yaml';
 
-import {input} from '#composite';
+import {V} from '#composite';
 import {sortChronologically} from '#sort';
 import Thing from '#thing';
 import {parseDate} from '#yaml';
@@ -25,11 +25,7 @@ export class NewsEntry extends Thing {
 
     // Expose only
 
-    isNewsEntry: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isNewsEntry: exposeConstant(V(true)),
 
     contentShort: {
       flags: {expose: true},
diff --git a/src/data/things/sorting-rule.js b/src/data/things/sorting-rule.js
index e113955f..71b90277 100644
--- a/src/data/things/sorting-rule.js
+++ b/src/data/things/sorting-rule.js
@@ -3,7 +3,7 @@ export const SORTING_RULE_DATA_FILE = 'sorting-rules.yaml';
 import {readFile, writeFile} from 'node:fs/promises';
 import * as path from 'node:path';
 
-import {input} from '#composite';
+import {V} from '#composite';
 import {chunkByProperties, compareArrays, unique} from '#sugar';
 import Thing from '#thing';
 import {isObject, isStringNonEmpty, anyOf, strictArrayOf} from '#validators';
@@ -52,11 +52,7 @@ export class SortingRule extends Thing {
 
     // Expose only
 
-    isSortingRule: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isSortingRule: exposeConstant(V(true)),
   });
 
   static [Thing.yamlDocumentSpec] = {
@@ -130,18 +126,14 @@ export class ThingSortingRule extends SortingRule {
 
     // Expose only
 
-    isThingSortingRule: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isThingSortingRule: exposeConstant(V(true)),
   });
 
-  static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(SortingRule, {
+  static [Thing.yamlDocumentSpec] = {
     fields: {
       'By Properties': {property: 'properties'},
     },
-  });
+  };
 
   sort(sortable) {
     if (this.properties) {
@@ -237,14 +229,10 @@ export class DocumentSortingRule extends ThingSortingRule {
 
     // Expose only
 
-    isDocumentSortingRule: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isDocumentSortingRule: exposeConstant(V(true)),
   });
 
-  static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(ThingSortingRule, {
+  static [Thing.yamlDocumentSpec] = {
     fields: {
       'Sort Documents': {property: 'filename'},
       'Select Documents Following': {property: 'selectDocumentsFollowing'},
@@ -257,7 +245,7 @@ export class DocumentSortingRule extends ThingSortingRule {
         'Select Documents Under',
       ]},
     ],
-  });
+  };
 
   static async apply(rule, {wikiData, dataPath, dry}) {
     const oldLayout = getThingLayoutForFilename(rule.filename, wikiData);
diff --git a/src/data/things/static-page.js b/src/data/things/static-page.js
index 617bc940..6a8ea8af 100644
--- a/src/data/things/static-page.js
+++ b/src/data/things/static-page.js
@@ -2,7 +2,7 @@ export const DATA_STATIC_PAGE_DIRECTORY = 'static-page';
 
 import * as path from 'node:path';
 
-import {input} from '#composite';
+import {V} from '#composite';
 import {traverse} from '#node-utils';
 import {sortAlphabetically} from '#sort';
 import Thing from '#thing';
@@ -42,11 +42,7 @@ export class StaticPage extends Thing {
 
     // Expose only
 
-    isStaticPage: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isStaticPage: exposeConstant(V(true)),
   });
 
   static [Thing.findSpecs] = {
diff --git a/src/data/things/wiki-info.js b/src/data/things/wiki-info.js
index 73470b7d..258b1af0 100644
--- a/src/data/things/wiki-info.js
+++ b/src/data/things/wiki-info.js
@@ -1,6 +1,6 @@
 export const WIKI_INFO_FILE = 'wiki-info.yaml';
 
-import {input} from '#composite';
+import {input, V} from '#composite';
 import Thing from '#thing';
 import {parseContributionPresets, parseWallpaperParts} from '#yaml';
 
@@ -114,11 +114,7 @@ export class WikiInfo extends Thing {
 
     // Expose only
 
-    isWikiInfo: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
+    isWikiInfo: exposeConstant(V(true)),
   });
 
   static [Thing.yamlDocumentSpec] = {