@codemation/core 1.0.1 → 2.0.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 (86) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/dist/{EngineRuntimeRegistration.types-kxQA5NLt.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
  3. package/dist/{EngineWorkflowRunnerService-Ba2AvBnL.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
  4. package/dist/{InMemoryRunDataFactory-Ou4tQUOS.d.cts → InMemoryRunDataFactory-Xw7v4-sj.d.cts} +31 -29
  5. package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs +47 -0
  6. package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map +1 -0
  7. package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js +41 -0
  8. package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js.map +1 -0
  9. package/dist/{RunIntentService-dteLjNiT.d.ts → RunIntentService-BE9CAkbf.d.ts} +602 -213
  10. package/dist/{RunIntentService-Dyh_dH0k.d.cts → RunIntentService-siBSjaaY.d.cts} +430 -125
  11. package/dist/bootstrap/index.cjs +5 -2
  12. package/dist/bootstrap/index.d.cts +212 -135
  13. package/dist/bootstrap/index.d.ts +4 -4
  14. package/dist/bootstrap/index.js +3 -3
  15. package/dist/{bootstrap-Cko6udwL.cjs → bootstrap-Cm5ruQxx.cjs} +253 -3
  16. package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
  17. package/dist/{bootstrap-CL68rqWg.js → bootstrap-D3r505ko.js} +236 -4
  18. package/dist/bootstrap-D3r505ko.js.map +1 -0
  19. package/dist/{index-CyfGTfU1.d.ts → index-DeLl1Tne.d.ts} +574 -242
  20. package/dist/index.cjs +328 -180
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +441 -103
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +305 -163
  25. package/dist/index.js.map +1 -1
  26. package/dist/{runtime-284ok0cm.js → runtime-BGNbRnqs.js} +764 -75
  27. package/dist/runtime-BGNbRnqs.js.map +1 -0
  28. package/dist/{runtime-B3Og-_St.cjs → runtime-DKXJwTNv.cjs} +841 -80
  29. package/dist/runtime-DKXJwTNv.cjs.map +1 -0
  30. package/dist/testing.cjs +4 -4
  31. package/dist/testing.cjs.map +1 -1
  32. package/dist/testing.d.cts +2 -2
  33. package/dist/testing.d.ts +2 -2
  34. package/dist/testing.js +3 -3
  35. package/package.json +7 -2
  36. package/src/authoring/DefinedCollectionRegistry.ts +17 -0
  37. package/src/authoring/defineCollection.types.ts +181 -0
  38. package/src/authoring/definePollingTrigger.types.ts +396 -0
  39. package/src/authoring/definePollingTriggerInternals.ts +74 -0
  40. package/src/authoring/index.ts +19 -0
  41. package/src/bootstrap/index.ts +9 -0
  42. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +5 -1
  43. package/src/contracts/assertionTypes.ts +63 -0
  44. package/src/contracts/baseTypes.ts +12 -0
  45. package/src/contracts/collectionTypes.ts +44 -0
  46. package/src/contracts/credentialTypes.ts +23 -1
  47. package/src/contracts/index.ts +4 -0
  48. package/src/contracts/runTypes.ts +27 -1
  49. package/src/contracts/runtimeTypes.ts +34 -0
  50. package/src/contracts/testTriggerTypes.ts +66 -0
  51. package/src/contracts/workflowTypes.ts +30 -7
  52. package/src/contracts.ts +59 -0
  53. package/src/events/runEvents.ts +49 -0
  54. package/src/execution/ChildExecutionScopeFactory.ts +4 -7
  55. package/src/execution/DefaultExecutionContextFactory.ts +6 -0
  56. package/src/execution/NodeInstanceFactory.ts +13 -1
  57. package/src/execution/NodeInstantiationError.ts +16 -0
  58. package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
  59. package/src/execution/index.ts +1 -0
  60. package/src/index.ts +7 -0
  61. package/src/orchestration/AbortControllerFactory.ts +9 -0
  62. package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
  63. package/src/orchestration/RunContinuationService.ts +3 -0
  64. package/src/orchestration/RunStartService.ts +114 -2
  65. package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
  66. package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
  67. package/src/orchestration/TriggerRuntimeService.ts +34 -7
  68. package/src/orchestration/index.ts +9 -0
  69. package/src/runtime/EngineFactory.ts +11 -0
  70. package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
  71. package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
  72. package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
  73. package/src/triggers/polling/index.ts +5 -0
  74. package/src/types/index.ts +12 -9
  75. package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
  76. package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
  77. package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
  78. package/src/workflow/index.ts +2 -0
  79. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
  80. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
  81. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
  82. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
  83. package/dist/bootstrap-CL68rqWg.js.map +0 -1
  84. package/dist/bootstrap-Cko6udwL.cjs.map +0 -1
  85. package/dist/runtime-284ok0cm.js.map +0 -1
  86. package/dist/runtime-B3Og-_St.cjs.map +0 -1
@@ -0,0 +1,396 @@
1
+ /**
2
+ * definePollingTrigger — declarative helper for authoring polling triggers.
3
+ *
4
+ * Mirrors the ergonomics of `defineNode` / `defineRestNode` / `defineCredential`.
5
+ * Plugin authors supply a `poll` function plus metadata; the helper synthesises the
6
+ * two internal classes (`DefinedPollingTriggerRuntime` + `DefinedPollingTriggerConfig`)
7
+ * that the engine's trigger machinery requires. Internal classes, DI annotations, and
8
+ * `PollingTriggerRuntime` wiring are hidden from the plugin-author surface entirely.
9
+ */
10
+ import type {
11
+ Items,
12
+ JsonValue,
13
+ NodeExecutionContext,
14
+ NodeOutputs,
15
+ TestableTriggerNode,
16
+ TriggerNodeConfig,
17
+ TriggerSetupContext,
18
+ TriggerTestItemsContext,
19
+ TypeToken,
20
+ } from "..";
21
+ import type { CredentialJsonRecord, CredentialRequirement } from "../contracts/credentialTypes";
22
+ import type { DefinedNodeCredentialAccessors, DefinedNodeCredentialBindings } from "./defineNode.types";
23
+ import { node as persistedNode } from "../runtime-types/runtimeTypeDecorators.types";
24
+ import {
25
+ definedNodeCredentialRequirementFactory,
26
+ definedNodeCredentialAccessorFactory,
27
+ } from "./definePollingTriggerInternals";
28
+ import type { ZodType } from "zod";
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Public types
32
+ // ---------------------------------------------------------------------------
33
+
34
+ type MaybePromise<TValue> = TValue | Promise<TValue>;
35
+
36
+ /**
37
+ * Context passed into the `poll` callback on each tick.
38
+ */
39
+ export interface DefinePollingTriggerPollContext<
40
+ TConfig extends CredentialJsonRecord,
41
+ TState extends JsonValue | undefined,
42
+ TBindings extends DefinedNodeCredentialBindings | undefined,
43
+ > {
44
+ readonly config: TConfig;
45
+ readonly state: TState;
46
+ readonly credentials: DefinedNodeCredentialAccessors<TBindings>;
47
+ }
48
+
49
+ /**
50
+ * What `poll` must return each tick.
51
+ */
52
+ export interface DefinePollingTriggerPollResult<TItemJson, TState extends JsonValue | undefined> {
53
+ /**
54
+ * New items to emit. Each item may carry an optional `dedupKey`; duplicate keys are
55
+ * filtered out against a rolling dedup window (managed internally by the runtime).
56
+ * Items without a `dedupKey` are always emitted.
57
+ */
58
+ readonly items: ReadonlyArray<{ json: TItemJson; dedupKey?: string }>;
59
+ /** Persisted as the trigger's setup state for the next tick. */
60
+ readonly nextState: TState;
61
+ }
62
+
63
+ /**
64
+ * Context passed into the `execute` callback for post-emit enrichment (e.g. fetching
65
+ * attachment bytes). Mirrors `NodeExecutionContext` so plugin authors use familiar patterns.
66
+ */
67
+ export type DefinePollingTriggerExecuteContext<TConfig extends TriggerNodeConfig<any, any>> =
68
+ NodeExecutionContext<TConfig>;
69
+
70
+ /**
71
+ * Context passed into the `testItems` callback.
72
+ */
73
+ export type DefinePollingTriggerTestItemsContext<TConfig extends TriggerNodeConfig<any, any>> =
74
+ TriggerTestItemsContext<TConfig>;
75
+
76
+ /**
77
+ * Options accepted by `definePollingTrigger`.
78
+ */
79
+ export interface DefinePollingTriggerOptions<
80
+ TKey extends string,
81
+ TConfig extends CredentialJsonRecord,
82
+ TItemJson,
83
+ TState extends JsonValue | undefined,
84
+ TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
85
+ > {
86
+ /**
87
+ * Unique node-token-id-style key, e.g. `"msgraph-mail.on-new-mail"`.
88
+ * Used as the persisted runtime type name — must be stable across deployments.
89
+ */
90
+ readonly key: TKey;
91
+ readonly title: string;
92
+ readonly description?: string;
93
+ /** Canvas icon (same contract as `NodeConfigBase.icon`). */
94
+ readonly icon?: string;
95
+ /**
96
+ * Zod schema for the trigger's user-facing configuration.
97
+ * When provided, the returned `create()` method is typed against the inferred config type.
98
+ */
99
+ readonly configSchema?: ZodType<TConfig>;
100
+ /** Credential bindings keyed by slot (same format as `defineNode`). */
101
+ readonly credentials?: TBindings;
102
+ /**
103
+ * Called once when the trigger arms (or re-arms after a server restart) to provide the
104
+ * initial value for `state` when no persisted state exists.
105
+ */
106
+ initialState?(): TState;
107
+ /**
108
+ * Polling interval in milliseconds. The runtime enforces a minimum of 25 ms.
109
+ * @default 60_000
110
+ */
111
+ readonly pollIntervalMs?: number;
112
+ /**
113
+ * The per-tick poll logic. Called by the runtime each interval.
114
+ * Must return new items plus the next persisted state.
115
+ */
116
+ poll(
117
+ pollCtx: DefinePollingTriggerPollContext<TConfig, TState, TBindings>,
118
+ ): MaybePromise<DefinePollingTriggerPollResult<TItemJson, TState>>;
119
+ /**
120
+ * Optional post-emit enrichment step (runs in the normal node-execute phase after the
121
+ * trigger fires and the workflow run starts). Use for expensive per-item work such as
122
+ * fetching attachment bytes via `ctx.binary.attach`. When omitted, the trigger passes
123
+ * items through unchanged.
124
+ */
125
+ execute?(
126
+ items: Items<TItemJson>,
127
+ ctx: NodeExecutionContext<DefinedPollingTriggerConfig<TConfig, TItemJson>>,
128
+ ): MaybePromise<NodeOutputs>;
129
+ /**
130
+ * Optional implementation for the workflow UI's "Test" button. Should return a small
131
+ * sample of current items without consulting or mutating polling state.
132
+ */
133
+ testItems?(
134
+ ctx: TriggerTestItemsContext<DefinedPollingTriggerConfig<TConfig, TItemJson>>,
135
+ ): MaybePromise<Items<TItemJson>>;
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // DefinedPollingTrigger (returned object)
140
+ // ---------------------------------------------------------------------------
141
+
142
+ /**
143
+ * The object returned by `definePollingTrigger`. Register it via
144
+ * `definePlugin({ nodes: [myTrigger] })` or call `.register(ctx)` directly.
145
+ *
146
+ * `poll` is also directly callable for unit-testing — no runtime needed.
147
+ */
148
+ export interface DefinedPollingTrigger<
149
+ TKey extends string,
150
+ TConfig extends CredentialJsonRecord,
151
+ TItemJson,
152
+ TState extends JsonValue | undefined,
153
+ TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
154
+ > {
155
+ readonly kind: "defined-polling-trigger";
156
+ readonly key: TKey;
157
+ readonly title: string;
158
+ readonly description?: string;
159
+ /**
160
+ * Create the trigger config for use in workflow definitions.
161
+ * @param cfg - User-facing trigger configuration.
162
+ * @param name - Display name (defaults to `title`).
163
+ * @param id - Optional stable node id.
164
+ */
165
+ create(cfg: TConfig, name?: string, id?: string): DefinedPollingTriggerConfig<TConfig, TItemJson>;
166
+ /**
167
+ * Test seam: call `poll` directly without starting the runtime.
168
+ * Returns `{ items, nextState }` just like the real runtime receives.
169
+ */
170
+ poll(
171
+ pollCtx: Omit<DefinePollingTriggerPollContext<TConfig, TState, TBindings>, "credentials"> & {
172
+ credentials?: DefinedNodeCredentialAccessors<TBindings>;
173
+ },
174
+ ): MaybePromise<DefinePollingTriggerPollResult<TItemJson, TState>>;
175
+ /** Registers the synthesised runtime class with the plugin container. */
176
+ register(context: { registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void }): void;
177
+ }
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // DefinedPollingTriggerConfig (TriggerNodeConfig shape for the engine)
181
+ // ---------------------------------------------------------------------------
182
+
183
+ /**
184
+ * TriggerNodeConfig produced by `DefinedPollingTrigger.create(...)`.
185
+ * Holds user configuration and credential requirements for the engine.
186
+ * The setup state type is opaque `JsonValue | undefined` — the runtime
187
+ * uses an internal wrapped shape that plugin authors never see.
188
+ */
189
+ export class DefinedPollingTriggerConfig<TConfig extends CredentialJsonRecord, TItemJson> implements TriggerNodeConfig<
190
+ TItemJson,
191
+ JsonValue | undefined
192
+ > {
193
+ readonly kind = "trigger" as const;
194
+ readonly type: TypeToken<unknown>;
195
+ readonly icon: string | undefined;
196
+
197
+ constructor(
198
+ public readonly name: string,
199
+ public readonly cfg: TConfig,
200
+ typeToken: TypeToken<unknown>,
201
+ icon: string | undefined,
202
+ private readonly credentialRequirements: ReadonlyArray<CredentialRequirement>,
203
+ public readonly id?: string,
204
+ ) {
205
+ this.type = typeToken;
206
+ this.icon = icon;
207
+ }
208
+
209
+ getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
210
+ return this.credentialRequirements;
211
+ }
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Internal wrapped state helpers
216
+ // ---------------------------------------------------------------------------
217
+
218
+ /** Opaque shape stored in the trigger setup state repository. @internal */
219
+ interface InternalWrappedState {
220
+ readonly userState: JsonValue | undefined;
221
+ readonly seenKeys: ReadonlyArray<string>;
222
+ }
223
+
224
+ function isWrappedState(value: unknown): value is InternalWrappedState {
225
+ return (
226
+ value !== null &&
227
+ typeof value === "object" &&
228
+ "seenKeys" in (value as Record<string, unknown>) &&
229
+ Array.isArray((value as InternalWrappedState).seenKeys)
230
+ );
231
+ }
232
+
233
+ // ---------------------------------------------------------------------------
234
+ // Implementation factory
235
+ // ---------------------------------------------------------------------------
236
+
237
+ /**
238
+ * Declarative helper for authoring polling triggers.
239
+ *
240
+ * ```ts
241
+ * export const onNewMail = definePollingTrigger({
242
+ * key: "my-plugin.on-new-mail",
243
+ * title: "On new mail",
244
+ * configSchema: z.object({ folder: z.string() }),
245
+ * credentials: { auth: myOAuthCredentialType },
246
+ * initialState: () => ({ lastSeenId: undefined }),
247
+ * pollIntervalMs: 60_000,
248
+ * async poll({ config, state, credentials }) {
249
+ * const session = await credentials.auth();
250
+ * const messages = await fetchMessages(session, config.folder, state.lastSeenId);
251
+ * return {
252
+ * items: messages.map(m => ({ json: m, dedupKey: m.id })),
253
+ * nextState: { lastSeenId: messages[0]?.id ?? state.lastSeenId },
254
+ * };
255
+ * },
256
+ * });
257
+ * ```
258
+ */
259
+ export function definePollingTrigger<
260
+ TKey extends string,
261
+ TConfig extends CredentialJsonRecord,
262
+ TItemJson,
263
+ TState extends JsonValue | undefined,
264
+ TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
265
+ >(
266
+ options: DefinePollingTriggerOptions<TKey, TConfig, TItemJson, TState, TBindings>,
267
+ ): DefinedPollingTrigger<TKey, TConfig, TItemJson, TState, TBindings> {
268
+ const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
269
+ const DEFAULT_INTERVAL_MS = 60_000;
270
+
271
+ type TConfig_ = DefinedPollingTriggerConfig<TConfig, TItemJson>;
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // Synthesised runtime class (implements TestableTriggerNode)
275
+ // ---------------------------------------------------------------------------
276
+
277
+ const DefinedPollingTriggerRuntime = class implements TestableTriggerNode<TConfig_> {
278
+ readonly kind = "trigger" as const;
279
+ readonly outputPorts = ["main"] as const;
280
+
281
+ async setup(ctx: TriggerSetupContext<TConfig_, JsonValue | undefined>): Promise<JsonValue | undefined> {
282
+ const cfg = ctx.config.cfg;
283
+ const intervalMs =
284
+ (cfg as Partial<{ pollIntervalMs: number }>).pollIntervalMs ?? options.pollIntervalMs ?? DEFAULT_INTERVAL_MS;
285
+
286
+ // Unwrap previously persisted state, or create the initial wrapped state.
287
+ const persisted = ctx.previousState;
288
+ const existingWrapped: InternalWrappedState | undefined = isWrappedState(persisted) ? persisted : undefined;
289
+ const seedWrapped: InternalWrappedState = existingWrapped ?? {
290
+ userState: options.initialState ? options.initialState() : undefined,
291
+ seenKeys: [],
292
+ };
293
+
294
+ const result = await ctx.polling.start<InternalWrappedState, TItemJson>({
295
+ intervalMs,
296
+ seedState: seedWrapped,
297
+ runCycle: async ({ previousState }) => {
298
+ const wrapped: InternalWrappedState = previousState ?? seedWrapped;
299
+ const seenSet = new Set(wrapped.seenKeys);
300
+
301
+ const credentialAccessors = definedNodeCredentialAccessorFactory.create(
302
+ options.credentials,
303
+ ctx,
304
+ ) as DefinedNodeCredentialAccessors<TBindings>;
305
+
306
+ const pollResult = await options.poll({
307
+ config: cfg,
308
+ state: wrapped.userState as TState,
309
+ credentials: credentialAccessors,
310
+ });
311
+
312
+ // Dedup: filter items whose dedupKey is already seen
313
+ const newItems: Array<{ json: TItemJson }> = [];
314
+ const newKeys: string[] = [];
315
+ for (const item of pollResult.items) {
316
+ if (item.dedupKey !== undefined) {
317
+ if (seenSet.has(item.dedupKey)) {
318
+ continue;
319
+ }
320
+ newKeys.push(item.dedupKey);
321
+ }
322
+ newItems.push({ json: item.json });
323
+ }
324
+
325
+ // Merge keys, cap the window at 2000 to bound state size
326
+ const allKeys = [...wrapped.seenKeys, ...newKeys];
327
+ const cappedKeys = allKeys.length > 2000 ? allKeys.slice(allKeys.length - 2000) : allKeys;
328
+
329
+ const nextWrapped: InternalWrappedState = {
330
+ userState: pollResult.nextState,
331
+ seenKeys: cappedKeys,
332
+ };
333
+
334
+ return {
335
+ items: newItems as Items<TItemJson>,
336
+ nextState: nextWrapped,
337
+ };
338
+ },
339
+ });
340
+
341
+ return result as JsonValue | undefined;
342
+ }
343
+
344
+ async execute(items: Items<TItemJson>, ctx: NodeExecutionContext<TConfig_>): Promise<NodeOutputs> {
345
+ if (options.execute) {
346
+ return await options.execute(items, ctx);
347
+ }
348
+ return { main: items };
349
+ }
350
+
351
+ async getTestItems(ctx: TriggerTestItemsContext<TConfig_>): Promise<Items> {
352
+ if (options.testItems) {
353
+ return await options.testItems(ctx);
354
+ }
355
+ return [];
356
+ }
357
+ };
358
+
359
+ persistedNode({ name: options.key })(DefinedPollingTriggerRuntime);
360
+
361
+ // ---------------------------------------------------------------------------
362
+ // Returned definition object
363
+ // ---------------------------------------------------------------------------
364
+
365
+ const definition: DefinedPollingTrigger<TKey, TConfig, TItemJson, TState, TBindings> = {
366
+ kind: "defined-polling-trigger",
367
+ key: options.key,
368
+ title: options.title,
369
+ description: options.description,
370
+
371
+ create(cfg: TConfig, name = options.title, id?: string): DefinedPollingTriggerConfig<TConfig, TItemJson> {
372
+ return new DefinedPollingTriggerConfig<TConfig, TItemJson>(
373
+ name,
374
+ cfg,
375
+ DefinedPollingTriggerRuntime,
376
+ options.icon,
377
+ credentialRequirements,
378
+ id,
379
+ );
380
+ },
381
+
382
+ poll(pollCtx) {
383
+ return options.poll({
384
+ config: pollCtx.config,
385
+ state: pollCtx.state,
386
+ credentials: (pollCtx.credentials ?? {}) as DefinedNodeCredentialAccessors<TBindings>,
387
+ });
388
+ },
389
+
390
+ register(context) {
391
+ context.registerNode(DefinedPollingTriggerRuntime);
392
+ },
393
+ };
394
+
395
+ return definition;
396
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Shared internal helpers for defineNode and definePollingTrigger.
3
+ * Not part of the public API — import only from authoring helpers.
4
+ */
5
+ import type { AnyCredentialType, CredentialRequirement, CredentialTypeId } from "../contracts/credentialTypes";
6
+ import type { NodeExecutionContext } from "../contracts/runtimeTypes";
7
+ import type { RunnableNodeConfig } from "../contracts/workflowTypes";
8
+ import type { DefinedNodeCredentialAccessors, DefinedNodeCredentialBindings } from "./defineNode.types";
9
+
10
+ type ResolvableCredentialType = AnyCredentialType | CredentialTypeId;
11
+
12
+ export const definedNodeCredentialRequirementFactory = {
13
+ create(bindings: DefinedNodeCredentialBindings | undefined): ReadonlyArray<CredentialRequirement> {
14
+ if (!bindings) {
15
+ return [];
16
+ }
17
+ return Object.entries(bindings).map(([slotKey, binding]) => {
18
+ if (typeof binding === "string" || this.isCredentialType(binding)) {
19
+ return {
20
+ slotKey,
21
+ label: this.humanize(slotKey),
22
+ acceptedTypes: [this.resolveTypeId(binding)],
23
+ };
24
+ }
25
+
26
+ const types = Array.isArray(binding.type) ? binding.type : [binding.type];
27
+ return {
28
+ slotKey,
29
+ label: binding.label ?? this.humanize(slotKey),
30
+ acceptedTypes: types.map((entry) => this.resolveTypeId(entry)),
31
+ optional: binding.optional,
32
+ helpText: binding.helpText,
33
+ helpUrl: binding.helpUrl,
34
+ };
35
+ });
36
+ },
37
+
38
+ isCredentialType(value: unknown): value is AnyCredentialType {
39
+ return (
40
+ Boolean(value) &&
41
+ typeof value === "object" &&
42
+ "definition" in (value as Record<string, unknown>) &&
43
+ typeof (value as AnyCredentialType).definition?.typeId === "string"
44
+ );
45
+ },
46
+
47
+ resolveTypeId(type: ResolvableCredentialType): string {
48
+ return typeof type === "string" ? type : type.definition.typeId;
49
+ },
50
+
51
+ humanize(key: string): string {
52
+ return key
53
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
54
+ .replace(/[-_.]+/g, " ")
55
+ .replace(/\s+/g, " ")
56
+ .trim()
57
+ .replace(/^./, (character) => character.toUpperCase());
58
+ },
59
+ } as const;
60
+
61
+ export const definedNodeCredentialAccessorFactory = {
62
+ create<TBindings extends DefinedNodeCredentialBindings | undefined>(
63
+ bindings: TBindings,
64
+ ctx:
65
+ | NodeExecutionContext<RunnableNodeConfig<any, any>>
66
+ | { getCredential<TSession = unknown>(slotKey: string): Promise<TSession> },
67
+ ): DefinedNodeCredentialAccessors<TBindings> {
68
+ if (!bindings) {
69
+ return {} as DefinedNodeCredentialAccessors<TBindings>;
70
+ }
71
+ const entries = Object.keys(bindings).map((slotKey) => [slotKey, () => ctx.getCredential(slotKey)] as const);
72
+ return Object.fromEntries(entries) as DefinedNodeCredentialAccessors<TBindings>;
73
+ },
74
+ } as const;
@@ -14,3 +14,22 @@ export { defineBatchNode, defineNode } from "./defineNode.types";
14
14
  export type { DefineCredentialOptions } from "./defineCredential.types";
15
15
  export { defineCredential } from "./defineCredential.types";
16
16
  export { callableTool } from "./callableTool.types";
17
+ export { DefinedCollectionRegistry } from "./DefinedCollectionRegistry";
18
+ export type {
19
+ DefinedCollection,
20
+ CollectionDefinition,
21
+ CollectionFieldDefinition,
22
+ CollectionIndexDefinition,
23
+ CollectionColumnBuilder,
24
+ DefineCollectionOptions,
25
+ } from "./defineCollection.types";
26
+ export { defineCollection, c } from "./defineCollection.types";
27
+ export type {
28
+ DefinePollingTriggerOptions,
29
+ DefinePollingTriggerPollContext,
30
+ DefinePollingTriggerPollResult,
31
+ DefinePollingTriggerExecuteContext,
32
+ DefinePollingTriggerTestItemsContext,
33
+ DefinedPollingTrigger,
34
+ } from "./definePollingTrigger.types";
35
+ export { definePollingTrigger, DefinedPollingTriggerConfig } from "./definePollingTrigger.types";
@@ -1,5 +1,14 @@
1
1
  /** Composition-root engine graph and advanced runtime wiring. Not part of the main `@codemation/core` barrel. */
2
2
  export { Engine } from "../orchestration/Engine";
3
+ export {
4
+ AbortControllerFactory,
5
+ TestSuiteOrchestrator,
6
+ TestSuiteRunIdFactory,
7
+ type RunTestSuiteArgs,
8
+ type TestSuiteCaseOutcome,
9
+ type TestSuiteOrchestratorEngine,
10
+ type TestSuiteRunResult,
11
+ } from "../orchestration";
3
12
  export { EngineFactory, type EngineCompositionDeps } from "../runtime/EngineFactory";
4
13
  export {
5
14
  EngineRuntimeRegistrar,
@@ -52,7 +52,11 @@ export class EngineRuntimeRegistrar {
52
52
  container.registerSingleton(RunnableOutputBehaviorResolver, RunnableOutputBehaviorResolver);
53
53
  }
54
54
  if (!container.isRegistered(ChildExecutionScopeFactory, true)) {
55
- container.registerSingleton(ChildExecutionScopeFactory, ChildExecutionScopeFactory);
55
+ container.register(ChildExecutionScopeFactory, {
56
+ useFactory: instanceCachingFactory((dependencyContainer) => {
57
+ return new ChildExecutionScopeFactory(dependencyContainer.resolve(CoreTokens.ActivationIdFactory));
58
+ }),
59
+ });
56
60
  }
57
61
  container.registerSingleton(EngineExecutionLimitsPolicyFactory, EngineExecutionLimitsPolicyFactory);
58
62
  container.registerSingleton(NodeInstanceFactoryFactory, NodeInstanceFactoryFactory);
@@ -0,0 +1,63 @@
1
+ import type { JsonValue, NodeId } from "./workflowTypes";
2
+
3
+ /**
4
+ * One assertion emitted by an assertion-emitting node (a node whose config sets
5
+ * `emitsAssertions: true`). Each emitted item on `main` carries one of these as `item.json`.
6
+ *
7
+ * Pass/fail is derived from `score >= (passThreshold ?? 0.5)` — see {@link deriveAssertionPassed}.
8
+ * The `errored` marker is for cases where the assertion code itself threw (distinct from
9
+ * "the assertion was evaluated and the score was low") and is treated as a hard fail in rollups
10
+ * regardless of `score`.
11
+ */
12
+ export interface AssertionResult {
13
+ readonly name: string;
14
+ /** 0..1 score. Source of truth for pass/fail (compared against `passThreshold`). */
15
+ readonly score: number;
16
+ /** 0..1 threshold for "passed". When omitted, consumers default to 0.5. */
17
+ readonly passThreshold?: number;
18
+ /** True when evaluating the assertion threw — treated as fail regardless of `score`. */
19
+ readonly errored?: true;
20
+ /** What the assertion expected. Free-form JSON; UIs render with a JSON viewer. */
21
+ readonly expected?: JsonValue;
22
+ /** What the workflow actually produced. */
23
+ readonly actual?: JsonValue;
24
+ /** Short human-readable explanation, especially for fails / errors. */
25
+ readonly message?: string;
26
+ /** Bag of supplemental fields (e.g. judge prompt, judge raw response, comparison method). */
27
+ readonly details?: Readonly<Record<string, JsonValue>>;
28
+ }
29
+
30
+ /**
31
+ * Default {@link AssertionResult.passThreshold} when authors omit it. Boolean-style assertions
32
+ * (assertEqual / contains / etc.) emit `score: 1` or `score: 0` so this default works for them;
33
+ * AI-judge assertions are expected to set their own threshold.
34
+ */
35
+ export const DEFAULT_ASSERTION_PASS_THRESHOLD = 0.5;
36
+
37
+ /**
38
+ * Derive whether an assertion result is considered "passing" using the score-based contract:
39
+ * `errored` always fails, otherwise `score >= (passThreshold ?? 0.5)`. This is the canonical
40
+ * derivation — UI and rollup code should call it rather than inlining the comparison so future
41
+ * tweaks (e.g. NaN handling) land in one place.
42
+ */
43
+ export function deriveAssertionPassed(result: {
44
+ readonly score: number;
45
+ readonly passThreshold?: number;
46
+ readonly errored?: true;
47
+ }): boolean {
48
+ if (result.errored === true) return false;
49
+ const threshold = result.passThreshold ?? DEFAULT_ASSERTION_PASS_THRESHOLD;
50
+ return result.score >= threshold;
51
+ }
52
+
53
+ /**
54
+ * Provenance for a persisted {@link AssertionResult}: which node produced it and where in the
55
+ * per-item iteration tree it landed. Filled in by the host-side persister, not the node itself.
56
+ */
57
+ export interface AssertionResultProvenance {
58
+ readonly nodeId: NodeId;
59
+ /** Per-item iteration id when the emitting node ran inside a per-item loop. */
60
+ readonly iterationId?: string;
61
+ /** Item index (0-based) within the activation that produced this assertion. */
62
+ readonly itemIndex?: number;
63
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Minimal base types that have no dependencies on other contracts.
3
+ * Used by credentialTypes, workflowTypes, and other contract layers
4
+ * to avoid circular dependencies.
5
+ */
6
+
7
+ export type WorkflowId = string;
8
+ export type NodeId = string;
9
+ export type OutputPortKey = string;
10
+ export type InputPortKey = string;
11
+ export type PersistedTokenId = string;
12
+ export type NodeConnectionName = string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Represents a typed store for a single collection.
3
+ * All rows include auto-managed id, created_at, and updated_at fields.
4
+ */
5
+ export interface CollectionStore<TRow extends Record<string, unknown> = Record<string, unknown>> {
6
+ /**
7
+ * Insert a new row. id, created_at, and updated_at are auto-populated.
8
+ */
9
+ insert(row: TRow): Promise<TRow & { id: string; created_at: Date; updated_at: Date }>;
10
+
11
+ /**
12
+ * Get a single row by id.
13
+ */
14
+ get(id: string): Promise<(TRow & { id: string; created_at: Date; updated_at: Date }) | null>;
15
+
16
+ /**
17
+ * Find a single row matching the provided filter.
18
+ */
19
+ findOne(filter: Partial<TRow>): Promise<(TRow & { id: string; created_at: Date; updated_at: Date }) | null>;
20
+
21
+ /**
22
+ * List rows with optional pagination and filtering.
23
+ */
24
+ list(opts?: {
25
+ limit?: number;
26
+ offset?: number;
27
+ where?: Partial<TRow>;
28
+ }): Promise<{ rows: ReadonlyArray<TRow & { id: string; created_at: Date; updated_at: Date }>; total: number }>;
29
+
30
+ /**
31
+ * Update a row by id with partial data.
32
+ */
33
+ update(id: string, patch: Partial<TRow>): Promise<TRow & { id: string; created_at: Date; updated_at: Date }>;
34
+
35
+ /**
36
+ * Delete a row by id. Hard delete only (no soft delete).
37
+ */
38
+ delete(id: string): Promise<{ deleted: boolean }>;
39
+ }
40
+
41
+ /**
42
+ * Runtime collections context: keyed by collection name.
43
+ */
44
+ export type CollectionsContext = Readonly<Record<string, CollectionStore>>;
@@ -1,4 +1,4 @@
1
- import type { NodeId, WorkflowId } from "./workflowTypes";
1
+ import type { NodeId, WorkflowId } from "./baseTypes";
2
2
 
3
3
  export type CredentialTypeId = string;
4
4
  export type CredentialInstanceId = string;
@@ -91,6 +91,28 @@ export type CredentialOAuth2AuthDefinition = Readonly<
91
91
  clientIdFieldKey?: string;
92
92
  clientSecretFieldKey?: string;
93
93
  }
94
+ | {
95
+ kind: "oauth2";
96
+ /**
97
+ * Free-form provider identifier for telemetry, DB rows, and Better Auth provider naming.
98
+ * Not used for any registry lookup — URLs come from {@link authorizeUrl} / {@link tokenUrl}.
99
+ */
100
+ providerId: string;
101
+ /**
102
+ * Authorization endpoint. May contain `{publicFieldKey}` placeholders that the runtime
103
+ * substitutes from the credential's resolved public config (URL-encoded).
104
+ * Example: `https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize`
105
+ */
106
+ authorizeUrl: string;
107
+ /** Token endpoint. Same templating rules as {@link authorizeUrl}. */
108
+ tokenUrl: string;
109
+ /** Optional userinfo endpoint. Same templating rules as {@link authorizeUrl}. */
110
+ userInfoUrl?: string;
111
+ scopes: ReadonlyArray<string>;
112
+ scopesFromPublicConfig?: CredentialOAuth2ScopesFromPublicConfig;
113
+ clientIdFieldKey?: string;
114
+ clientSecretFieldKey?: string;
115
+ }
94
116
  >;
95
117
 
96
118
  export type CredentialAuthDefinition = CredentialOAuth2AuthDefinition;