import { IGraph, makeGraphApi } from '../Graphs/Graph.js'; import { NodeConfiguration } from './Node.js'; import { IFunctionNodeDefinition, IHasTriggered, SocketNames, SocketsDefinition } from './NodeDefinitions.js'; import { makeOrGenerateSockets } from './nodeFactory.js'; import { NodeConfigurationDescription } from './Registry/NodeDescription.js'; const makeEmptyGraph = (): IGraph => { return makeGraphApi({ dependencies: {}, values: {} }); }; export type SocketValues = { [key in SocketNames]?: any; }; /** Helper function to test an function node's exec and get the resulting outputs. * Can simulate the input socket values. Returns the output socket values */ export const testExec = < TInput extends SocketsDefinition, TOutput extends SocketsDefinition, TConfig extends NodeConfigurationDescription >({ nodeInputVals = {}, configuration = {}, exec, makeGraph = makeEmptyGraph }: { /** Exec function from the node defintion */ exec: IFunctionNodeDefinition['exec']; /** Runtime configuration of the node */ configuration?: NodeConfiguration; /** Simulated input values the input sockets have */ nodeInputVals?: SocketValues; makeGraph?: () => IGraph; }): SocketValues => { const outputs: SocketValues = {}; exec({ read: (socketName) => nodeInputVals[socketName], write: (outputValueName, value) => { outputs[outputValueName] = value; }, configuration, graph: makeGraph() }); return outputs; }; export enum RecordedOutputType { write = 'write', commit = 'commit' } export type RecordedWritesOrCommits = ( | { outputType: RecordedOutputType.write; socketName: SocketNames; value: any; } | { outputType: RecordedOutputType.commit; socketName: SocketNames; } )[]; /** * Generates a function that can be used to test the triggered function of a node. * The trigger function will maintain state between each invokation, and returns a list * the recorded outputs, including the commits to flow outputs. * @returns */ export const generateTriggerTester = < TInput extends SocketsDefinition, TOutput extends SocketsDefinition, TState >( { triggered, initialState, out }: { /** Triggered function from the node defintion */ /** Runtime configuration of the node */ configuration?: NodeConfiguration; makeGraph?: () => IGraph; } & Pick< IHasTriggered, 'initialState' | 'triggered' > & { out: TOutput; }, configuration: NodeConfiguration = {}, makeGraph = makeEmptyGraph ) => { let state: TState = initialState; const graph = makeGraph(); const outputSocketKeys = getOutputSocketKeys({ outputs: out, config: configuration, graph }); /** Triggers the `triggered` function, and updates internal state. Returns a * list of the recorded outputs, including the commits to flow outputs. */ const trigger = ({ inputVals = {}, triggeringSocketName }: { /** input values to simulate on the input sockets */ inputVals?: SocketValues; /** name of the flow socket that is to be triggered */ triggeringSocketName: SocketNames; }) => { const recordedOutputs: RecordedWritesOrCommits = []; // call the triggered function with the current state and // simulated input vals, and udpate the state with the result. state = triggered({ triggeringSocketName, read: (socketName) => inputVals[socketName], write: (outputValueName, value) => { recordedOutputs.push({ outputType: RecordedOutputType.write, socketName: outputValueName, value: value }); }, commit: (outputFlowName, fiberCompletedListener) => { recordedOutputs.push({ outputType: RecordedOutputType.commit, socketName: outputFlowName }); if (fiberCompletedListener) { fiberCompletedListener(() => Promise.resolve()); } }, configuration, graph, state, finished: () => { return; }, outputSocketKeys }) as TState; return recordedOutputs; }; return trigger; }; function getOutputSocketKeys({ outputs, config, graph }: { outputs: TSockets; config: NodeConfiguration; graph: IGraph; }): SocketNames[] { const sockets = makeOrGenerateSockets(outputs, config, graph); return sockets.map((x) => x.name) as SocketNames[]; }