import { Arr, Fun, Id, Obj, type Result } from '@ephox/katamari'; import type { DieFn, NextFn } from '../pipe/Pipe'; import { Chain } from './Chain'; import type { TestLogs } from './TestLogs'; const inputNameId = Id.generate('input-name'); const outputNameId = Id.generate('output-name'); const outputUnset = Id.generate('output-unset'); export type NamedData = Record; export type NamedChain = Chain; const asChain = (chains: NamedChain[]): Chain => Chain.fromChains(Arr.flatten([ [ Chain.mapper((input: T) => ({ [inputNameId]: input, [outputNameId]: outputUnset })) ], chains, [ Chain.mapper((data: NamedData) => { const output = data[outputNameId]; delete data[outputNameId]; return output === outputUnset ? data : output; }) ] ])); // Write merges in its output into input because it knows that it was // given a complete input. const write = (name: string, chain: Chain): Chain => Chain.on((input, next, die, initLogs) => { chain.runChain(input, (output, newLogs) => { const data = wrapSingle(name, output); return next( { ...input, ...data }, newLogs ); }, die, initLogs); }); // Partial write does not try and merge in input, because it knows that it // might not be getting the full input const partialWrite = (name: string, chain: Chain): Chain => Chain.on((input, next, die, initLogs) => { chain.runChain(input, (output, newLogs) => { const data = wrapSingle(name, output); return next(data, newLogs); }, die, initLogs); }); const wrapSingle = (name: string, value: any): NamedData => { if (name === '_') { return {}; } return { [name]: value }; }; const combine = (input: NamedData, name: string, value: any): NamedData => ({ ...input, ...wrapSingle(name, value) }); const process = (name: string, chain: Chain): Chain => Chain.on((input, next, die, initLogs) => { if (Obj.has(input, name)) { const part = input[name]; chain.runChain(part, (other, newLogs) => { const merged: NamedData = { ...input, ...other }; next(merged, newLogs); }, die, initLogs); } else { die(name + ' is not a field in the index object.', initLogs); } }); const direct = (inputName: string, chain: Chain, outputName: string): Chain => process(inputName, partialWrite(outputName, chain)); const overwrite = (inputName: string, chain: Chain): Chain => direct(inputName, chain, inputName); const writeValue = (name: string, value: any): Chain => Chain.mapper((input) => combine(input, name, value)); const read = (name: string, chain: Chain): Chain => Chain.on((input, next, die, initLogs) => { chain.runChain(input[name], (_, newLogs) => next(input, newLogs), die, initLogs ); }); const merge = (names: string[], combinedName: string): Chain => Chain.mapper((input) => { const r: NamedData = {}; Arr.each(names, (name) => { r[name] = input[name]; }); return combine(input, combinedName, r); }); const bundle = (f: (input: NamedData) => Result): Chain => write(outputNameId, Chain.binder(f)); const output = (name: string): Chain => direct(name, Chain.identity, outputNameId); const outputInput = output(inputNameId); const pipeline = (namedChains: NamedChain[], onSuccess: NextFn, onFailure: DieFn, initLogs: TestLogs): void => { Chain.pipeline([ asChain(namedChains) ], onSuccess, onFailure, initLogs); }; const inputName = Fun.constant(inputNameId); // tests need these values but other users should not export const _outputName = Fun.constant(outputNameId); export const _outputUnset = Fun.constant(outputUnset); export const NamedChain = { inputName, asChain, write, direct, writeValue, overwrite, read, merge, bundle, output, outputInput, pipeline };