« get me outta code hell

data: miscellaneous composite template updates - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-09-15 20:03:25 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-09-15 20:03:58 -0300
commit7cd3bdc4998ae1fc1b9ab4bb721d2727f64511e1 (patch)
treedf7c09acd8a10e76deece0afb42461dee2944a19
parent194676f45f54d09a3ad247e9ba4e2b3ba2e56db4 (diff)
data: miscellaneous composite template updates
-rw-r--r--src/data/things/album.js10
-rw-r--r--src/data/things/composite.js23
-rw-r--r--src/data/things/homepage-layout.js11
-rw-r--r--src/data/things/thing.js543
-rw-r--r--src/data/things/track.js93
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({
     },
   ]);
 }
+*/