@codemation/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/{EngineRuntimeRegistration.types-Bjeo7Sfq.d.ts → EngineRuntimeRegistration.types-BtTZolK0.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-Dd4yD31l.d.cts → EngineWorkflowRunnerService-Ddl0fekp.d.cts} +2 -2
- package/dist/{InMemoryRunDataFactory-OUzDmAHt.d.cts → InMemoryRunDataFactory-i-u2yngD.d.cts} +11 -3
- package/dist/{RunIntentService-Bkg4oYrM.d.cts → RunIntentService-Cjx-glgz.d.cts} +232 -237
- package/dist/{RunIntentService-BAKikN8h.d.ts → RunIntentService-Dkr4YwN8.d.ts} +313 -259
- package/dist/bootstrap/index.cjs +2 -2
- package/dist/bootstrap/index.d.cts +19 -7
- package/dist/bootstrap/index.d.ts +3 -3
- package/dist/bootstrap/index.js +2 -2
- package/dist/{bootstrap-DwS5S7s9.cjs → bootstrap-DHH2uo-W.cjs} +4 -2
- package/dist/bootstrap-DHH2uo-W.cjs.map +1 -0
- package/dist/{bootstrap-BD6CobHl.js → bootstrap-DbUlOl11.js} +4 -2
- package/dist/bootstrap-DbUlOl11.js.map +1 -0
- package/dist/{index-uCm9l0nw.d.ts → index-B2v4wtys.d.ts} +62 -34
- package/dist/index.cjs +22 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -42
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -16
- package/dist/index.js.map +1 -1
- package/dist/{runtime-Cy-3FTI_.js → runtime-BdH94eBR.js} +502 -123
- package/dist/runtime-BdH94eBR.js.map +1 -0
- package/dist/{runtime-ZJUpWmPH.cjs → runtime-feFn8OmG.cjs} +561 -122
- package/dist/runtime-feFn8OmG.cjs.map +1 -0
- package/dist/testing.cjs +40 -36
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +17 -26
- package/dist/testing.d.ts +17 -26
- package/dist/testing.js +40 -36
- package/dist/testing.js.map +1 -1
- package/dist/{workflowActivationPolicy-BzyzXLa_.cjs → workflowActivationPolicy-6V3OJD3N.cjs} +65 -19
- package/dist/workflowActivationPolicy-6V3OJD3N.cjs.map +1 -0
- package/dist/{workflowActivationPolicy-B8HzTk3o.js → workflowActivationPolicy-Td9HTOuD.js} +65 -19
- package/dist/workflowActivationPolicy-Td9HTOuD.js.map +1 -0
- package/package.json +2 -1
- package/src/ai/AgentConfigInspectorFactory.ts +4 -0
- package/src/ai/AgentMessageConfigNormalizerFactory.ts +7 -0
- package/src/ai/AgentToolFactory.ts +2 -2
- package/src/ai/AiHost.ts +11 -10
- package/src/ai/NodeBackedToolConfig.ts +1 -1
- package/src/authoring/defineNode.types.ts +48 -72
- package/src/authoring/index.ts +1 -1
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +8 -0
- package/src/contracts/credentialTypes.ts +9 -0
- package/src/contracts/emitPorts.ts +27 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/itemMeta.ts +11 -0
- package/src/contracts/itemValue.ts +147 -0
- package/src/contracts/runtimeTypes.ts +39 -22
- package/src/contracts/workflowTypes.ts +26 -56
- package/src/execution/FanInMergeByOriginMerger.ts +67 -0
- package/src/execution/ItemValueResolver.ts +27 -0
- package/src/execution/NodeActivationRequestComposer.ts +25 -0
- package/src/execution/NodeActivationRequestInputPreparer.ts +57 -25
- package/src/execution/NodeExecutor.ts +199 -30
- package/src/execution/NodeOutputNormalizer.ts +90 -0
- package/src/execution/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +39 -18
- package/src/orchestration/RunContinuationService.ts +11 -17
- package/src/planning/CurrentStateFrontierPlanner.ts +20 -20
- package/src/planning/RunQueuePlanner.ts +56 -19
- package/src/planning/WorkflowTopologyPlanner.ts +57 -33
- package/src/testing/ItemHarnessNode.ts +4 -10
- package/src/testing/ItemHarnessNodeConfig.ts +7 -16
- package/src/testing/RegistrarEngineTestKitFactory.ts +2 -0
- package/src/testing/SubWorkflowRunnerTestNode.ts +28 -43
- package/src/testing/SwitchHarnessNode.ts +54 -0
- package/src/types/index.ts +3 -0
- package/src/workflow/dsl/ChainCursorResolver.ts +68 -23
- package/src/workflow/dsl/WorkflowBuilder.ts +3 -5
- package/src/workflow/dsl/workflowBuilderTypes.ts +5 -8
- package/src/workflowSnapshots/MissingRuntimeNode.ts +4 -4
- package/src/workflowSnapshots/MissingRuntimeNodeConfig.ts +2 -2
- package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +16 -7
- package/dist/bootstrap-BD6CobHl.js.map +0 -1
- package/dist/bootstrap-DwS5S7s9.cjs.map +0 -1
- package/dist/runtime-Cy-3FTI_.js.map +0 -1
- package/dist/runtime-ZJUpWmPH.cjs.map +0 -1
- package/dist/workflowActivationPolicy-B8HzTk3o.js.map +0 -1
- package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +0 -1
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
NodeConfigBase,
|
|
26
26
|
NodeId,
|
|
27
27
|
NodeOutputs,
|
|
28
|
+
RunnableNodeConfig,
|
|
28
29
|
OutputPortKey,
|
|
29
30
|
ParentExecutionRef,
|
|
30
31
|
RunDataFactory,
|
|
@@ -221,39 +222,55 @@ export interface EngineHost {
|
|
|
221
222
|
workflows?: WorkflowRunnerService;
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
export interface Node<TConfig extends NodeConfigBase = NodeConfigBase> {
|
|
225
|
-
kind: "node";
|
|
226
|
-
outputPorts: ReadonlyArray<OutputPortKey>;
|
|
227
|
-
execute(items: Items, ctx: NodeExecutionContext<TConfig>): Promise<NodeOutputs>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
225
|
/**
|
|
231
|
-
*
|
|
232
|
-
* Engine applies
|
|
226
|
+
* Per-item runnable node: return JSON, an array to fan-out on `main`, or {@link emitPorts} for multi-port emission.
|
|
227
|
+
* Engine applies `inputSchema.parse(item.json)` and passes the result as `args.input` (wire `item.json` is unchanged).
|
|
233
228
|
*/
|
|
234
|
-
export interface
|
|
235
|
-
TConfig extends
|
|
229
|
+
export interface RunnableNodeExecuteArgs<
|
|
230
|
+
TConfig extends RunnableNodeConfig<any, any> = RunnableNodeConfig<any, any>,
|
|
236
231
|
TInputJson = unknown,
|
|
237
|
-
|
|
232
|
+
> {
|
|
233
|
+
readonly input: TInputJson;
|
|
234
|
+
readonly item: Item;
|
|
235
|
+
readonly itemIndex: number;
|
|
236
|
+
readonly items: Items;
|
|
237
|
+
readonly ctx: NodeExecutionContext<TConfig>;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface RunnableNode<
|
|
241
|
+
TConfig extends RunnableNodeConfig<any, any> = RunnableNodeConfig<any, any>,
|
|
242
|
+
TInputJson = unknown,
|
|
243
|
+
_TOutputJson = unknown,
|
|
238
244
|
> {
|
|
239
245
|
readonly kind: "node";
|
|
240
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Declared output ports (e.g. `["main"]`).
|
|
248
|
+
*
|
|
249
|
+
* Prefer describing dynamic router ports (Switch) and fixed multi-ports (If true/false)
|
|
250
|
+
* via {@link NodeConfigBase.declaredOutputPorts}. Engine defaults to `["main"]` when omitted.
|
|
251
|
+
*/
|
|
252
|
+
readonly outputPorts?: ReadonlyArray<OutputPortKey>;
|
|
241
253
|
/** When omitted, engine uses {@link RunnableNodeConfig.inputSchema} or `z.unknown()`. */
|
|
242
254
|
readonly inputSchema?: ZodType<TInputJson>;
|
|
243
|
-
|
|
244
|
-
args: Readonly<{
|
|
245
|
-
input: TInputJson;
|
|
246
|
-
item: Item;
|
|
247
|
-
itemIndex: number;
|
|
248
|
-
items: Items;
|
|
249
|
-
ctx: NodeExecutionContext<TConfig>;
|
|
250
|
-
}>,
|
|
251
|
-
): Promise<TOutputJson> | TOutputJson;
|
|
255
|
+
execute(args: RunnableNodeExecuteArgs<TConfig, TInputJson>): Promise<unknown> | unknown;
|
|
252
256
|
}
|
|
253
257
|
|
|
258
|
+
/** @deprecated Use {@link RunnableNode} */
|
|
259
|
+
export type ItemNode<
|
|
260
|
+
TConfig extends RunnableNodeConfig<any, any> = RunnableNodeConfig<any, any>,
|
|
261
|
+
TInputJson = unknown,
|
|
262
|
+
TOutputJson = unknown,
|
|
263
|
+
> = RunnableNode<TConfig, TInputJson, TOutputJson>;
|
|
264
|
+
|
|
254
265
|
export interface MultiInputNode<TConfig extends NodeConfigBase = NodeConfigBase> {
|
|
255
266
|
kind: "node";
|
|
256
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Declared output ports (typically `["main"]`).
|
|
269
|
+
*
|
|
270
|
+
* Prefer describing ports for authoring/canvas via {@link NodeConfigBase.declaredOutputPorts}.
|
|
271
|
+
* Engine defaults to `["main"]` when omitted.
|
|
272
|
+
*/
|
|
273
|
+
outputPorts?: ReadonlyArray<OutputPortKey>;
|
|
257
274
|
executeMulti(inputsByPort: NodeInputsByPort, ctx: NodeExecutionContext<TConfig>): Promise<NodeOutputs>;
|
|
258
275
|
}
|
|
259
276
|
|
|
@@ -17,6 +17,8 @@ export interface JsonObject {
|
|
|
17
17
|
}
|
|
18
18
|
export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
19
19
|
export type JsonArray = ReadonlyArray<JsonValue>;
|
|
20
|
+
/** JSON value that is not a top-level array (nested arrays inside objects are allowed). */
|
|
21
|
+
export type JsonNonArray = JsonPrimitive | JsonObject;
|
|
20
22
|
|
|
21
23
|
export interface Edge {
|
|
22
24
|
from: { nodeId: NodeId; output: OutputPortKey };
|
|
@@ -81,74 +83,45 @@ export interface NodeConfigBase {
|
|
|
81
83
|
* main batches skip downstream execution and propagate the empty path.
|
|
82
84
|
*/
|
|
83
85
|
readonly continueWhenEmptyOutput?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Declared I/O port names for canvas authoring (unioned with ports inferred from edges).
|
|
88
|
+
* Use for dynamic routers (Switch) and future error ports.
|
|
89
|
+
*/
|
|
90
|
+
readonly declaredOutputPorts?: ReadonlyArray<OutputPortKey>;
|
|
91
|
+
readonly declaredInputPorts?: ReadonlyArray<InputPortKey>;
|
|
84
92
|
getCredentialRequirements?(): ReadonlyArray<CredentialRequirement>;
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
export declare const runnableNodeInputType: unique symbol;
|
|
88
96
|
export declare const runnableNodeOutputType: unique symbol;
|
|
89
|
-
/** Phantom: JSON shape on the wire from upstream before {@link RunnableNodeConfig.mapInput}. */
|
|
90
|
-
export declare const runnableNodeWireType: unique symbol;
|
|
91
97
|
export declare const triggerNodeOutputType: unique symbol;
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
* Read-only execution slice passed to {@link RunnableNodeConfig.mapInput} (aligned with the engine’s
|
|
95
|
-
* node execution context for `runId`, `data`, etc.). Use **`ctx.data`** to read **any completed** upstream
|
|
96
|
-
* node’s outputs in this run (e.g. `ctx.data.getOutputItems(nodeIdA, "main")` while mapping at D), not only
|
|
97
|
-
* the immediate predecessor’s {@link ItemInputMapperArgs.item}.
|
|
98
|
-
*/
|
|
99
|
-
export interface ItemInputMapperContext {
|
|
100
|
-
readonly runId: RunId;
|
|
101
|
-
readonly workflowId: WorkflowId;
|
|
102
|
-
/** Node whose activation is being prepared (the consumer of `mapInput`). */
|
|
103
|
-
readonly nodeId: NodeId;
|
|
104
|
-
readonly activationId: NodeActivationId;
|
|
105
|
-
readonly parent?: ParentExecutionRef;
|
|
106
|
-
readonly data: RunDataSnapshot;
|
|
107
|
-
}
|
|
99
|
+
export type LineageCarryPolicy = "emitOnly" | "carryThrough";
|
|
108
100
|
|
|
109
101
|
/**
|
|
110
|
-
*
|
|
102
|
+
* Runnable node: **`TInputJson`** is what **`inputSchema`** validates on **`item.json`** (the wire payload).
|
|
103
|
+
* **`TOutputJson`** is emitted `item.json` on outputs.
|
|
111
104
|
*/
|
|
112
|
-
export interface
|
|
113
|
-
readonly item: Item<TWireJson>;
|
|
114
|
-
readonly itemIndex: number;
|
|
115
|
-
readonly items: Items<TWireJson>;
|
|
116
|
-
readonly ctx: ItemInputMapperContext;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Per-item mapper before Zod validation. Uses a **bivariant** method signature so concrete
|
|
121
|
-
* `ItemInputMapper<SpecificWire, TIn>` remains assignable to `RunnableNodeConfig` fields typed as
|
|
122
|
-
* `ItemInputMapper<unknown, unknown>` (same pattern as React-style callbacks).
|
|
123
|
-
*/
|
|
124
|
-
export type ItemInputMapper<TWireJson = unknown, TInputJson = unknown> = {
|
|
125
|
-
bivarianceHack(args: ItemInputMapperArgs<TWireJson>): TInputJson | Promise<TInputJson>;
|
|
126
|
-
}["bivarianceHack"];
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Runnable node: **`TInputJson`** is the payload after `mapInput` (if any) + Zod validation — what {@link ItemNode}
|
|
130
|
-
* `executeOne` receives. **`TOutputJson`** is emitted `item.json` on outputs. **`TWireJson`** is `item.json` from
|
|
131
|
-
* upstream **before** `mapInput`; it defaults to **`TInputJson`** when there is no mapper or wire differs from execute input.
|
|
132
|
-
*/
|
|
133
|
-
export interface RunnableNodeConfig<
|
|
134
|
-
TInputJson = unknown,
|
|
135
|
-
TOutputJson = unknown,
|
|
136
|
-
TWireJson = TInputJson,
|
|
137
|
-
> extends NodeConfigBase {
|
|
105
|
+
export interface RunnableNodeConfig<TInputJson = unknown, TOutputJson = unknown> extends NodeConfigBase {
|
|
138
106
|
readonly kind: "node";
|
|
139
107
|
readonly [runnableNodeInputType]?: TInputJson;
|
|
140
108
|
readonly [runnableNodeOutputType]?: TOutputJson;
|
|
141
|
-
readonly [runnableNodeWireType]?: TWireJson;
|
|
142
109
|
/**
|
|
143
|
-
* Optional Zod input contract for {@link
|
|
110
|
+
* Optional Zod input contract for {@link RunnableNode} when not set on the node class.
|
|
144
111
|
* Resolution order: node instance `inputSchema`, then config `inputSchema`, then `z.unknown()`.
|
|
145
112
|
*/
|
|
146
113
|
readonly inputSchema?: ZodType<TInputJson>;
|
|
147
114
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
115
|
+
* Overrides default lineage propagation for `execute` outputs (binary/meta/paired).
|
|
116
|
+
* Routers with multiple {@link RunnableNode#outputPorts} default to **`carryThrough`**; others default to **`emitOnly`**.
|
|
117
|
+
*/
|
|
118
|
+
readonly lineageCarry?: LineageCarryPolicy;
|
|
119
|
+
/**
|
|
120
|
+
* When an activation receives **zero** input items, the engine normally runs `execute` zero times.
|
|
121
|
+
* Set to **`runOnce`** to run `execute` once with an empty `items` batch (and a synthetic wire item for schema parsing).
|
|
122
|
+
* Used by batch-style callback nodes (built-in `Callback`) so `callback([], ctx)` still runs.
|
|
150
123
|
*/
|
|
151
|
-
readonly
|
|
124
|
+
readonly emptyBatchExecution?: "skip" | "runOnce";
|
|
152
125
|
}
|
|
153
126
|
|
|
154
127
|
export declare const triggerNodeSetupStateType: unique symbol;
|
|
@@ -162,14 +135,11 @@ export interface TriggerNodeConfig<
|
|
|
162
135
|
readonly [triggerNodeSetupStateType]?: TSetupState;
|
|
163
136
|
}
|
|
164
137
|
|
|
165
|
-
export type RunnableNodeInputJson<TConfig extends RunnableNodeConfig<any, any
|
|
166
|
-
TConfig extends RunnableNodeConfig<infer TInputJson, any
|
|
167
|
-
|
|
168
|
-
export type RunnableNodeWireJson<TConfig extends RunnableNodeConfig<any, any, any>> =
|
|
169
|
-
TConfig extends RunnableNodeConfig<any, any, infer TWireJson> ? TWireJson : never;
|
|
138
|
+
export type RunnableNodeInputJson<TConfig extends RunnableNodeConfig<any, any>> =
|
|
139
|
+
TConfig extends RunnableNodeConfig<infer TInputJson, any> ? TInputJson : never;
|
|
170
140
|
|
|
171
|
-
export type RunnableNodeOutputJson<TConfig extends RunnableNodeConfig<any, any
|
|
172
|
-
TConfig extends RunnableNodeConfig<any, infer TOutputJson
|
|
141
|
+
export type RunnableNodeOutputJson<TConfig extends RunnableNodeConfig<any, any>> =
|
|
142
|
+
TConfig extends RunnableNodeConfig<any, infer TOutputJson> ? TOutputJson : never;
|
|
173
143
|
|
|
174
144
|
export type TriggerNodeOutputJson<TConfig extends TriggerNodeConfig<any, any>> =
|
|
175
145
|
TConfig extends TriggerNodeConfig<infer TOutputJson, any> ? TOutputJson : never;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { InputPortKey, Item, Items, NodeInputsByPort } from "../types";
|
|
2
|
+
|
|
3
|
+
import { getOriginIndexFromItem } from "../contracts/itemMeta";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default fan-in: combine multi-port {@link NodeInputsByPort} into one {@link Items} batch for per-item nodes.
|
|
7
|
+
*
|
|
8
|
+
* This is used when a single-input per-item node has multiple inbound edges (for example, branch reconverge
|
|
9
|
+
* after an `If` / `Switch`). The default behavior is **append / union** (preserving item payloads) with a
|
|
10
|
+
* deterministic order:
|
|
11
|
+
*
|
|
12
|
+
* - When router origin metadata exists (`meta._cm.originIndex`), items are sorted by origin index so the
|
|
13
|
+
* downstream batch preserves original ordering across branches.
|
|
14
|
+
* - Otherwise, items are appended by port-key order, preserving each port's local order.
|
|
15
|
+
*/
|
|
16
|
+
export class FanInMergeByOriginMerger {
|
|
17
|
+
merge(inputsByPort: NodeInputsByPort): Items {
|
|
18
|
+
const portKeys = Object.keys(inputsByPort).sort();
|
|
19
|
+
if (portKeys.length === 0) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
if (portKeys.length === 1) {
|
|
23
|
+
const only = portKeys[0]!;
|
|
24
|
+
return [...(inputsByPort[only] ?? [])];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type Entry = Readonly<{
|
|
28
|
+
portKey: InputPortKey;
|
|
29
|
+
portIndex: number;
|
|
30
|
+
item: Item;
|
|
31
|
+
originIndex: number | undefined;
|
|
32
|
+
}>;
|
|
33
|
+
|
|
34
|
+
const entries: Entry[] = [];
|
|
35
|
+
let anyOrigin = false;
|
|
36
|
+
|
|
37
|
+
for (let p = 0; p < portKeys.length; p++) {
|
|
38
|
+
const portKey = portKeys[p]!;
|
|
39
|
+
const items = inputsByPort[portKey] ?? [];
|
|
40
|
+
for (let i = 0; i < items.length; i++) {
|
|
41
|
+
const item = items[i] as Item;
|
|
42
|
+
const originIndex = getOriginIndexFromItem(item);
|
|
43
|
+
if (originIndex !== undefined) {
|
|
44
|
+
anyOrigin = true;
|
|
45
|
+
}
|
|
46
|
+
entries.push({ portKey, portIndex: i, item, originIndex });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!anyOrigin) {
|
|
51
|
+
return entries.map((e) => e.item);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const missingOriginRank = Number.MAX_SAFE_INTEGER;
|
|
55
|
+
return entries
|
|
56
|
+
.slice()
|
|
57
|
+
.sort((a, b) => {
|
|
58
|
+
const ao = a.originIndex ?? missingOriginRank;
|
|
59
|
+
const bo = b.originIndex ?? missingOriginRank;
|
|
60
|
+
if (ao !== bo) return ao - bo;
|
|
61
|
+
const pk = a.portKey.localeCompare(b.portKey);
|
|
62
|
+
if (pk !== 0) return pk;
|
|
63
|
+
return a.portIndex - b.portIndex;
|
|
64
|
+
})
|
|
65
|
+
.map((e) => e.item);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveItemValuesForExecution } from "../contracts/itemValue";
|
|
2
|
+
import type { Item, NodeExecutionContext, RunnableNodeConfig } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves {@link import("../contracts/itemValue").ItemValue} leaves on runnable config before {@link RunnableNode.execute}.
|
|
6
|
+
*/
|
|
7
|
+
export class ItemValueResolver {
|
|
8
|
+
async resolveConfigForItem<TConfig extends RunnableNodeConfig<any, any>>(
|
|
9
|
+
ctx: NodeExecutionContext<TConfig>,
|
|
10
|
+
item: Item,
|
|
11
|
+
itemIndex: number,
|
|
12
|
+
items: ReadonlyArray<Item>,
|
|
13
|
+
): Promise<NodeExecutionContext<TConfig>> {
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new Error("ItemValueResolver.resolveConfigForItem: ctx is required");
|
|
16
|
+
}
|
|
17
|
+
const resolvedConfig = await resolveItemValuesForExecution(ctx.config, ctx, item, itemIndex, items);
|
|
18
|
+
const merged = resolvedConfig !== undefined && resolvedConfig !== null ? resolvedConfig : ctx.config;
|
|
19
|
+
if (merged === undefined || merged === null) {
|
|
20
|
+
return ctx;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
...ctx,
|
|
24
|
+
config: merged as TConfig,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
NodeActivationRequest,
|
|
7
7
|
NodeExecutionContext,
|
|
8
8
|
NodeId,
|
|
9
|
+
NodeInputsByPort,
|
|
9
10
|
ParentExecutionRef,
|
|
10
11
|
RunDataFactory,
|
|
11
12
|
RunExecutionOptions,
|
|
@@ -34,6 +35,12 @@ export type SingleDefinitionActivationRequest = NodeActivationContextArgs & {
|
|
|
34
35
|
input: Items;
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
export type MultiDefinitionActivationRequest = NodeActivationContextArgs & {
|
|
39
|
+
definition: NodeExecutionDefinition;
|
|
40
|
+
batchId: string;
|
|
41
|
+
inputsByPort: NodeInputsByPort;
|
|
42
|
+
};
|
|
43
|
+
|
|
37
44
|
export type PlannedNodeActivationRequest = NodeActivationContextArgs & {
|
|
38
45
|
next: PlannedActivation;
|
|
39
46
|
nodeDefinition: NodeExecutionDefinition;
|
|
@@ -75,6 +82,24 @@ export class NodeActivationRequestComposer {
|
|
|
75
82
|
};
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
createMultiFromDefinitionWithActivation(
|
|
86
|
+
args: MultiDefinitionActivationRequest & Readonly<{ activationId: NodeActivationId }>,
|
|
87
|
+
): Extract<NodeActivationRequest, { kind: "multi" }> {
|
|
88
|
+
const ctx = this.createNodeExecutionContext(args, args.definition, args.activationId);
|
|
89
|
+
return {
|
|
90
|
+
kind: "multi",
|
|
91
|
+
runId: args.runId,
|
|
92
|
+
activationId: args.activationId,
|
|
93
|
+
workflowId: args.workflowId,
|
|
94
|
+
nodeId: args.definition.id,
|
|
95
|
+
parent: args.parent,
|
|
96
|
+
executionOptions: args.executionOptions,
|
|
97
|
+
batchId: args.batchId,
|
|
98
|
+
inputsByPort: args.inputsByPort,
|
|
99
|
+
ctx,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
78
103
|
createFromPlannedActivation(args: PlannedNodeActivationRequest): NodeActivationRequest {
|
|
79
104
|
const activationId = this.activationIdFactory.makeActivationId();
|
|
80
105
|
const ctx = this.createNodeExecutionContext(args, args.nodeDefinition, activationId);
|
|
@@ -2,40 +2,62 @@ import { z, ZodError } from "zod";
|
|
|
2
2
|
|
|
3
3
|
import type { Item, NodeActivationRequest, RunnableNodeConfig, WorkflowNodeInstanceFactory } from "../types";
|
|
4
4
|
|
|
5
|
+
import { FanInMergeByOriginMerger } from "./FanInMergeByOriginMerger";
|
|
5
6
|
import { NodeInputContractError } from "./NodeInputContractError";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* Validates per-item inputs for {@link RunnableNode} before enqueue persistence (Zod on `item.json`).
|
|
10
|
+
* Does not rewrite `item.json` (wire stays as emitted upstream; engine passes parsed input via `execute` args).
|
|
11
|
+
* Converts multi-input activations into a single-input batch when the node is per-item only (engine fan-in).
|
|
9
12
|
*/
|
|
10
13
|
export class NodeActivationRequestInputPreparer {
|
|
14
|
+
private readonly fanInMerger = new FanInMergeByOriginMerger();
|
|
15
|
+
|
|
11
16
|
constructor(private readonly workflowNodeInstanceFactory: WorkflowNodeInstanceFactory) {}
|
|
12
17
|
|
|
13
18
|
async prepare(request: NodeActivationRequest): Promise<NodeActivationRequest> {
|
|
14
|
-
if (request.kind
|
|
19
|
+
if (request.kind === "multi") {
|
|
20
|
+
return await this.prepareMulti(request);
|
|
21
|
+
}
|
|
22
|
+
return await this.prepareSingle(request);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private async prepareMulti(
|
|
26
|
+
request: Extract<NodeActivationRequest, { kind: "multi" }>,
|
|
27
|
+
): Promise<NodeActivationRequest> {
|
|
28
|
+
const nodeInstance: unknown = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
|
|
29
|
+
if (
|
|
30
|
+
!this.hasRunnableExecute(nodeInstance) ||
|
|
31
|
+
this.hasExecuteMulti(nodeInstance) ||
|
|
32
|
+
this.isTriggerNode(nodeInstance)
|
|
33
|
+
) {
|
|
15
34
|
return request;
|
|
16
35
|
}
|
|
36
|
+
const merged = this.fanInMerger.merge(request.inputsByPort);
|
|
37
|
+
const single: Extract<NodeActivationRequest, { kind: "single" }> = {
|
|
38
|
+
...request,
|
|
39
|
+
kind: "single",
|
|
40
|
+
input: merged,
|
|
41
|
+
};
|
|
42
|
+
return await this.prepareSingle(single);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async prepareSingle(
|
|
46
|
+
request: Extract<NodeActivationRequest, { kind: "single" }>,
|
|
47
|
+
): Promise<NodeActivationRequest> {
|
|
17
48
|
const nodeInstance: unknown = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
|
|
18
|
-
if (!this.
|
|
49
|
+
if (!this.hasRunnableExecute(nodeInstance) || this.isTriggerNode(nodeInstance)) {
|
|
19
50
|
return request;
|
|
20
51
|
}
|
|
21
52
|
const inputSchema = this.resolveInputSchema(nodeInstance, request.ctx.config as RunnableNodeConfig);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const item = request.input[i] as Item;
|
|
53
|
+
const inputBatch = request.input ?? [];
|
|
54
|
+
for (let i = 0; i < inputBatch.length; i++) {
|
|
55
|
+
const item = inputBatch[i] as Item;
|
|
26
56
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
itemIndex: i,
|
|
32
|
-
items: request.input,
|
|
33
|
-
ctx: request.ctx,
|
|
34
|
-
}),
|
|
35
|
-
)
|
|
36
|
-
: item.json;
|
|
37
|
-
const parsed = inputSchema.parse(mappedRaw);
|
|
38
|
-
mappedItems.push({ ...item, json: parsed });
|
|
57
|
+
if (Array.isArray(item.json)) {
|
|
58
|
+
throw new Error("Item JSON must not be a top-level array");
|
|
59
|
+
}
|
|
60
|
+
inputSchema.parse(item.json);
|
|
39
61
|
} catch (cause) {
|
|
40
62
|
const message = this.formatContractFailure(cause);
|
|
41
63
|
throw new NodeInputContractError(
|
|
@@ -46,20 +68,30 @@ export class NodeActivationRequestInputPreparer {
|
|
|
46
68
|
);
|
|
47
69
|
}
|
|
48
70
|
}
|
|
49
|
-
return {
|
|
50
|
-
...request,
|
|
51
|
-
input: mappedItems,
|
|
52
|
-
};
|
|
71
|
+
return request.input === undefined ? { ...request, input: inputBatch } : request;
|
|
53
72
|
}
|
|
54
73
|
|
|
55
|
-
private
|
|
74
|
+
private isTriggerNode(nodeInstance: unknown): boolean {
|
|
56
75
|
return (
|
|
57
76
|
typeof nodeInstance === "object" &&
|
|
58
77
|
nodeInstance !== null &&
|
|
59
|
-
|
|
78
|
+
(nodeInstance as { kind?: string }).kind === "trigger"
|
|
60
79
|
);
|
|
61
80
|
}
|
|
62
81
|
|
|
82
|
+
private hasRunnableExecute(nodeInstance: unknown): boolean {
|
|
83
|
+
return (
|
|
84
|
+
typeof nodeInstance === "object" &&
|
|
85
|
+
nodeInstance !== null &&
|
|
86
|
+
(nodeInstance as { kind?: string }).kind === "node" &&
|
|
87
|
+
typeof (nodeInstance as { execute?: unknown }).execute === "function"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private hasExecuteMulti(nodeInstance: unknown): boolean {
|
|
92
|
+
return typeof (nodeInstance as { executeMulti?: unknown }).executeMulti === "function";
|
|
93
|
+
}
|
|
94
|
+
|
|
63
95
|
private resolveInputSchema(
|
|
64
96
|
nodeInstance: unknown,
|
|
65
97
|
config: RunnableNodeConfig,
|