@codemation/core 0.8.0 → 0.10.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 (103) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/dist/{EngineRuntimeRegistration.types-BP6tsaNP.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
  3. package/dist/{EngineWorkflowRunnerService-DzOCa1BW.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
  4. package/dist/{InMemoryRunDataFactory-1iz7_SnO.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-BqhmdoA1.d.ts → RunIntentService-BE9CAkbf.d.ts} +966 -471
  10. package/dist/{RunIntentService-S-1lW-gS.d.cts → RunIntentService-siBSjaaY.d.cts} +859 -493
  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-BaN6hZ5I.cjs → bootstrap-Cm5ruQxx.cjs} +263 -12
  16. package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
  17. package/dist/bootstrap-D3r505ko.js +454 -0
  18. package/dist/bootstrap-D3r505ko.js.map +1 -0
  19. package/dist/{index-CVs9rVhl.d.ts → index-DeLl1Tne.d.ts} +632 -230
  20. package/dist/index.cjs +323 -176
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +544 -91
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +299 -166
  25. package/dist/index.js.map +1 -1
  26. package/dist/{runtime-DUW6tIJ1.js → runtime-BGNbRnqs.js} +934 -75
  27. package/dist/runtime-BGNbRnqs.js.map +1 -0
  28. package/dist/{runtime-Dvo2ru5A.cjs → runtime-DKXJwTNv.cjs} +1028 -73
  29. package/dist/runtime-DKXJwTNv.cjs.map +1 -0
  30. package/dist/testing.cjs +5 -5
  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 +4 -4
  35. package/dist/testing.js.map +1 -1
  36. package/package.json +7 -2
  37. package/src/ai/AiHost.ts +42 -14
  38. package/src/authoring/DefinedCollectionRegistry.ts +17 -0
  39. package/src/authoring/defineCollection.types.ts +181 -0
  40. package/src/authoring/definePollingTrigger.types.ts +396 -0
  41. package/src/authoring/definePollingTriggerInternals.ts +74 -0
  42. package/src/authoring/index.ts +19 -0
  43. package/src/bootstrap/index.ts +9 -0
  44. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +21 -14
  45. package/src/browser.ts +1 -0
  46. package/src/contracts/CodemationTelemetryAttributeNames.ts +6 -0
  47. package/src/contracts/NoOpNodeExecutionTelemetry.ts +2 -11
  48. package/src/contracts/NoOpTelemetrySpanScope.ts +46 -10
  49. package/src/contracts/assertionTypes.ts +63 -0
  50. package/src/contracts/baseTypes.ts +12 -0
  51. package/src/contracts/collectionTypes.ts +44 -0
  52. package/src/contracts/credentialTypes.ts +23 -1
  53. package/src/contracts/executionPersistenceContracts.ts +30 -0
  54. package/src/contracts/index.ts +4 -0
  55. package/src/contracts/runTypes.ts +37 -1
  56. package/src/contracts/runtimeTypes.ts +42 -0
  57. package/src/contracts/telemetryTypes.ts +8 -0
  58. package/src/contracts/testTriggerTypes.ts +66 -0
  59. package/src/contracts/workflowTypes.ts +36 -7
  60. package/src/contracts.ts +59 -0
  61. package/src/events/ConnectionInvocationEventPublisher.ts +46 -0
  62. package/src/events/index.ts +1 -0
  63. package/src/events/runEvents.ts +74 -0
  64. package/src/execution/ChildExecutionScopeFactory.ts +55 -0
  65. package/src/execution/DefaultExecutionContextFactory.ts +6 -0
  66. package/src/execution/ExecutionTelemetryCostTrackingDecoratorFactory.ts +18 -0
  67. package/src/execution/NodeExecutor.ts +10 -2
  68. package/src/execution/NodeInstanceFactory.ts +13 -1
  69. package/src/execution/NodeInstantiationError.ts +16 -0
  70. package/src/execution/NodeRunStateWriter.ts +7 -0
  71. package/src/execution/NodeRunStateWriterFactory.ts +7 -0
  72. package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
  73. package/src/execution/index.ts +2 -0
  74. package/src/index.ts +8 -0
  75. package/src/orchestration/AbortControllerFactory.ts +9 -0
  76. package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
  77. package/src/orchestration/RunContinuationService.ts +3 -0
  78. package/src/orchestration/RunStartService.ts +122 -3
  79. package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
  80. package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
  81. package/src/orchestration/TriggerRuntimeService.ts +34 -7
  82. package/src/orchestration/index.ts +9 -0
  83. package/src/runtime/EngineFactory.ts +12 -0
  84. package/src/testing/WorkflowTestKitNodeRegistrationContextFactory.ts +1 -3
  85. package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
  86. package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
  87. package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
  88. package/src/triggers/polling/index.ts +5 -0
  89. package/src/types/index.ts +12 -9
  90. package/src/workflow/definition/NodeIterationIdFactory.ts +26 -0
  91. package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
  92. package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
  93. package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
  94. package/src/workflow/index.ts +3 -0
  95. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
  96. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
  97. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
  98. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
  99. package/dist/bootstrap-BaN6hZ5I.cjs.map +0 -1
  100. package/dist/bootstrap-d_BMaDT4.js +0 -221
  101. package/dist/bootstrap-d_BMaDT4.js.map +0 -1
  102. package/dist/runtime-DUW6tIJ1.js.map +0 -1
  103. package/dist/runtime-Dvo2ru5A.cjs.map +0 -1
@@ -8,9 +8,7 @@ export class WorkflowTestKitNodeRegistrationContextFactory {
8
8
  create(dependencyContainer: DependencyContainer): DefinedNodeRegistrationContext {
9
9
  return {
10
10
  registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>) {
11
- dependencyContainer.register(token, {
12
- useClass: (implementation ?? token) as never,
13
- });
11
+ dependencyContainer.registerSingleton(token as never, (implementation ?? token) as never);
14
12
  },
15
13
  };
16
14
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Merges processed-ID windows for polling triggers, capping the total to avoid unbounded growth.
3
+ * Plugin code receives an instance of this class via {@link PollingTriggerHandle.dedup}.
4
+ */
5
+ export class PollingTriggerDedupWindow {
6
+ static readonly defaultCapN = 2000;
7
+
8
+ merge(
9
+ previous: ReadonlyArray<string>,
10
+ incoming: ReadonlyArray<string>,
11
+ capN: number = PollingTriggerDedupWindow.defaultCapN,
12
+ ): ReadonlyArray<string> {
13
+ const merged = new Set(previous);
14
+ for (const id of incoming) {
15
+ merged.add(id);
16
+ }
17
+ const result = [...merged];
18
+ if (result.length <= capN) {
19
+ return result;
20
+ }
21
+ return result.slice(result.length - capN);
22
+ }
23
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Minimal logger surface for the polling-trigger runtime.
3
+ * Hosts supply this via {@link EngineDeps.pollingTriggerLogger};
4
+ * when absent the runtime is silent.
5
+ */
6
+ export interface PollingTriggerLogger {
7
+ info(message: string): void;
8
+ warn(message: string): void;
9
+ error(message: string, exception?: Error): void;
10
+ debug(message: string): void;
11
+ }
12
+
13
+ export class NoOpPollingTriggerLogger implements PollingTriggerLogger {
14
+ info(): void {}
15
+ warn(): void {}
16
+ error(): void {}
17
+ debug(): void {}
18
+ }
@@ -0,0 +1,122 @@
1
+ import type { Items, TriggerInstanceId, TriggerSetupStateRepository } from "../../types";
2
+ import type { PollingTriggerLogger } from "./PollingTriggerLogger";
3
+
4
+ export interface PollingRunCycleArgs<TState> {
5
+ previousState: TState | undefined;
6
+ signal: AbortSignal;
7
+ }
8
+
9
+ export interface PollingRunCycleResult<TState, TItem> {
10
+ items: Items<TItem>;
11
+ nextState: TState;
12
+ }
13
+
14
+ export interface PollingTriggerStartArgs<TState, TItem> {
15
+ trigger: TriggerInstanceId;
16
+ intervalMs: number;
17
+ seedState?: TState;
18
+ runCycle: (cycleCtx: PollingRunCycleArgs<TState>) => Promise<PollingRunCycleResult<TState, TItem>>;
19
+ emit: (items: Items) => Promise<void>;
20
+ }
21
+
22
+ /**
23
+ * Generic polling-trigger runtime. Owns the set-interval loop, overlap guard, and persistence.
24
+ * Constructed by {@link import("../../runtime/EngineFactory").EngineFactory} and exposed to plugin
25
+ * authors via {@link import("../../contracts/runtimeTypes").TriggerSetupContext}.polling.
26
+ */
27
+ export class PollingTriggerRuntime {
28
+ private readonly activeTriggers = new Set<string>();
29
+ private readonly intervalsByTrigger = new Map<string, ReturnType<typeof setInterval>>();
30
+ private readonly busyTriggers = new Set<string>();
31
+
32
+ constructor(
33
+ private readonly triggerSetupStateRepository: TriggerSetupStateRepository,
34
+ private readonly logger: PollingTriggerLogger,
35
+ ) {}
36
+
37
+ async start<TState, TItem>(args: PollingTriggerStartArgs<TState, TItem>): Promise<TState | undefined> {
38
+ let first: TState | undefined;
39
+ try {
40
+ first = await this.runCycle(args, { seedState: args.seedState });
41
+ } catch (err: unknown) {
42
+ this.logError(`Polling trigger initial cycle failed for ${this.describe(args.trigger)}`, err);
43
+ }
44
+ this.ensureLoop(args);
45
+ return first;
46
+ }
47
+
48
+ async stop(trigger: TriggerInstanceId): Promise<void> {
49
+ const key = this.toKey(trigger);
50
+ const interval = this.intervalsByTrigger.get(key);
51
+ if (interval !== undefined) {
52
+ clearInterval(interval);
53
+ this.intervalsByTrigger.delete(key);
54
+ }
55
+ this.busyTriggers.delete(key);
56
+ this.activeTriggers.delete(key);
57
+ this.logger.debug(`Polling trigger stopped for ${this.describe(trigger)}`);
58
+ }
59
+
60
+ private ensureLoop<TState, TItem>(args: PollingTriggerStartArgs<TState, TItem>): void {
61
+ const key = this.toKey(args.trigger);
62
+ if (this.activeTriggers.has(key)) {
63
+ this.logger.debug(`Polling trigger already active for ${this.describe(args.trigger)}`);
64
+ return;
65
+ }
66
+ this.activeTriggers.add(key);
67
+ const intervalMs = Math.max(args.intervalMs, 25);
68
+ const interval = setInterval(() => {
69
+ void this.runCycle(args, { seedState: undefined }).catch((err: unknown) => {
70
+ this.logError(`Polling trigger cycle failed for ${this.describe(args.trigger)}`, err);
71
+ });
72
+ }, intervalMs);
73
+ this.intervalsByTrigger.set(key, interval);
74
+ this.logger.info(`Polling trigger started for ${this.describe(args.trigger)} (interval ${intervalMs}ms)`);
75
+ }
76
+
77
+ private async runCycle<TState, TItem>(
78
+ args: PollingTriggerStartArgs<TState, TItem>,
79
+ opts: { seedState: TState | undefined },
80
+ ): Promise<TState | undefined> {
81
+ const key = this.toKey(args.trigger);
82
+ if (this.busyTriggers.has(key)) {
83
+ this.logger.debug(`Polling trigger skipping overlapping tick for ${this.describe(args.trigger)}`);
84
+ return undefined;
85
+ }
86
+ this.busyTriggers.add(key);
87
+ try {
88
+ const loaded = await this.triggerSetupStateRepository.load(args.trigger);
89
+ const previousState = loaded !== undefined ? (loaded.state as TState | undefined) : opts.seedState;
90
+ const controller = new AbortController();
91
+ const { items, nextState } = await args.runCycle({ previousState, signal: controller.signal });
92
+ await this.triggerSetupStateRepository.save({
93
+ trigger: args.trigger,
94
+ updatedAt: new Date().toISOString(),
95
+ state: nextState as never,
96
+ });
97
+ if (items.length > 0) {
98
+ this.logger.info(`Polling trigger emitting ${items.length} item(s) for ${this.describe(args.trigger)}`);
99
+ await args.emit(items);
100
+ }
101
+ return nextState;
102
+ } finally {
103
+ this.busyTriggers.delete(key);
104
+ }
105
+ }
106
+
107
+ private toKey(trigger: TriggerInstanceId): string {
108
+ return `${trigger.workflowId}:${trigger.nodeId}`;
109
+ }
110
+
111
+ private describe(trigger: TriggerInstanceId): string {
112
+ return `${trigger.workflowId}.${trigger.nodeId}`;
113
+ }
114
+
115
+ private logError(message: string, error: unknown): void {
116
+ if (error instanceof Error) {
117
+ this.logger.error(message, error);
118
+ return;
119
+ }
120
+ this.logger.error(`${message}: ${String(error)}`);
121
+ }
122
+ }
@@ -0,0 +1,5 @@
1
+ export { PollingTriggerDedupWindow } from "./PollingTriggerDedupWindow";
2
+ export { PollingTriggerRuntime } from "./PollingTriggerRuntime";
3
+ export type { PollingTriggerLogger } from "./PollingTriggerLogger";
4
+ export { NoOpPollingTriggerLogger } from "./PollingTriggerLogger";
5
+ export type { PollingRunCycleArgs, PollingRunCycleResult, PollingTriggerStartArgs } from "./PollingTriggerRuntime";
@@ -1,22 +1,25 @@
1
+ // Re-export pure-type contracts first (available via @codemation/core/contracts subpath)
2
+ export * from "../contracts";
3
+ export type { CollectionStore, CollectionsContext } from "../contracts/collectionTypes";
4
+
5
+ // Additional runtime exports not included in contracts (factory classes, DSL builders, etc.)
1
6
  export * from "../contracts/emitPorts";
2
7
  export * from "../contracts/itemMeta";
3
- export * from "../contracts/params";
4
8
  export * from "../contracts/itemExpr";
5
- export * from "../contracts/retryPolicySpec.types";
6
9
  export * from "../contracts/NoRetryPolicy";
7
10
  export * from "../contracts/RetryPolicy";
8
11
  export * from "../contracts/ExpRetryPolicy";
9
12
  export * from "../contracts/credentialTypes";
10
- export * from "../contracts/CostCatalogContract";
11
13
  export * from "../contracts/CostTrackingTelemetryContract";
12
14
  export * from "../contracts/NoOpCostTrackingTelemetry";
13
15
  export * from "../contracts/NoOpCostTrackingTelemetryFactory";
14
- export * from "../contracts/executionPersistenceContracts";
15
- export * from "../contracts/runtimeTypes";
16
- export * from "../contracts/telemetryTypes";
16
+ export * from "../contracts/NoOpExecutionTelemetryFactory";
17
17
  export * from "../contracts/runFinishedAtFactory";
18
- export * from "../contracts/runTypes";
19
- export * from "../contracts/webhookTypes";
20
- export * from "../contracts/workflowTypes";
21
18
  export * from "../contracts/workflowActivationPolicy";
19
+ // telemetryTypes and workflowTypes also have runtime exports (No-Op telemetry classes,
20
+ // attribute-name registries, `nodeRef` factory, unique-symbol type tags) — `contracts.ts`
21
+ // already re-exports them with `export type *` for the slim subpath; re-export with the
22
+ // full `export *` here so back-compat for `@codemation/core` (root) consumers is preserved.
23
+ export * from "../contracts/telemetryTypes";
24
+ export * from "../contracts/workflowTypes";
22
25
  export * from "../workflow";
@@ -0,0 +1,26 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ import type { NodeId } from "../../types";
4
+
5
+ /**
6
+ * Unique ids for one per-item iteration of a runnable node's execute loop.
7
+ *
8
+ * Activations are per-batch (one scheduled execution of a node, possibly with N items).
9
+ * Iterations refine that to one identifier per item-index inside the batch loop, so per-item
10
+ * connection invocations and telemetry can be grouped without time-window heuristics.
11
+ */
12
+ export class NodeIterationIdFactory {
13
+ static create(): string {
14
+ return `iter_${randomUUID()}`;
15
+ }
16
+
17
+ /** Deterministic id for tests when a stable sequence is needed. */
18
+ static createForTest(seed: string, sequence: number): string {
19
+ return `iter_${seed}_${sequence}`;
20
+ }
21
+
22
+ /** Deterministic id derived from a connection node id (for sub-agent / tool-call scopes). */
23
+ static createForConnection(connectionNodeId: NodeId, sequence: number): string {
24
+ return `iter_${connectionNodeId}_${sequence}`;
25
+ }
26
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Converts a human-readable node label into a stable, URL-safe identifier segment.
3
+ *
4
+ * Rules:
5
+ * - Lowercase the entire string.
6
+ * - Replace every run of characters outside `[a-z0-9]` with a single `-`.
7
+ * - Strip any leading or trailing `-` characters.
8
+ * - Return `""` for blank/empty input.
9
+ */
10
+ export const NodeIdSlugifier = {
11
+ slugify(label: string): string {
12
+ if (!label) return "";
13
+ return label
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9]+/g, "-")
16
+ .replace(/^-+|-+$/g, "");
17
+ },
18
+ };
@@ -10,13 +10,22 @@ import type {
10
10
  WorkflowId,
11
11
  } from "../../types";
12
12
 
13
+ import { AgentConfigInspector } from "../../ai/AgentConfigInspectorFactory";
14
+ import { AgentConnectionNodeCollector } from "../../ai/AgentConnectionNodeCollector";
13
15
  import { ChainCursor } from "./ChainCursorResolver";
16
+ import { NodeIdSlugifier } from "./NodeIdSlugifier";
17
+ import { WorkflowDefinitionError } from "./WorkflowDefinitionError";
14
18
  import type { AnyRunnableNodeConfig, AnyTriggerNodeConfig } from "./workflowBuilderTypes";
15
19
 
20
+ type NodeIdEntry = Readonly<{
21
+ nodeId: string;
22
+ tokenName: string;
23
+ label: string;
24
+ }>;
25
+
16
26
  export class WorkflowBuilder {
17
27
  private readonly nodes: NodeDefinition[] = [];
18
28
  private readonly edges: WorkflowDefinition["edges"] = [];
19
- private seq = 0;
20
29
 
21
30
  constructor(
22
31
  private readonly meta: { id: WorkflowId; name: string },
@@ -24,8 +33,7 @@ export class WorkflowBuilder {
24
33
  ) {}
25
34
 
26
35
  private add(config: NodeConfigBase): NodeRef {
27
- const tokenName = typeof config.type === "function" ? config.type.name : String(config.type);
28
- const id = config.id ?? `${tokenName}:${++this.seq}`;
36
+ const id = config.id ?? NodeIdSlugifier.slugify(config.name ?? "");
29
37
  this.nodes.push({ id, kind: config.kind, type: config.type, name: config.name, config });
30
38
  return { id, kind: config.kind, name: config.name };
31
39
  }
@@ -45,8 +53,68 @@ export class WorkflowBuilder {
45
53
  }
46
54
 
47
55
  build(): WorkflowDefinition {
56
+ this.validateNodeIds();
48
57
  return { ...this.meta, nodes: this.nodes, edges: this.edges };
49
58
  }
59
+
60
+ private validateNodeIds(): void {
61
+ const entries: NodeIdEntry[] = [];
62
+
63
+ for (const node of this.nodes) {
64
+ const tokenName = typeof node.type === "function" ? node.type.name : String(node.type);
65
+ entries.push({ nodeId: node.id, tokenName, label: node.name ?? "" });
66
+
67
+ if (AgentConfigInspector.isAgentNodeConfig(node.config)) {
68
+ for (const child of AgentConnectionNodeCollector.collect(node.id, node.config)) {
69
+ entries.push({ nodeId: child.nodeId, tokenName: child.typeName, label: child.name });
70
+ }
71
+ }
72
+ }
73
+
74
+ const emptyIds: NodeIdEntry[] = [];
75
+ const seenIds = new Map<string, NodeIdEntry>();
76
+ const duplicateIds: NodeIdEntry[] = [];
77
+
78
+ for (const entry of entries) {
79
+ if (!entry.nodeId) {
80
+ emptyIds.push(entry);
81
+ continue;
82
+ }
83
+ const existing = seenIds.get(entry.nodeId);
84
+ if (existing) {
85
+ if (!duplicateIds.includes(existing)) {
86
+ duplicateIds.push(existing);
87
+ }
88
+ duplicateIds.push(entry);
89
+ } else {
90
+ seenIds.set(entry.nodeId, entry);
91
+ }
92
+ }
93
+
94
+ if (emptyIds.length === 0 && duplicateIds.length === 0) {
95
+ return;
96
+ }
97
+
98
+ const lines: string[] = ["WorkflowBuilder.build() found invalid node ids:"];
99
+
100
+ if (emptyIds.length > 0) {
101
+ lines.push(" Empty ids (label is blank and no explicit id was given):");
102
+ for (const e of emptyIds) {
103
+ lines.push(` - type "${e.tokenName}" label "${e.label}"`);
104
+ }
105
+ }
106
+
107
+ if (duplicateIds.length > 0) {
108
+ lines.push(" Duplicate ids:");
109
+ for (const e of duplicateIds) {
110
+ lines.push(` - id "${e.nodeId}" type "${e.tokenName}" label "${e.label}"`);
111
+ }
112
+ }
113
+
114
+ lines.push(" Fix: set an explicit `id:` on each offending node config.");
115
+
116
+ throw new WorkflowDefinitionError(lines.join("\n"));
117
+ }
50
118
  }
51
119
 
52
120
  export { ChainCursor } from "./ChainCursorResolver";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Thrown by {@link WorkflowBuilder.build} when the workflow definition is structurally invalid.
3
+ *
4
+ * Common causes:
5
+ * - A node has an empty effective id (label is blank and no explicit `id` was given).
6
+ * - Two or more nodes share the same effective id (label slugs collide or explicit ids clash).
7
+ *
8
+ * Fix: provide an explicit `id:` on the offending node configs.
9
+ */
10
+ export class WorkflowDefinitionError extends Error {
11
+ constructor(message: string) {
12
+ super(message);
13
+ this.name = "WorkflowDefinitionError";
14
+ }
15
+ }
@@ -1,10 +1,13 @@
1
1
  export { WorkflowBuilder } from "./dsl/WorkflowBuilder";
2
+ export { WorkflowDefinitionError } from "./dsl/WorkflowDefinitionError";
3
+ export { NodeIdSlugifier } from "./dsl/NodeIdSlugifier";
2
4
  export { ChainCursor } from "./dsl/ChainCursorResolver";
3
5
  export { WhenBuilder } from "./dsl/WhenBuilder";
4
6
  export * from "./dsl/workflowBuilderTypes";
5
7
 
6
8
  export { ConnectionInvocationIdFactory } from "./definition/ConnectionInvocationIdFactory";
7
9
  export { ConnectionNodeIdFactory } from "./definition/ConnectionNodeIdFactory";
10
+ export { NodeIterationIdFactory } from "./definition/NodeIterationIdFactory";
8
11
  export { WorkflowExecutableNodeClassifier } from "./definition/WorkflowExecutableNodeClassifier";
9
12
  export * from "./definition/WorkflowExecutableNodeClassifierFactory";
10
13
 
@@ -1,262 +0,0 @@
1
-
2
- //#region src/workflow/dsl/WhenBuilder.ts
3
- var WhenBuilder = class WhenBuilder {
4
- constructor(wf, from, branchPort) {
5
- this.wf = wf;
6
- this.from = from;
7
- this.branchPort = branchPort;
8
- }
9
- addBranch(steps) {
10
- const created = [];
11
- let prev = null;
12
- for (const cfg of steps) {
13
- const ref = this.wf.add(cfg);
14
- created.push(ref);
15
- if (!prev) this.wf.connect(this.from, ref, this.branchPort, "in");
16
- else this.wf.connect(prev, ref, "main", "in");
17
- prev = ref;
18
- }
19
- for (const cfg of steps) {
20
- const maybe = cfg;
21
- if (!Array.isArray(maybe.upstreamRefs) || maybe.upstreamRefs.length === 0) continue;
22
- maybe.upstreamRefs = maybe.upstreamRefs.map((r) => {
23
- if (typeof r !== "string") return r;
24
- const nodeId = created[parseInt(r.slice(1), 10)]?.id;
25
- return nodeId ? { nodeId } : { nodeId: r };
26
- });
27
- }
28
- return this;
29
- }
30
- when = (branch, steps, ...more) => {
31
- const list = Array.isArray(steps) ? steps : [steps, ...more];
32
- const port = branch ? "true" : "false";
33
- const b = new WhenBuilder(this.wf, this.from, port);
34
- b.addBranch(list);
35
- return b;
36
- };
37
- build() {
38
- return this.wf.build();
39
- }
40
- };
41
-
42
- //#endregion
43
- //#region src/workflow/dsl/ChainCursorResolver.ts
44
- var ChainCursor = class ChainCursor {
45
- constructor(wf, endpoints) {
46
- this.wf = wf;
47
- this.endpoints = endpoints;
48
- }
49
- then(config) {
50
- const next = this.wf.add(config);
51
- const inputPortHint = this.resolveSharedInputPortHint();
52
- for (const e of this.endpoints) this.wf.connect(e.node, next, e.output);
53
- return new ChainCursor(this.wf, [{
54
- node: next,
55
- output: "main",
56
- ...inputPortHint ? { inputPortHint } : {}
57
- }]);
58
- }
59
- thenIntoInputHints(config) {
60
- const next = this.wf.add(config);
61
- for (const e of this.endpoints) this.wf.connect(e.node, next, e.output, e.inputPortHint ?? "in");
62
- return new ChainCursor(this.wf, [{
63
- node: next,
64
- output: "main"
65
- }]);
66
- }
67
- when = ((arg1, steps, ...more) => {
68
- if (this.endpoints.length !== 1) throw new Error("ChainCursor.when(...) is only supported from a single cursor endpoint");
69
- const cursor = this.endpoints[0].node;
70
- if (typeof arg1 === "boolean") {
71
- const list = Array.isArray(steps) ? steps : steps ? [steps, ...more] : more;
72
- const port = arg1 ? "true" : "false";
73
- const b = new WhenBuilder(this.wf, cursor, port);
74
- b.addBranch(list);
75
- return b;
76
- }
77
- const branches = arg1;
78
- const wfAny = this.wf;
79
- const buildBranch = (port, branchSteps) => {
80
- const list = branchSteps ?? [];
81
- let prev = null;
82
- for (const cfg of list) {
83
- const ref = wfAny.add(cfg);
84
- if (!prev) wfAny.connect(cursor, ref, port, "in");
85
- else wfAny.connect(prev, ref, "main", "in");
86
- prev = ref;
87
- }
88
- if (!prev) return {
89
- end: cursor,
90
- endOutput: port,
91
- inputPortHint: port
92
- };
93
- return {
94
- end: prev,
95
- endOutput: "main",
96
- inputPortHint: port
97
- };
98
- };
99
- const t = buildBranch("true", branches.true);
100
- const f = buildBranch("false", branches.false);
101
- return new ChainCursor(this.wf, [{
102
- node: t.end,
103
- output: t.endOutput,
104
- inputPortHint: t.inputPortHint
105
- }, {
106
- node: f.end,
107
- output: f.endOutput,
108
- inputPortHint: f.inputPortHint
109
- }]);
110
- });
111
- route(branches) {
112
- if (this.endpoints.length !== 1) throw new Error("ChainCursor.route(...) is only supported from a single cursor endpoint");
113
- const cursor = this.endpoints[0];
114
- const nextEndpoints = [];
115
- for (const [port, branchFactory] of Object.entries(branches)) {
116
- if (!branchFactory) continue;
117
- const builtBranch = branchFactory(new ChainCursor(this.wf, [{
118
- node: cursor.node,
119
- output: port,
120
- inputPortHint: port
121
- }]));
122
- if (!builtBranch) continue;
123
- nextEndpoints.push(...builtBranch.endpoints);
124
- }
125
- return new ChainCursor(this.wf, nextEndpoints);
126
- }
127
- build() {
128
- return this.wf.build();
129
- }
130
- resolveSharedInputPortHint() {
131
- const first = this.endpoints[0]?.inputPortHint;
132
- if (!first) return;
133
- return this.endpoints.every((endpoint) => endpoint.inputPortHint === first) ? first : void 0;
134
- }
135
- };
136
-
137
- //#endregion
138
- //#region src/workflow/dsl/WorkflowBuilder.ts
139
- var WorkflowBuilder = class {
140
- nodes = [];
141
- edges = [];
142
- seq = 0;
143
- constructor(meta, options) {
144
- this.meta = meta;
145
- this.options = options;
146
- }
147
- add(config) {
148
- const tokenName = typeof config.type === "function" ? config.type.name : String(config.type);
149
- const id = config.id ?? `${tokenName}:${++this.seq}`;
150
- this.nodes.push({
151
- id,
152
- kind: config.kind,
153
- type: config.type,
154
- name: config.name,
155
- config
156
- });
157
- return {
158
- id,
159
- kind: config.kind,
160
- name: config.name
161
- };
162
- }
163
- connect(from, to, fromOutput = "main", toInput = "in") {
164
- this.edges.push({
165
- from: {
166
- nodeId: from.id,
167
- output: fromOutput
168
- },
169
- to: {
170
- nodeId: to.id,
171
- input: toInput
172
- }
173
- });
174
- }
175
- trigger(config) {
176
- const ref = this.add(config);
177
- return new ChainCursor(this, [{
178
- node: ref,
179
- output: "main"
180
- }]);
181
- }
182
- start(config) {
183
- const ref = this.add(config);
184
- return new ChainCursor(this, [{
185
- node: ref,
186
- output: "main"
187
- }]);
188
- }
189
- build() {
190
- return {
191
- ...this.meta,
192
- nodes: this.nodes,
193
- edges: this.edges
194
- };
195
- }
196
- };
197
-
198
- //#endregion
199
- //#region src/events/InMemoryRunEventSubscription.ts
200
- var InMemoryRunEventSubscription = class {
201
- constructor(onClose) {
202
- this.onClose = onClose;
203
- }
204
- async close() {
205
- this.onClose();
206
- }
207
- };
208
-
209
- //#endregion
210
- //#region src/events/InMemoryRunEventBusRegistry.ts
211
- var InMemoryRunEventBus = class {
212
- globalListeners = /* @__PURE__ */ new Set();
213
- listenersByWorkflowId = /* @__PURE__ */ new Map();
214
- async publish(event) {
215
- for (const listener of this.globalListeners) listener(event);
216
- for (const listener of this.listenersByWorkflowId.get(event.workflowId) ?? []) listener(event);
217
- }
218
- async subscribe(onEvent) {
219
- this.globalListeners.add(onEvent);
220
- return new InMemoryRunEventSubscription(() => {
221
- this.globalListeners.delete(onEvent);
222
- });
223
- }
224
- async subscribeToWorkflow(workflowId, onEvent) {
225
- const existing = this.listenersByWorkflowId.get(workflowId) ?? /* @__PURE__ */ new Set();
226
- existing.add(onEvent);
227
- this.listenersByWorkflowId.set(workflowId, existing);
228
- return new InMemoryRunEventSubscription(() => {
229
- const listeners = this.listenersByWorkflowId.get(workflowId);
230
- if (!listeners) return;
231
- listeners.delete(onEvent);
232
- if (listeners.size === 0) this.listenersByWorkflowId.delete(workflowId);
233
- });
234
- }
235
- };
236
-
237
- //#endregion
238
- Object.defineProperty(exports, 'ChainCursor', {
239
- enumerable: true,
240
- get: function () {
241
- return ChainCursor;
242
- }
243
- });
244
- Object.defineProperty(exports, 'InMemoryRunEventBus', {
245
- enumerable: true,
246
- get: function () {
247
- return InMemoryRunEventBus;
248
- }
249
- });
250
- Object.defineProperty(exports, 'WhenBuilder', {
251
- enumerable: true,
252
- get: function () {
253
- return WhenBuilder;
254
- }
255
- });
256
- Object.defineProperty(exports, 'WorkflowBuilder', {
257
- enumerable: true,
258
- get: function () {
259
- return WorkflowBuilder;
260
- }
261
- });
262
- //# sourceMappingURL=InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map