@codemation/core 0.2.1 → 0.3.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +2 -0
  3. package/dist/{EngineRuntimeRegistration.types-0sgV2XL2.d.ts → EngineRuntimeRegistration.types-Bjeo7Sfq.d.ts} +2 -2
  4. package/dist/{EngineWorkflowRunnerService-Dx7bJsJR.d.cts → EngineWorkflowRunnerService-Dd4yD31l.d.cts} +2 -2
  5. package/dist/{InMemoryRunDataFactory-qIYQEar7.d.cts → InMemoryRunDataFactory-OUzDmAHt.d.cts} +2 -2
  6. package/dist/{RunIntentService-BCvGdOSY.d.ts → RunIntentService-BAKikN8h.d.ts} +80 -12
  7. package/dist/{RunIntentService-CV8izV8t.d.cts → RunIntentService-Bkg4oYrM.d.cts} +74 -5
  8. package/dist/bootstrap/index.cjs +31 -31
  9. package/dist/bootstrap/index.d.cts +5 -3
  10. package/dist/bootstrap/index.d.ts +3 -3
  11. package/dist/bootstrap/index.js +2 -2
  12. package/dist/bootstrap-BD6CobHl.js +215 -0
  13. package/dist/bootstrap-BD6CobHl.js.map +1 -0
  14. package/dist/bootstrap-DwS5S7s9.cjs +240 -0
  15. package/dist/bootstrap-DwS5S7s9.cjs.map +1 -0
  16. package/dist/{index-CueSzHsf.d.ts → index-uCm9l0nw.d.ts} +64 -15
  17. package/dist/index.cjs +114 -32
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +68 -21
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +83 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/{RunIntentService-BFA48UpH.js → runtime-Cy-3FTI_.js} +1224 -94
  24. package/dist/runtime-Cy-3FTI_.js.map +1 -0
  25. package/dist/{RunIntentService-DcxXf_AM.cjs → runtime-ZJUpWmPH.cjs} +1251 -132
  26. package/dist/runtime-ZJUpWmPH.cjs.map +1 -0
  27. package/dist/testing.cjs +74 -29
  28. package/dist/testing.cjs.map +1 -1
  29. package/dist/testing.d.cts +55 -3
  30. package/dist/testing.d.ts +55 -3
  31. package/dist/testing.js +46 -3
  32. package/dist/testing.js.map +1 -1
  33. package/dist/workflowActivationPolicy-B8HzTk3o.js.map +1 -1
  34. package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +1 -1
  35. package/package.json +1 -1
  36. package/src/ai/AgentMessageConfigNormalizerFactory.ts +43 -0
  37. package/src/ai/AgentToolFactory.ts +2 -2
  38. package/src/ai/AiHost.ts +10 -9
  39. package/src/ai/NodeBackedToolConfig.ts +1 -1
  40. package/src/authoring/defineNode.types.ts +153 -10
  41. package/src/authoring/index.ts +3 -1
  42. package/src/contracts/runtimeTypes.ts +26 -0
  43. package/src/contracts/workflowTypes.ts +67 -5
  44. package/src/execution/ActivationEnqueueService.ts +8 -5
  45. package/src/execution/NodeActivationRequestInputPreparer.ts +89 -0
  46. package/src/execution/NodeExecutor.ts +38 -1
  47. package/src/execution/NodeInputContractError.ts +13 -0
  48. package/src/execution/index.ts +2 -0
  49. package/src/orchestration/RunContinuationService.ts +181 -50
  50. package/src/planning/RunQueuePlanner.ts +12 -1
  51. package/src/runtime/EngineFactory.ts +3 -0
  52. package/src/testing/ItemHarnessNode.ts +27 -0
  53. package/src/testing/ItemHarnessNodeConfig.ts +43 -0
  54. package/src/testing/RegistrarEngineTestKitFactory.ts +2 -0
  55. package/src/testing.ts +2 -0
  56. package/src/workflow/dsl/ChainCursorResolver.ts +1 -1
  57. package/src/workflow/dsl/workflowBuilderTypes.ts +8 -5
  58. package/dist/RunIntentService-BFA48UpH.js.map +0 -1
  59. package/dist/RunIntentService-DcxXf_AM.cjs.map +0 -1
  60. package/dist/bootstrap-D67Sf2BF.js +0 -1136
  61. package/dist/bootstrap-D67Sf2BF.js.map +0 -1
  62. package/dist/bootstrap-DoQHAEQJ.cjs +0 -1203
  63. package/dist/bootstrap-DoQHAEQJ.cjs.map +0 -1
@@ -4,10 +4,11 @@ import type {
4
4
  CredentialRequirement,
5
5
  CredentialTypeId,
6
6
  } from "../contracts/credentialTypes";
7
- import type { Node, NodeExecutionContext } from "../contracts/runtimeTypes";
8
- import type { Items, NodeOutputs, RunnableNodeConfig } from "../contracts/workflowTypes";
7
+ import type { ItemNode, Node, NodeExecutionContext } from "../contracts/runtimeTypes";
8
+ import type { Item, ItemInputMapper, Items, NodeOutputs, RunnableNodeConfig } from "../contracts/workflowTypes";
9
9
  import type { TypeToken } from "../di";
10
10
  import { node as persistedNode } from "../runtime-types/runtimeTypeDecorators.types";
11
+ import type { ZodType } from "zod";
11
12
  import { z } from "zod";
12
13
  import { DefinedNodeRegistry } from "./DefinedNodeRegistry";
13
14
 
@@ -54,27 +55,46 @@ export interface DefinedNodeRunContext<
54
55
  readonly execution: NodeExecutionContext<RunnableNodeConfig<TConfig, unknown>>;
55
56
  }
56
57
 
58
+ /**
59
+ * Arguments for {@link defineNode} `executeOne` (engine `ctx` matches {@link ItemNode.executeOne};
60
+ * the second callback parameter adds {@link DefinedNodeRunContext} for credential accessors).
61
+ */
62
+ export type DefineNodeExecuteOneArgs<TConfig extends CredentialJsonRecord, TInputJson, TWireJson> = Readonly<{
63
+ input: TInputJson;
64
+ item: Item;
65
+ itemIndex: number;
66
+ items: Items;
67
+ ctx: NodeExecutionContext<RunnableNodeConfig<TInputJson, unknown, TWireJson> & Readonly<{ config: TConfig }>>;
68
+ }>;
69
+
57
70
  export interface DefinedNode<
58
71
  TKey extends string,
59
72
  TConfig extends CredentialJsonRecord,
60
73
  TInputJson,
61
74
  TOutputJson,
62
75
  _TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
76
+ TWireJson = TInputJson,
63
77
  > {
64
78
  readonly kind: "defined-node";
65
79
  readonly key: TKey;
66
80
  readonly title: string;
67
81
  readonly description?: string;
68
- create(config: TConfig, name?: string, id?: string): RunnableNodeConfig<TInputJson, TOutputJson>;
82
+ create(config: TConfig, name?: string, id?: string): RunnableNodeConfig<TInputJson, TOutputJson, TWireJson>;
69
83
  register(context: { registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void }): void;
70
84
  }
71
85
 
86
+ /**
87
+ * Plugin / DSL-friendly node: **one item in, one item out** per engine activation step.
88
+ * The engine applies {@link RunnableNodeConfig.mapInput} (if any) + {@link RunnableNodeConfig.inputSchema}
89
+ * before `executeOne`. Use {@link defineBatchNode} for legacy batch `run(items, …)` semantics.
90
+ */
72
91
  export interface DefineNodeOptions<
73
92
  TKey extends string,
74
93
  TConfig extends CredentialJsonRecord,
75
94
  TInputJson,
76
95
  TOutputJson,
77
96
  TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
97
+ TWireJson = TInputJson,
78
98
  > {
79
99
  readonly key: TKey;
80
100
  readonly title: string;
@@ -84,6 +104,38 @@ export interface DefineNodeOptions<
84
104
  * The Next host resolves Lucide (`lucide:…`), built-in SVGs (`builtin:…`), Simple Icons (`si:…`), and image URLs (`https:`, `data:`, `/…`).
85
105
  */
86
106
  readonly icon?: string;
107
+ /** Default values / form hints for **static** node configuration (credentials, retry, IDs), not per-item payload. */
108
+ readonly input?: Readonly<Record<keyof TConfig & string, unknown>>;
109
+ readonly configSchema?: z.ZodType<TConfig>;
110
+ readonly credentials?: TBindings;
111
+ /**
112
+ * Validates **`input`** after optional {@link mapInput} (engine also accepts `inputSchema` on the node class).
113
+ */
114
+ readonly inputSchema?: ZodType<TInputJson>;
115
+ /**
116
+ * Maps wire JSON (`item.json` from upstream) to execute input before validation.
117
+ */
118
+ readonly mapInput?: ItemInputMapper<TWireJson, TInputJson>;
119
+ executeOne(
120
+ args: DefineNodeExecuteOneArgs<TConfig, TInputJson, TWireJson>,
121
+ context: DefinedNodeRunContext<TConfig, TBindings>,
122
+ ): MaybePromise<TOutputJson>;
123
+ }
124
+
125
+ /**
126
+ * Batch-oriented defined node (legacy): receives all items at once via `run`.
127
+ */
128
+ export interface DefineBatchNodeOptions<
129
+ TKey extends string,
130
+ TConfig extends CredentialJsonRecord,
131
+ TInputJson,
132
+ TOutputJson,
133
+ TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
134
+ > {
135
+ readonly key: TKey;
136
+ readonly title: string;
137
+ readonly description?: string;
138
+ readonly icon?: string;
87
139
  readonly input?: Readonly<Record<keyof TConfig & string, unknown>>;
88
140
  readonly configSchema?: z.ZodType<TConfig>;
89
141
  readonly credentials?: TBindings;
@@ -145,7 +197,7 @@ const definedNodeCredentialRequirementFactory = {
145
197
  const definedNodeCredentialAccessorFactory = {
146
198
  create<TBindings extends DefinedNodeCredentialBindings | undefined>(
147
199
  bindings: TBindings,
148
- ctx: NodeExecutionContext<RunnableNodeConfig<any, any>>,
200
+ ctx: NodeExecutionContext<RunnableNodeConfig<any, any, any>>,
149
201
  ): DefinedNodeCredentialAccessors<TBindings> {
150
202
  if (!bindings) {
151
203
  return {} as DefinedNodeCredentialAccessors<TBindings>;
@@ -161,11 +213,100 @@ export function defineNode<
161
213
  TInputJson,
162
214
  TOutputJson,
163
215
  TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
216
+ TWireJson = TInputJson,
217
+ >(
218
+ options: DefineNodeOptions<TKey, TConfig, TInputJson, TOutputJson, TBindings, TWireJson>,
219
+ ): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TWireJson> {
220
+ const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
221
+ type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson, TWireJson> &
222
+ Readonly<{ config: TConfig }>;
223
+
224
+ const DefinedNodeRuntime = class implements ItemNode<DefinedRunnableNodeConfigShape, TInputJson, TOutputJson> {
225
+ readonly kind = "node" as const;
226
+ readonly outputPorts = ["main"] as const;
227
+ readonly inputSchema = options.inputSchema;
228
+
229
+ async executeOne(
230
+ args: Readonly<{
231
+ input: TInputJson;
232
+ item: Item;
233
+ itemIndex: number;
234
+ items: Items;
235
+ ctx: NodeExecutionContext<DefinedRunnableNodeConfigShape>;
236
+ }>,
237
+ ): Promise<TOutputJson> {
238
+ const ctx = args.ctx;
239
+ const context: DefinedNodeRunContext<TConfig, TBindings> = {
240
+ config: ctx.config.config,
241
+ credentials: definedNodeCredentialAccessorFactory.create(
242
+ options.credentials,
243
+ ctx,
244
+ ) as DefinedNodeCredentialAccessors<TBindings>,
245
+ execution: ctx as unknown as NodeExecutionContext<RunnableNodeConfig<TConfig, unknown>>,
246
+ };
247
+ const payload: DefineNodeExecuteOneArgs<TConfig, TInputJson, TWireJson> = {
248
+ input: args.input,
249
+ item: args.item,
250
+ itemIndex: args.itemIndex,
251
+ items: args.items,
252
+ ctx,
253
+ };
254
+ return await options.executeOne(payload, context);
255
+ }
256
+ };
257
+
258
+ persistedNode({ name: options.key })(DefinedNodeRuntime);
259
+
260
+ const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson, TWireJson> {
261
+ readonly kind = "node" as const;
262
+ readonly type: TypeToken<unknown> = DefinedNodeRuntime;
263
+ readonly icon = options.icon;
264
+ readonly inputSchema = options.inputSchema;
265
+ readonly mapInput = options.mapInput;
266
+
267
+ constructor(
268
+ public readonly name: string,
269
+ public readonly config: TConfig,
270
+ public readonly id?: string,
271
+ ) {}
272
+
273
+ getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
274
+ return credentialRequirements;
275
+ }
276
+ };
277
+
278
+ const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TWireJson> = {
279
+ kind: "defined-node",
280
+ key: options.key,
281
+ title: options.title,
282
+ description: options.description,
283
+ create(config, name = options.title, id) {
284
+ return new DefinedRunnableNodeConfig(name, config, id);
285
+ },
286
+ register(context) {
287
+ context.registerNode(DefinedNodeRuntime);
288
+ },
289
+ };
290
+
291
+ DefinedNodeRegistry.register(
292
+ definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined, unknown>,
293
+ );
294
+
295
+ return definition;
296
+ }
297
+
298
+ export function defineBatchNode<
299
+ TKey extends string,
300
+ TConfig extends CredentialJsonRecord,
301
+ TInputJson,
302
+ TOutputJson,
303
+ TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
164
304
  >(
165
- options: DefineNodeOptions<TKey, TConfig, TInputJson, TOutputJson, TBindings>,
166
- ): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> {
305
+ options: DefineBatchNodeOptions<TKey, TConfig, TInputJson, TOutputJson, TBindings>,
306
+ ): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TInputJson> {
167
307
  const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
168
- type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson> & Readonly<{ config: TConfig }>;
308
+ type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson, TInputJson> &
309
+ Readonly<{ config: TConfig }>;
169
310
 
170
311
  const DefinedNodeRuntime = class implements Node<DefinedRunnableNodeConfigShape> {
171
312
  readonly kind = "node" as const;
@@ -198,7 +339,7 @@ export function defineNode<
198
339
 
199
340
  persistedNode({ name: options.key })(DefinedNodeRuntime);
200
341
 
201
- const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson> {
342
+ const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson, TInputJson> {
202
343
  readonly kind = "node" as const;
203
344
  readonly type: TypeToken<unknown> = DefinedNodeRuntime;
204
345
  readonly icon = options.icon;
@@ -214,7 +355,7 @@ export function defineNode<
214
355
  }
215
356
  };
216
357
 
217
- const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> = {
358
+ const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TInputJson> = {
218
359
  kind: "defined-node",
219
360
  key: options.key,
220
361
  title: options.title,
@@ -227,7 +368,9 @@ export function defineNode<
227
368
  },
228
369
  };
229
370
 
230
- DefinedNodeRegistry.register(definition as DefinedNode<string, Record<string, unknown>, unknown, unknown>);
371
+ DefinedNodeRegistry.register(
372
+ definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined, unknown>,
373
+ );
231
374
 
232
375
  return definition;
233
376
  }
@@ -5,8 +5,10 @@ export type {
5
5
  DefinedNodeCredentialBinding,
6
6
  DefinedNodeCredentialBindings,
7
7
  DefinedNodeRunContext,
8
+ DefineBatchNodeOptions,
9
+ DefineNodeExecuteOneArgs,
8
10
  DefineNodeOptions,
9
11
  } from "./defineNode.types";
10
- export { defineNode } from "./defineNode.types";
12
+ export { defineBatchNode, defineNode } from "./defineNode.types";
11
13
  export type { DefineCredentialOptions } from "./defineCredential.types";
12
14
  export { defineCredential } from "./defineCredential.types";
@@ -13,6 +13,8 @@ import type {
13
13
  } from "./runTypes";
14
14
  import type { WorkflowActivationPolicy } from "./workflowActivationPolicy";
15
15
  import type { TriggerInstanceId, WebhookTriggerMatcher } from "./webhookTypes";
16
+ import type { ZodType } from "zod";
17
+
16
18
  import type {
17
19
  ActivationIdFactory,
18
20
  BinaryAttachment,
@@ -225,6 +227,30 @@ export interface Node<TConfig extends NodeConfigBase = NodeConfigBase> {
225
227
  execute(items: Items, ctx: NodeExecutionContext<TConfig>): Promise<NodeOutputs>;
226
228
  }
227
229
 
230
+ /**
231
+ * Single-input runnable node with per-item execution on `main` only (1→1 default).
232
+ * Engine applies {@link RunnableNodeConfig.mapInput} (if any) + `inputSchema.parse` before `executeOne`.
233
+ */
234
+ export interface ItemNode<
235
+ TConfig extends NodeConfigBase = NodeConfigBase,
236
+ TInputJson = unknown,
237
+ TOutputJson = unknown,
238
+ > {
239
+ readonly kind: "node";
240
+ readonly outputPorts: readonly ["main"];
241
+ /** When omitted, engine uses {@link RunnableNodeConfig.inputSchema} or `z.unknown()`. */
242
+ readonly inputSchema?: ZodType<TInputJson>;
243
+ executeOne(
244
+ args: Readonly<{
245
+ input: TInputJson;
246
+ item: Item;
247
+ itemIndex: number;
248
+ items: Items;
249
+ ctx: NodeExecutionContext<TConfig>;
250
+ }>,
251
+ ): Promise<TOutputJson> | TOutputJson;
252
+ }
253
+
228
254
  export interface MultiInputNode<TConfig extends NodeConfigBase = NodeConfigBase> {
229
255
  kind: "node";
230
256
  outputPorts: ReadonlyArray<OutputPortKey>;
@@ -1,3 +1,5 @@
1
+ import type { ZodType } from "zod";
2
+
1
3
  import type { TypeToken } from "../di";
2
4
  import type { CredentialRequirement } from "./credentialTypes";
3
5
  import type { RetryPolicySpec } from "./retryPolicySpec.types";
@@ -84,12 +86,69 @@ export interface NodeConfigBase {
84
86
 
85
87
  export declare const runnableNodeInputType: unique symbol;
86
88
  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;
87
91
  export declare const triggerNodeOutputType: unique symbol;
88
92
 
89
- export interface RunnableNodeConfig<TInputJson = unknown, TOutputJson = unknown> extends NodeConfigBase {
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
+ }
108
+
109
+ /**
110
+ * Arguments for optional per-item input mapping applied by the engine before Zod validation.
111
+ */
112
+ export interface ItemInputMapperArgs<TWireJson = unknown> {
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 {
90
138
  readonly kind: "node";
91
139
  readonly [runnableNodeInputType]?: TInputJson;
92
140
  readonly [runnableNodeOutputType]?: TOutputJson;
141
+ readonly [runnableNodeWireType]?: TWireJson;
142
+ /**
143
+ * Optional Zod input contract for {@link ItemNode} when not set on the node class.
144
+ * Resolution order: node instance `inputSchema`, then config `inputSchema`, then `z.unknown()`.
145
+ */
146
+ readonly inputSchema?: ZodType<TInputJson>;
147
+ /**
148
+ * Optional per-item mapper: engine applies it before validating against the node’s `inputSchema`.
149
+ * When omitted, the engine validates `item.json` directly.
150
+ */
151
+ readonly mapInput?: ItemInputMapper<TWireJson, TInputJson>;
93
152
  }
94
153
 
95
154
  export declare const triggerNodeSetupStateType: unique symbol;
@@ -103,11 +162,14 @@ export interface TriggerNodeConfig<
103
162
  readonly [triggerNodeSetupStateType]?: TSetupState;
104
163
  }
105
164
 
106
- export type RunnableNodeInputJson<TConfig extends RunnableNodeConfig<any, any>> =
107
- TConfig extends RunnableNodeConfig<infer TInputJson, any> ? TInputJson : never;
165
+ export type RunnableNodeInputJson<TConfig extends RunnableNodeConfig<any, any, any>> =
166
+ TConfig extends RunnableNodeConfig<infer TInputJson, any, any> ? TInputJson : never;
167
+
168
+ export type RunnableNodeWireJson<TConfig extends RunnableNodeConfig<any, any, any>> =
169
+ TConfig extends RunnableNodeConfig<any, any, infer TWireJson> ? TWireJson : never;
108
170
 
109
- export type RunnableNodeOutputJson<TConfig extends RunnableNodeConfig<any, any>> =
110
- TConfig extends RunnableNodeConfig<any, infer TOutputJson> ? TOutputJson : never;
171
+ export type RunnableNodeOutputJson<TConfig extends RunnableNodeConfig<any, any, any>> =
172
+ TConfig extends RunnableNodeConfig<any, infer TOutputJson, any> ? TOutputJson : never;
111
173
 
112
174
  export type TriggerNodeOutputJson<TConfig extends TriggerNodeConfig<any, any>> =
113
175
  TConfig extends TriggerNodeConfig<infer TOutputJson, any> ? TOutputJson : never;
@@ -21,6 +21,7 @@ import type {
21
21
  import { RunQueuePlanner } from "../planning/RunQueuePlanner";
22
22
 
23
23
  import { NodeEventPublisher } from "../events/NodeEventPublisher";
24
+ import type { NodeActivationRequestInputPreparer } from "./NodeActivationRequestInputPreparer";
24
25
  import { NodeExecutionSnapshotFactory } from "./NodeExecutionSnapshotFactory";
25
26
  import { NodeInputsByPortFactory } from "./NodeInputsByPortFactory";
26
27
 
@@ -51,6 +52,7 @@ export class ActivationEnqueueService {
51
52
  private readonly activationScheduler: ActivationSchedulerPort,
52
53
  private readonly workflowExecutionRepository: WorkflowExecutionRepository,
53
54
  private readonly nodeEventPublisher: NodeEventPublisher,
55
+ private readonly nodeActivationRequestInputPreparer: NodeActivationRequestInputPreparer,
54
56
  ) {}
55
57
 
56
58
  async enqueueActivation(args: ActivationEnqueueRequest): Promise<RunResult> {
@@ -62,12 +64,13 @@ export class ActivationEnqueueService {
62
64
  async enqueueActivationWithSnapshot(
63
65
  args: ActivationEnqueueRequest,
64
66
  ): Promise<{ result: RunResult; queuedSnapshot: NodeExecutionSnapshot }> {
65
- const preparedDispatch = await this.activationScheduler.prepareDispatch(args.request);
66
- const inputsByPort = NodeInputsByPortFactory.fromRequest(args.request);
67
+ const preparedRequest = await this.nodeActivationRequestInputPreparer.prepare(args.request);
68
+ const preparedDispatch = await this.activationScheduler.prepareDispatch(preparedRequest);
69
+ const inputsByPort = NodeInputsByPortFactory.fromRequest(preparedRequest);
67
70
  const itemsIn =
68
- args.request.kind === "multi"
69
- ? args.planner.sumItemsByPort(args.request.inputsByPort)
70
- : args.request.input.length;
71
+ preparedRequest.kind === "multi"
72
+ ? args.planner.sumItemsByPort(preparedRequest.inputsByPort)
73
+ : preparedRequest.input.length;
71
74
  const enqueuedAt = new Date().toISOString();
72
75
  const pending: PendingNodeExecution = {
73
76
  runId: args.runId,
@@ -0,0 +1,89 @@
1
+ import { z, ZodError } from "zod";
2
+
3
+ import type { Item, NodeActivationRequest, RunnableNodeConfig, WorkflowNodeInstanceFactory } from "../types";
4
+
5
+ import { NodeInputContractError } from "./NodeInputContractError";
6
+
7
+ /**
8
+ * Maps and validates per-item inputs for {@link ItemNode} before enqueue persistence.
9
+ */
10
+ export class NodeActivationRequestInputPreparer {
11
+ constructor(private readonly workflowNodeInstanceFactory: WorkflowNodeInstanceFactory) {}
12
+
13
+ async prepare(request: NodeActivationRequest): Promise<NodeActivationRequest> {
14
+ if (request.kind !== "single") {
15
+ return request;
16
+ }
17
+ const nodeInstance: unknown = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
18
+ if (!this.hasExecuteOne(nodeInstance)) {
19
+ return request;
20
+ }
21
+ const inputSchema = this.resolveInputSchema(nodeInstance, request.ctx.config as RunnableNodeConfig);
22
+ const config = request.ctx.config as RunnableNodeConfig;
23
+ const mappedItems: Item[] = [];
24
+ for (let i = 0; i < request.input.length; i++) {
25
+ const item = request.input[i] as Item;
26
+ try {
27
+ const mappedRaw = config.mapInput
28
+ ? await Promise.resolve(
29
+ config.mapInput({
30
+ item,
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 });
39
+ } catch (cause) {
40
+ const message = this.formatContractFailure(cause);
41
+ throw new NodeInputContractError(
42
+ `Node ${request.nodeId} activation ${request.activationId}: input contract failed: ${message}`,
43
+ request.nodeId,
44
+ request.activationId,
45
+ cause,
46
+ );
47
+ }
48
+ }
49
+ return {
50
+ ...request,
51
+ input: mappedItems,
52
+ };
53
+ }
54
+
55
+ private hasExecuteOne(nodeInstance: unknown): boolean {
56
+ return (
57
+ typeof nodeInstance === "object" &&
58
+ nodeInstance !== null &&
59
+ typeof (nodeInstance as { executeOne?: unknown }).executeOne === "function"
60
+ );
61
+ }
62
+
63
+ private resolveInputSchema(
64
+ nodeInstance: unknown,
65
+ config: RunnableNodeConfig,
66
+ ): {
67
+ parse: (data: unknown) => unknown;
68
+ } {
69
+ const fromInstance = (nodeInstance as { inputSchema?: unknown }).inputSchema;
70
+ if (fromInstance && typeof (fromInstance as { parse?: unknown }).parse === "function") {
71
+ return fromInstance as { parse: (data: unknown) => unknown };
72
+ }
73
+ const fromConfig = config.inputSchema;
74
+ if (fromConfig && typeof fromConfig.parse === "function") {
75
+ return fromConfig as { parse: (data: unknown) => unknown };
76
+ }
77
+ return z.unknown();
78
+ }
79
+
80
+ private formatContractFailure(cause: unknown): string {
81
+ if (cause instanceof ZodError) {
82
+ return cause.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
83
+ }
84
+ if (cause instanceof Error) {
85
+ return cause.message;
86
+ }
87
+ return String(cause);
88
+ }
89
+ }
@@ -1,4 +1,11 @@
1
- import type { MultiInputNode, Node, NodeActivationRequest, NodeOutputs, WorkflowNodeInstanceFactory } from "../types";
1
+ import type {
2
+ Item,
3
+ MultiInputNode,
4
+ Node,
5
+ NodeActivationRequest,
6
+ NodeOutputs,
7
+ WorkflowNodeInstanceFactory,
8
+ } from "../types";
2
9
 
3
10
  import { InProcessRetryRunner } from "./InProcessRetryRunner";
4
11
 
@@ -34,10 +41,40 @@ export class NodeExecutor {
34
41
  request: Extract<NodeActivationRequest, { kind: "single" }>,
35
42
  node: unknown,
36
43
  ): Promise<NodeOutputs> {
44
+ if (this.hasExecuteOne(node)) {
45
+ return await this.executeItemNode(request, node);
46
+ }
37
47
  const singleInputNode = node as Node;
38
48
  if (typeof (singleInputNode as { execute?: unknown }).execute !== "function") {
39
49
  throw new Error(`Node ${request.nodeId} does not support execute but received single-input activation`);
40
50
  }
41
51
  return await singleInputNode.execute(request.input, request.ctx as any);
42
52
  }
53
+
54
+ private hasExecuteOne(node: unknown): node is { executeOne: (args: unknown) => unknown | Promise<unknown> } {
55
+ return (
56
+ typeof node === "object" && node !== null && typeof (node as { executeOne?: unknown }).executeOne === "function"
57
+ );
58
+ }
59
+
60
+ private async executeItemNode(
61
+ request: Extract<NodeActivationRequest, { kind: "single" }>,
62
+ node: { executeOne: (args: unknown) => unknown | Promise<unknown> },
63
+ ): Promise<NodeOutputs> {
64
+ const out: Item[] = [];
65
+ for (let i = 0; i < request.input.length; i++) {
66
+ const item = request.input[i] as Item;
67
+ const outputJson = await Promise.resolve(
68
+ node.executeOne({
69
+ input: item.json,
70
+ item,
71
+ itemIndex: i,
72
+ items: request.input,
73
+ ctx: request.ctx,
74
+ }),
75
+ );
76
+ out.push({ ...item, json: outputJson });
77
+ }
78
+ return { main: out };
79
+ }
43
80
  }
@@ -0,0 +1,13 @@
1
+ import type { NodeActivationId, NodeId } from "../types";
2
+
3
+ export class NodeInputContractError extends Error {
4
+ constructor(
5
+ message: string,
6
+ public readonly nodeId: NodeId,
7
+ public readonly activationId: NodeActivationId,
8
+ public readonly cause?: unknown,
9
+ ) {
10
+ super(message);
11
+ this.name = "NodeInputContractError";
12
+ }
13
+ }
@@ -1,4 +1,6 @@
1
1
  export { ActivationEnqueueService } from "./ActivationEnqueueService";
2
+ export { NodeActivationRequestInputPreparer } from "./NodeActivationRequestInputPreparer";
3
+ export { NodeInputContractError } from "./NodeInputContractError";
2
4
  export { CredentialResolverFactory } from "./CredentialResolverFactory";
3
5
  export { DefaultAsyncSleeper } from "./DefaultAsyncSleeper";
4
6
  export { DefaultExecutionContextFactory } from "./DefaultExecutionContextFactory";