@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.
- package/CHANGELOG.md +293 -0
- package/dist/{EngineRuntimeRegistration.types-kxQA5NLt.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-Ba2AvBnL.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
- package/dist/{InMemoryRunDataFactory-Ou4tQUOS.d.cts → InMemoryRunDataFactory-Xw7v4-sj.d.cts} +31 -29
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs +47 -0
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map +1 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js +41 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js.map +1 -0
- package/dist/{RunIntentService-dteLjNiT.d.ts → RunIntentService-BE9CAkbf.d.ts} +602 -213
- package/dist/{RunIntentService-Dyh_dH0k.d.cts → RunIntentService-siBSjaaY.d.cts} +430 -125
- package/dist/bootstrap/index.cjs +5 -2
- package/dist/bootstrap/index.d.cts +212 -135
- package/dist/bootstrap/index.d.ts +4 -4
- package/dist/bootstrap/index.js +3 -3
- package/dist/{bootstrap-Cko6udwL.cjs → bootstrap-Cm5ruQxx.cjs} +253 -3
- package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
- package/dist/{bootstrap-CL68rqWg.js → bootstrap-D3r505ko.js} +236 -4
- package/dist/bootstrap-D3r505ko.js.map +1 -0
- package/dist/{index-CyfGTfU1.d.ts → index-DeLl1Tne.d.ts} +574 -242
- package/dist/index.cjs +328 -180
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +441 -103
- package/dist/index.d.ts +3 -3
- package/dist/index.js +305 -163
- package/dist/index.js.map +1 -1
- package/dist/{runtime-284ok0cm.js → runtime-BGNbRnqs.js} +764 -75
- package/dist/runtime-BGNbRnqs.js.map +1 -0
- package/dist/{runtime-B3Og-_St.cjs → runtime-DKXJwTNv.cjs} +841 -80
- package/dist/runtime-DKXJwTNv.cjs.map +1 -0
- package/dist/testing.cjs +4 -4
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +3 -3
- package/package.json +7 -2
- package/src/authoring/DefinedCollectionRegistry.ts +17 -0
- package/src/authoring/defineCollection.types.ts +181 -0
- package/src/authoring/definePollingTrigger.types.ts +396 -0
- package/src/authoring/definePollingTriggerInternals.ts +74 -0
- package/src/authoring/index.ts +19 -0
- package/src/bootstrap/index.ts +9 -0
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +5 -1
- package/src/contracts/assertionTypes.ts +63 -0
- package/src/contracts/baseTypes.ts +12 -0
- package/src/contracts/collectionTypes.ts +44 -0
- package/src/contracts/credentialTypes.ts +23 -1
- package/src/contracts/index.ts +4 -0
- package/src/contracts/runTypes.ts +27 -1
- package/src/contracts/runtimeTypes.ts +34 -0
- package/src/contracts/testTriggerTypes.ts +66 -0
- package/src/contracts/workflowTypes.ts +30 -7
- package/src/contracts.ts +59 -0
- package/src/events/runEvents.ts +49 -0
- package/src/execution/ChildExecutionScopeFactory.ts +4 -7
- package/src/execution/DefaultExecutionContextFactory.ts +6 -0
- package/src/execution/NodeInstanceFactory.ts +13 -1
- package/src/execution/NodeInstantiationError.ts +16 -0
- package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
- package/src/execution/index.ts +1 -0
- package/src/index.ts +7 -0
- package/src/orchestration/AbortControllerFactory.ts +9 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
- package/src/orchestration/RunContinuationService.ts +3 -0
- package/src/orchestration/RunStartService.ts +114 -2
- package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
- package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
- package/src/orchestration/TriggerRuntimeService.ts +34 -7
- package/src/orchestration/index.ts +9 -0
- package/src/runtime/EngineFactory.ts +11 -0
- package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
- package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
- package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
- package/src/triggers/polling/index.ts +5 -0
- package/src/types/index.ts +12 -9
- package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
- package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
- package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
- package/src/workflow/index.ts +2 -0
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
- package/dist/bootstrap-CL68rqWg.js.map +0 -1
- package/dist/bootstrap-Cko6udwL.cjs.map +0 -1
- package/dist/runtime-284ok0cm.js.map +0 -1
- package/dist/runtime-B3Og-_St.cjs.map +0 -1
|
@@ -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";
|
package/src/types/index.ts
CHANGED
|
@@ -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/
|
|
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,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
|
|
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
|
+
}
|
package/src/workflow/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
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";
|
|
@@ -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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InMemoryRunEventBusRegistry-B0_C4OnP.cjs","names":["wf: WorkflowBuilder","from: NodeRef","branchPort: OutputPortKey","created: NodeRef[]","prev: NodeRef | null","port: OutputPortKey","wf: WorkflowBuilder","endpoints: ReadonlyArray<ChainCursorEndpoint>","port: OutputPortKey","prev: NodeRef | null","nextEndpoints: ChainCursorEndpoint[]","meta: { id: WorkflowId; name: string }","options?: Readonly<Record<string, never>>","onClose: () => void"],"sources":["../src/workflow/dsl/WhenBuilder.ts","../src/workflow/dsl/ChainCursorResolver.ts","../src/workflow/dsl/WorkflowBuilder.ts","../src/events/InMemoryRunEventSubscription.ts","../src/events/InMemoryRunEventBusRegistry.ts"],"sourcesContent":["import type { NodeId, NodeRef, OutputPortKey, UpstreamRefPlaceholder, WorkflowDefinition } from \"../../types\";\n\nimport { WorkflowBuilder } from \"./WorkflowBuilder\";\nimport type { AnyRunnableNodeConfig, BooleanWhenOverloads, ValidStepSequence } from \"./workflowBuilderTypes\";\n\nexport class WhenBuilder<TCurrentJson> {\n constructor(\n private readonly wf: WorkflowBuilder,\n private readonly from: NodeRef,\n private readonly branchPort: OutputPortKey,\n ) {}\n\n addBranch<TSteps extends ReadonlyArray<AnyRunnableNodeConfig>>(\n steps: TSteps & ValidStepSequence<TCurrentJson, TSteps>,\n ): this {\n const created: NodeRef[] = [];\n\n let prev: NodeRef | null = null;\n for (const cfg of steps) {\n const ref = (this.wf as any).add(cfg) as NodeRef;\n created.push(ref);\n if (!prev) (this.wf as any).connect(this.from, ref, this.branchPort, \"in\");\n else (this.wf as any).connect(prev, ref, \"main\", \"in\");\n prev = ref;\n }\n\n for (const cfg of steps) {\n const maybe = cfg as unknown as { upstreamRefs?: Array<{ nodeId: NodeId } | UpstreamRefPlaceholder> };\n if (!Array.isArray(maybe.upstreamRefs) || maybe.upstreamRefs.length === 0) continue;\n\n maybe.upstreamRefs = maybe.upstreamRefs.map((r) => {\n if (typeof r !== \"string\") return r;\n const idx = parseInt(r.slice(1), 10);\n const nodeId = created[idx]?.id;\n return nodeId ? { nodeId } : { nodeId: r };\n });\n }\n\n return this;\n }\n\n readonly when: BooleanWhenOverloads<TCurrentJson, WhenBuilder<TCurrentJson>> = (\n branch: boolean,\n steps: ReadonlyArray<AnyRunnableNodeConfig> | AnyRunnableNodeConfig,\n ...more: AnyRunnableNodeConfig[]\n ): WhenBuilder<TCurrentJson> => {\n const list = Array.isArray(steps) ? steps : [steps, ...more];\n const port: OutputPortKey = branch ? \"true\" : \"false\";\n const b = new WhenBuilder<TCurrentJson>(this.wf, this.from, port);\n b.addBranch(list);\n return b;\n };\n\n build(): WorkflowDefinition {\n return this.wf.build();\n }\n}\n","import type {\n InputPortKey,\n NodeRef,\n OutputPortKey,\n RunnableNodeConfig,\n RunnableNodeOutputJson,\n WorkflowDefinition,\n} from \"../../types\";\n\nimport { WorkflowBuilder } from \"./WorkflowBuilder\";\nimport { WhenBuilder } from \"./WhenBuilder\";\nimport type {\n AnyRunnableNodeConfig,\n BooleanWhenOverloads,\n BranchOutputGuard,\n BranchStepsArg,\n StepSequenceOutput,\n} from \"./workflowBuilderTypes\";\n\ntype ChainCursorEndpoint = Readonly<{ node: NodeRef; output: OutputPortKey; inputPortHint?: InputPortKey }>;\n\ntype ChainCursorWhenOverloads<TCurrentJson> = BooleanWhenOverloads<TCurrentJson, WhenBuilder<TCurrentJson>> & {\n <\n TTrueSteps extends ReadonlyArray<AnyRunnableNodeConfig> | undefined,\n TFalseSteps extends ReadonlyArray<AnyRunnableNodeConfig> | undefined,\n >(\n branches: Readonly<{\n true?: TTrueSteps extends ReadonlyArray<AnyRunnableNodeConfig> ? BranchStepsArg<TCurrentJson, TTrueSteps> : never;\n false?: TFalseSteps extends ReadonlyArray<AnyRunnableNodeConfig>\n ? BranchStepsArg<TCurrentJson, TFalseSteps>\n : never;\n }> &\n BranchOutputGuard<TCurrentJson, TTrueSteps, TFalseSteps>,\n ): ChainCursor<StepSequenceOutput<TCurrentJson, TTrueSteps>>;\n};\n\nexport class ChainCursor<TCurrentJson> {\n constructor(\n private readonly wf: WorkflowBuilder,\n private readonly endpoints: ReadonlyArray<ChainCursorEndpoint>,\n ) {}\n\n then<TOutputJson, TConfig extends RunnableNodeConfig<TCurrentJson, TOutputJson>>(\n config: TConfig,\n ): ChainCursor<RunnableNodeOutputJson<TConfig>> {\n const next = (this.wf as any).add(config) as NodeRef;\n const inputPortHint = this.resolveSharedInputPortHint();\n for (const e of this.endpoints) {\n (this.wf as any).connect(e.node, next, e.output);\n }\n return new ChainCursor<RunnableNodeOutputJson<TConfig>>(this.wf, [\n { node: next, output: \"main\", ...(inputPortHint ? { inputPortHint } : {}) },\n ]);\n }\n\n thenIntoInputHints<TOutputJson, TConfig extends RunnableNodeConfig<any, TOutputJson>>(\n config: TConfig,\n ): ChainCursor<RunnableNodeOutputJson<TConfig>> {\n const next = (this.wf as any).add(config) as NodeRef;\n for (const e of this.endpoints) {\n (this.wf as any).connect(e.node, next, e.output, e.inputPortHint ?? \"in\");\n }\n return new ChainCursor<RunnableNodeOutputJson<TConfig>>(this.wf, [{ node: next, output: \"main\" }]);\n }\n\n readonly when: ChainCursorWhenOverloads<TCurrentJson> = ((\n arg1:\n | boolean\n | Readonly<{ true?: ReadonlyArray<AnyRunnableNodeConfig>; false?: ReadonlyArray<AnyRunnableNodeConfig> }>,\n steps?: ReadonlyArray<AnyRunnableNodeConfig> | AnyRunnableNodeConfig,\n ...more: AnyRunnableNodeConfig[]\n ): WhenBuilder<TCurrentJson> | ChainCursor<TCurrentJson> => {\n if (this.endpoints.length !== 1) {\n throw new Error(\"ChainCursor.when(...) is only supported from a single cursor endpoint\");\n }\n const cursor = this.endpoints[0]!.node;\n\n if (typeof arg1 === \"boolean\") {\n const list = Array.isArray(steps) ? steps : steps ? [steps, ...more] : more;\n const port: OutputPortKey = arg1 ? \"true\" : \"false\";\n const b = new WhenBuilder<TCurrentJson>(this.wf, cursor, port);\n b.addBranch(list);\n return b;\n }\n\n const branches = arg1;\n const wfAny = this.wf as any;\n\n const buildBranch = (\n port: OutputPortKey,\n branchSteps: ReadonlyArray<AnyRunnableNodeConfig> | undefined,\n ): Readonly<{ end: NodeRef; endOutput: OutputPortKey; inputPortHint: InputPortKey }> => {\n const list = branchSteps ?? [];\n let prev: NodeRef | null = null;\n for (const cfg of list) {\n const ref = wfAny.add(cfg) as NodeRef;\n if (!prev) wfAny.connect(cursor, ref, port, \"in\");\n else wfAny.connect(prev, ref, \"main\", \"in\");\n prev = ref;\n }\n if (!prev) return { end: cursor, endOutput: port, inputPortHint: port };\n return { end: prev, endOutput: \"main\", inputPortHint: port };\n };\n\n const t = buildBranch(\"true\", branches.true);\n const f = buildBranch(\"false\", branches.false);\n return new ChainCursor<TCurrentJson>(this.wf, [\n { node: t.end, output: t.endOutput, inputPortHint: t.inputPortHint },\n { node: f.end, output: f.endOutput, inputPortHint: f.inputPortHint },\n ]);\n }) as ChainCursorWhenOverloads<TCurrentJson>;\n\n route<TNextJson>(\n branches: Readonly<\n Record<OutputPortKey, (branch: ChainCursor<TCurrentJson>) => ChainCursor<TNextJson> | undefined>\n >,\n ): ChainCursor<TNextJson> {\n if (this.endpoints.length !== 1) {\n throw new Error(\"ChainCursor.route(...) is only supported from a single cursor endpoint\");\n }\n const cursor = this.endpoints[0]!;\n const nextEndpoints: ChainCursorEndpoint[] = [];\n for (const [port, branchFactory] of Object.entries(branches)) {\n if (!branchFactory) {\n continue;\n }\n const branch = new ChainCursor<TCurrentJson>(this.wf, [{ node: cursor.node, output: port, inputPortHint: port }]);\n const builtBranch = branchFactory(branch);\n if (!builtBranch) {\n continue;\n }\n nextEndpoints.push(...builtBranch.endpoints);\n }\n return new ChainCursor<TNextJson>(this.wf, nextEndpoints);\n }\n\n build(): WorkflowDefinition {\n return this.wf.build();\n }\n\n private resolveSharedInputPortHint(): InputPortKey | undefined {\n const first = this.endpoints[0]?.inputPortHint;\n if (!first) {\n return undefined;\n }\n return this.endpoints.every((endpoint) => endpoint.inputPortHint === first) ? first : undefined;\n }\n}\n","import type {\n InputPortKey,\n NodeConfigBase,\n NodeDefinition,\n NodeRef,\n OutputPortKey,\n RunnableNodeOutputJson,\n TriggerNodeOutputJson,\n WorkflowDefinition,\n WorkflowId,\n} from \"../../types\";\n\nimport { ChainCursor } from \"./ChainCursorResolver\";\nimport type { AnyRunnableNodeConfig, AnyTriggerNodeConfig } from \"./workflowBuilderTypes\";\n\nexport class WorkflowBuilder {\n private readonly nodes: NodeDefinition[] = [];\n private readonly edges: WorkflowDefinition[\"edges\"] = [];\n private seq = 0;\n\n constructor(\n private readonly meta: { id: WorkflowId; name: string },\n private readonly options?: Readonly<Record<string, never>>,\n ) {}\n\n private add(config: NodeConfigBase): NodeRef {\n const tokenName = typeof config.type === \"function\" ? config.type.name : String(config.type);\n const id = config.id ?? `${tokenName}:${++this.seq}`;\n this.nodes.push({ id, kind: config.kind, type: config.type, name: config.name, config });\n return { id, kind: config.kind, name: config.name };\n }\n\n private connect(from: NodeRef, to: NodeRef, fromOutput: OutputPortKey = \"main\", toInput: InputPortKey = \"in\"): void {\n this.edges.push({ from: { nodeId: from.id, output: fromOutput }, to: { nodeId: to.id, input: toInput } });\n }\n\n trigger<TConfig extends AnyTriggerNodeConfig>(config: TConfig): ChainCursor<TriggerNodeOutputJson<TConfig>> {\n const ref = this.add(config);\n return new ChainCursor<TriggerNodeOutputJson<TConfig>>(this, [{ node: ref, output: \"main\" }]);\n }\n\n start<TConfig extends AnyRunnableNodeConfig>(config: TConfig): ChainCursor<RunnableNodeOutputJson<TConfig>> {\n const ref = this.add(config);\n return new ChainCursor<RunnableNodeOutputJson<TConfig>>(this, [{ node: ref, output: \"main\" }]);\n }\n\n build(): WorkflowDefinition {\n return { ...this.meta, nodes: this.nodes, edges: this.edges };\n }\n}\n\nexport { ChainCursor } from \"./ChainCursorResolver\";\nexport { WhenBuilder } from \"./WhenBuilder\";\n","import type { RunEventSubscription } from \"./runEvents\";\n\nexport class InMemoryRunEventSubscription implements RunEventSubscription {\n constructor(private readonly onClose: () => void) {}\n\n async close(): Promise<void> {\n this.onClose();\n }\n}\n","import type { WorkflowId } from \"../types\";\n\nimport type { RunEvent, RunEventBus, RunEventSubscription } from \"./runEvents\";\n\nimport { InMemoryRunEventSubscription } from \"./InMemoryRunEventSubscription\";\n\nexport class InMemoryRunEventBus implements RunEventBus {\n private readonly globalListeners = new Set<(event: RunEvent) => void>();\n private readonly listenersByWorkflowId = new Map<WorkflowId, Set<(event: RunEvent) => void>>();\n\n async publish(event: RunEvent): Promise<void> {\n for (const listener of this.globalListeners) listener(event);\n for (const listener of this.listenersByWorkflowId.get(event.workflowId) ?? []) listener(event);\n }\n\n async subscribe(onEvent: (event: RunEvent) => void): Promise<RunEventSubscription> {\n this.globalListeners.add(onEvent);\n return new InMemoryRunEventSubscription(() => {\n this.globalListeners.delete(onEvent);\n });\n }\n\n async subscribeToWorkflow(workflowId: WorkflowId, onEvent: (event: RunEvent) => void): Promise<RunEventSubscription> {\n const existing = this.listenersByWorkflowId.get(workflowId) ?? new Set<(event: RunEvent) => void>();\n existing.add(onEvent);\n this.listenersByWorkflowId.set(workflowId, existing);\n\n return new InMemoryRunEventSubscription(() => {\n const listeners = this.listenersByWorkflowId.get(workflowId);\n if (!listeners) return;\n listeners.delete(onEvent);\n if (listeners.size === 0) this.listenersByWorkflowId.delete(workflowId);\n });\n }\n}\n\nexport { InMemoryRunEventSubscription } from \"./InMemoryRunEventSubscription\";\n"],"mappings":"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"}
|