@codemation/core 0.0.19 → 0.2.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 +16 -0
- package/dist/EngineRuntimeRegistration.types-0sgV2XL2.d.ts +42 -0
- package/dist/EngineWorkflowRunnerService-Dx7bJsJR.d.cts +73 -0
- package/dist/InMemoryRunDataFactory-qIYQEar7.d.cts +94 -0
- package/dist/{InMemoryLiveWorkflowRepository-DxoualoC.d.ts → RunIntentService-BCvGdOSY.d.ts} +438 -9
- package/dist/{RunIntentService-C1nu_YwM.js → RunIntentService-BFA48UpH.js} +252 -67
- package/dist/RunIntentService-BFA48UpH.js.map +1 -0
- package/dist/{InMemoryLiveWorkflowRepository-orY1VsWG.d.cts → RunIntentService-CV8izV8t.d.cts} +214 -7
- package/dist/{RunIntentService-ZkjpY7MS.cjs → RunIntentService-DcxXf_AM.cjs} +262 -65
- package/dist/RunIntentService-DcxXf_AM.cjs.map +1 -0
- package/dist/bootstrap/index.cjs +14 -1135
- package/dist/bootstrap/index.d.cts +7 -60
- package/dist/bootstrap/index.d.ts +4 -40
- package/dist/bootstrap/index.js +3 -1122
- package/dist/bootstrap-D67Sf2BF.js +1136 -0
- package/dist/bootstrap-D67Sf2BF.js.map +1 -0
- package/dist/bootstrap-DoQHAEQJ.cjs +1203 -0
- package/dist/bootstrap-DoQHAEQJ.cjs.map +1 -0
- package/dist/{index-BIewO9-9.d.ts → index-BHmrZIHp.d.ts} +32 -260
- package/dist/index.cjs +98 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +92 -218
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +329 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +181 -4
- package/dist/testing.d.ts +181 -3
- package/dist/testing.js +319 -2
- package/dist/testing.js.map +1 -1
- package/dist/workflowActivationPolicy-B8HzTk3o.js +201 -0
- package/dist/workflowActivationPolicy-B8HzTk3o.js.map +1 -0
- package/dist/workflowActivationPolicy-BzyzXLa_.cjs +231 -0
- package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +1 -0
- package/package.json +1 -1
- package/src/ai/AgentConnectionNodeCollector.ts +99 -0
- package/src/ai/AgentToolFactory.ts +38 -2
- package/src/ai/AiHost.ts +1 -1
- package/src/browser.ts +11 -0
- package/src/contracts/executionPersistenceContracts.ts +186 -0
- package/src/contracts/index.ts +1 -0
- package/src/contracts/runFinishedAtFactory.ts +5 -2
- package/src/contracts/runTypes.ts +10 -0
- package/src/contracts/runtimeTypes.ts +6 -2
- package/src/contracts/workflowTypes.ts +3 -2
- package/src/events/EventPublishingWorkflowExecutionRepository.ts +5 -0
- package/src/execution/ActivationEnqueueService.ts +8 -8
- package/src/execution/PersistedRunStateTerminalBuilder.ts +3 -0
- package/src/index.ts +6 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +11 -6
- package/src/orchestration/RunContinuationService.ts +94 -24
- package/src/runStorage/InMemoryWorkflowExecutionRepository.ts +14 -1
- package/src/scheduler/DefaultDrivingScheduler.ts +21 -11
- package/src/scheduler/InlineDrivingScheduler.ts +17 -21
- package/src/testing/CapturingScheduler.ts +15 -0
- package/src/testing/EngineTestKitRunIdFactory.ts +24 -0
- package/src/testing/InMemoryTriggerSetupStateRepository.ts +21 -0
- package/src/testing/PrefixedSequentialIdGenerator.ts +17 -0
- package/src/testing/RegistrarEngineTestKit.types.ts +76 -0
- package/src/testing/RegistrarEngineTestKitFactory.ts +154 -0
- package/src/testing/SubWorkflowRunnerTestNode.ts +83 -0
- package/src/testing/WorkflowTestHarnessManualTrigger.ts +39 -0
- package/src/testing/WorkflowTestKit.types.ts +9 -0
- package/src/testing/WorkflowTestKitBuilder.ts +77 -0
- package/src/testing/WorkflowTestKitNodeRegistrationContextFactory.ts +17 -0
- package/src/testing/WorkflowTestKitRunNodeWorkflowFactory.ts +26 -0
- package/src/testing.ts +19 -0
- package/src/types/index.ts +1 -0
- package/src/workflow/definition/ConnectionNodeIdFactory.ts +28 -0
- package/dist/InMemoryLiveWorkflowRepository-BTzHpQ6e.cjs +0 -151
- package/dist/InMemoryLiveWorkflowRepository-BTzHpQ6e.cjs.map +0 -1
- package/dist/InMemoryLiveWorkflowRepository-BoLNnVLg.js +0 -139
- package/dist/InMemoryLiveWorkflowRepository-BoLNnVLg.js.map +0 -1
- package/dist/RunIntentService-C1nu_YwM.js.map +0 -1
- package/dist/RunIntentService-DjbxzBBP.d.cts +0 -288
- package/dist/RunIntentService-ZkjpY7MS.cjs.map +0 -1
- package/dist/WorkflowSnapshotCodec-DSEzKyt3.d.cts +0 -22
- package/dist/bootstrap/index.cjs.map +0 -1
- package/dist/bootstrap/index.js.map +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { DependencyContainer, InjectionToken } from "tsyringe";
|
|
2
|
+
import { container as tsyringeContainer } from "tsyringe";
|
|
3
|
+
|
|
4
|
+
import type { WorkflowRunnerService } from "../contracts/runtimeTypes";
|
|
5
|
+
import { CoreTokens } from "../di";
|
|
6
|
+
import { InMemoryRunEventBus } from "../events/InMemoryRunEventBusRegistry";
|
|
7
|
+
import { AllWorkflowsActiveWorkflowActivationPolicy } from "../contracts/workflowActivationPolicy";
|
|
8
|
+
import { RunIntentService } from "../runtime/RunIntentService";
|
|
9
|
+
import {
|
|
10
|
+
DefaultDrivingScheduler,
|
|
11
|
+
DefaultAsyncSleeper,
|
|
12
|
+
DefaultExecutionContextFactory,
|
|
13
|
+
Engine,
|
|
14
|
+
EngineRuntimeRegistrar,
|
|
15
|
+
EngineWorkflowRunnerService,
|
|
16
|
+
HintOnlyOffloadPolicy,
|
|
17
|
+
InProcessRetryRunner,
|
|
18
|
+
InMemoryRunDataFactory,
|
|
19
|
+
InMemoryWorkflowExecutionRepository,
|
|
20
|
+
InlineDrivingScheduler,
|
|
21
|
+
NodeExecutor,
|
|
22
|
+
NodeInstanceFactory,
|
|
23
|
+
PersistedWorkflowTokenRegistry,
|
|
24
|
+
} from "../bootstrap/index";
|
|
25
|
+
import { InMemoryLiveWorkflowRepository } from "../runtime/InMemoryLiveWorkflowRepository";
|
|
26
|
+
import { EngineTestKitRunIdFactory } from "./EngineTestKitRunIdFactory";
|
|
27
|
+
import { InMemoryTriggerSetupStateRepository } from "./InMemoryTriggerSetupStateRepository";
|
|
28
|
+
import { RejectingCredentialSessionService } from "./RejectingCredentialSessionService";
|
|
29
|
+
import { CapturingScheduler } from "./CapturingScheduler";
|
|
30
|
+
import { PrefixedSequentialIdGenerator } from "./PrefixedSequentialIdGenerator";
|
|
31
|
+
import type { RegistrarEngineTestKitHandle, RegistrarEngineTestKitOptions } from "./RegistrarEngineTestKit.types";
|
|
32
|
+
import { SubWorkflowRunnerNode } from "./SubWorkflowRunnerTestNode";
|
|
33
|
+
import { WorkflowTestHarnessManualTriggerNode } from "./WorkflowTestHarnessManualTrigger";
|
|
34
|
+
|
|
35
|
+
export class RegistrarEngineTestKitFactory {
|
|
36
|
+
static create(options: RegistrarEngineTestKitOptions = {}): RegistrarEngineTestKitHandle {
|
|
37
|
+
const runStore = options.runStore ?? new InMemoryWorkflowExecutionRepository();
|
|
38
|
+
const scheduler = options.scheduler ?? new CapturingScheduler();
|
|
39
|
+
const offloadPolicy = options.offloadPolicy ?? new HintOnlyOffloadPolicy();
|
|
40
|
+
const runIdGen = new PrefixedSequentialIdGenerator("run_");
|
|
41
|
+
const activationIdGen = new PrefixedSequentialIdGenerator("act_");
|
|
42
|
+
const makeRunId = options.makeRunId ?? runIdGen.asFn();
|
|
43
|
+
const makeActivationId = options.makeActivationId ?? activationIdGen.asFn();
|
|
44
|
+
const credentialSessions = options.credentialSessions ?? new RejectingCredentialSessionService();
|
|
45
|
+
const eventBus = options.eventBus ?? new InMemoryRunEventBus();
|
|
46
|
+
const triggerSetupStateRepository =
|
|
47
|
+
options.triggerSetupStateRepository ?? new InMemoryTriggerSetupStateRepository();
|
|
48
|
+
const liveWorkflowRepository = new InMemoryLiveWorkflowRepository();
|
|
49
|
+
const runDataFactory = options.runDataFactory ?? new InMemoryRunDataFactory();
|
|
50
|
+
const executionContextFactory = options.executionContextFactory ?? new DefaultExecutionContextFactory();
|
|
51
|
+
const container = options.container ?? tsyringeContainer.createChildContainer();
|
|
52
|
+
const dependencyContainer = container as DependencyContainer;
|
|
53
|
+
const nodeResolver = container;
|
|
54
|
+
const nodeExecutor = new NodeExecutor(
|
|
55
|
+
new NodeInstanceFactory(nodeResolver),
|
|
56
|
+
new InProcessRetryRunner(new DefaultAsyncSleeper()),
|
|
57
|
+
);
|
|
58
|
+
const activationScheduler = new DefaultDrivingScheduler(
|
|
59
|
+
offloadPolicy,
|
|
60
|
+
scheduler,
|
|
61
|
+
new InlineDrivingScheduler(nodeExecutor),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
for (const [token, value] of options.providers ?? new Map<InjectionToken<unknown>, unknown>()) {
|
|
65
|
+
dependencyContainer.registerInstance(token, value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dependencyContainer.registerInstance(CoreTokens.CredentialSessionService, credentialSessions);
|
|
69
|
+
dependencyContainer.registerInstance(CoreTokens.LiveWorkflowRepository, liveWorkflowRepository);
|
|
70
|
+
dependencyContainer.registerInstance(CoreTokens.WorkflowRepository, liveWorkflowRepository);
|
|
71
|
+
dependencyContainer.registerInstance(CoreTokens.NodeResolver, nodeResolver);
|
|
72
|
+
dependencyContainer.registerInstance(
|
|
73
|
+
CoreTokens.RunIdFactory,
|
|
74
|
+
new EngineTestKitRunIdFactory(makeRunId, makeActivationId),
|
|
75
|
+
);
|
|
76
|
+
dependencyContainer.registerInstance(
|
|
77
|
+
CoreTokens.ActivationIdFactory,
|
|
78
|
+
new EngineTestKitRunIdFactory(makeRunId, makeActivationId),
|
|
79
|
+
);
|
|
80
|
+
dependencyContainer.registerInstance(CoreTokens.WebhookBasePath, options.webhookBasePath ?? "/webhooks");
|
|
81
|
+
dependencyContainer.registerInstance(CoreTokens.WorkflowExecutionRepository, runStore);
|
|
82
|
+
dependencyContainer.registerInstance(CoreTokens.TriggerSetupStateRepository, triggerSetupStateRepository);
|
|
83
|
+
dependencyContainer.registerInstance(CoreTokens.NodeActivationScheduler, activationScheduler);
|
|
84
|
+
dependencyContainer.registerInstance(CoreTokens.RunDataFactory, runDataFactory);
|
|
85
|
+
dependencyContainer.registerInstance(CoreTokens.ExecutionContextFactory, executionContextFactory);
|
|
86
|
+
dependencyContainer.registerInstance(CoreTokens.RunEventBus, eventBus);
|
|
87
|
+
dependencyContainer.registerInstance(
|
|
88
|
+
CoreTokens.PersistedWorkflowTokenRegistry,
|
|
89
|
+
new PersistedWorkflowTokenRegistry(),
|
|
90
|
+
);
|
|
91
|
+
dependencyContainer.registerInstance(
|
|
92
|
+
CoreTokens.WorkflowActivationPolicy,
|
|
93
|
+
new AllWorkflowsActiveWorkflowActivationPolicy(),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (options.executionLimitsPolicy !== undefined) {
|
|
97
|
+
dependencyContainer.registerInstance(CoreTokens.EngineExecutionLimitsPolicy, options.executionLimitsPolicy);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
new EngineRuntimeRegistrar().register(dependencyContainer, options.registrarOptions ?? {});
|
|
101
|
+
|
|
102
|
+
const engine = dependencyContainer.resolve(Engine);
|
|
103
|
+
const runIntent = dependencyContainer.resolve(RunIntentService);
|
|
104
|
+
const workflowRunner =
|
|
105
|
+
options.workflowRunner ??
|
|
106
|
+
(dependencyContainer.resolve(CoreTokens.WorkflowRunnerService) as EngineWorkflowRunnerService);
|
|
107
|
+
dependencyContainer.registerInstance(
|
|
108
|
+
SubWorkflowRunnerNode,
|
|
109
|
+
new SubWorkflowRunnerNode(workflowRunner as WorkflowRunnerService),
|
|
110
|
+
);
|
|
111
|
+
dependencyContainer.registerInstance(
|
|
112
|
+
WorkflowTestHarnessManualTriggerNode,
|
|
113
|
+
new WorkflowTestHarnessManualTriggerNode(),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const start = async (workflows: ReadonlyArray<import("../types").WorkflowDefinition>): Promise<void> => {
|
|
117
|
+
await engine.start([...workflows]);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const runToCompletion: RegistrarEngineTestKitHandle["runToCompletion"] = async (args) => {
|
|
121
|
+
const r0 = await engine.runWorkflow(args.wf, args.startAt, args.items, args.parent);
|
|
122
|
+
if (r0.status !== "pending") return r0;
|
|
123
|
+
return await engine.waitForCompletion(r0.runId);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const runIntentStartToCompletion: RegistrarEngineTestKitHandle["runIntentStartToCompletion"] = async (args) => {
|
|
127
|
+
const r0 = await runIntent.startWorkflow({
|
|
128
|
+
workflow: args.wf,
|
|
129
|
+
startAt: args.startAt,
|
|
130
|
+
items: args.items,
|
|
131
|
+
parent: args.parent,
|
|
132
|
+
});
|
|
133
|
+
if (r0.status !== "pending") return r0;
|
|
134
|
+
return await engine.waitForCompletion(r0.runId);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
engine,
|
|
139
|
+
runIntent,
|
|
140
|
+
liveWorkflowRepository,
|
|
141
|
+
runStore,
|
|
142
|
+
triggerSetupStateRepository,
|
|
143
|
+
scheduler: scheduler as CapturingScheduler | import("../types").NodeExecutionScheduler,
|
|
144
|
+
offloadPolicy,
|
|
145
|
+
workflowRunner,
|
|
146
|
+
dependencyContainer,
|
|
147
|
+
start,
|
|
148
|
+
runToCompletion,
|
|
149
|
+
runIntentStartToCompletion,
|
|
150
|
+
makeRunId,
|
|
151
|
+
makeActivationId,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* eslint-disable codemation/single-class-per-file -- Runnable config and implementation share a TypeToken pairing. */
|
|
2
|
+
import type { WorkflowRunnerService } from "../contracts/runtimeTypes";
|
|
3
|
+
import type { TypeToken } from "../di";
|
|
4
|
+
import type {
|
|
5
|
+
Item,
|
|
6
|
+
Items,
|
|
7
|
+
NodeExecutionContext,
|
|
8
|
+
NodeId,
|
|
9
|
+
NodeOutputs,
|
|
10
|
+
Node,
|
|
11
|
+
RunnableNodeConfig,
|
|
12
|
+
WorkflowId,
|
|
13
|
+
} from "../types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Test harness subworkflow runner (mirrors integration patterns; lives under {@link "@codemation/core/testing"}).
|
|
17
|
+
*/
|
|
18
|
+
export class SubWorkflowRunnerConfig<TInputJson = unknown, TOutputJson = unknown> implements RunnableNodeConfig<
|
|
19
|
+
TInputJson,
|
|
20
|
+
TOutputJson
|
|
21
|
+
> {
|
|
22
|
+
readonly kind = "node" as const;
|
|
23
|
+
readonly type: TypeToken<unknown> = SubWorkflowRunnerNode;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
public readonly name: string,
|
|
27
|
+
public readonly args: Readonly<{
|
|
28
|
+
workflowId: WorkflowId;
|
|
29
|
+
startAt?: NodeId;
|
|
30
|
+
id?: string;
|
|
31
|
+
execution?: Readonly<{ hint?: "local" | "worker"; queue?: string }>;
|
|
32
|
+
}>,
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
get id(): string | undefined {
|
|
36
|
+
return this.args.id;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get execution(): Readonly<{ hint?: "local" | "worker"; queue?: string }> | undefined {
|
|
40
|
+
return this.args.execution;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get workflowId(): WorkflowId {
|
|
44
|
+
return this.args.workflowId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get startAt(): NodeId | undefined {
|
|
48
|
+
return this.args.startAt;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class SubWorkflowRunnerNode implements Node<SubWorkflowRunnerConfig<any, any>> {
|
|
53
|
+
readonly kind = "node" as const;
|
|
54
|
+
readonly outputPorts = ["main"] as const;
|
|
55
|
+
|
|
56
|
+
constructor(private readonly workflows: WorkflowRunnerService) {}
|
|
57
|
+
|
|
58
|
+
async execute(items: Items, ctx: NodeExecutionContext<SubWorkflowRunnerConfig<any, any>>): Promise<NodeOutputs> {
|
|
59
|
+
const out: Item[] = [];
|
|
60
|
+
for (let i = 0; i < items.length; i++) {
|
|
61
|
+
const current = items[i]!;
|
|
62
|
+
const result = await this.workflows.runById({
|
|
63
|
+
workflowId: ctx.config.workflowId,
|
|
64
|
+
startAt: ctx.config.startAt,
|
|
65
|
+
items: [current],
|
|
66
|
+
parent: {
|
|
67
|
+
runId: ctx.runId,
|
|
68
|
+
workflowId: ctx.workflowId,
|
|
69
|
+
nodeId: ctx.nodeId,
|
|
70
|
+
subworkflowDepth: ctx.subworkflowDepth,
|
|
71
|
+
engineMaxNodeActivations: ctx.engineMaxNodeActivations,
|
|
72
|
+
engineMaxSubworkflowDepth: ctx.engineMaxSubworkflowDepth,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
if (result.status !== "completed") {
|
|
76
|
+
throw new Error(`Subworkflow ${ctx.config.workflowId} did not complete (status=${result.status})`);
|
|
77
|
+
}
|
|
78
|
+
out.push(...result.outputs);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { main: out };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* eslint-disable codemation/single-class-per-file -- Trigger config and implementation share a TypeToken pairing. */
|
|
2
|
+
import type { TypeToken } from "../di";
|
|
3
|
+
import type {
|
|
4
|
+
Items,
|
|
5
|
+
NodeExecutionContext,
|
|
6
|
+
NodeOutputs,
|
|
7
|
+
TriggerNode,
|
|
8
|
+
TriggerNodeConfig,
|
|
9
|
+
TriggerSetupContext,
|
|
10
|
+
} from "../types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal pass-through manual trigger for {@link WorkflowTestKit.runNode}; emits input items unchanged.
|
|
14
|
+
*/
|
|
15
|
+
export class WorkflowTestHarnessManualTriggerConfig implements TriggerNodeConfig<unknown> {
|
|
16
|
+
readonly kind = "trigger" as const;
|
|
17
|
+
readonly type: TypeToken<unknown> = WorkflowTestHarnessManualTriggerNode;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
public readonly name: string,
|
|
21
|
+
public readonly id?: string,
|
|
22
|
+
) {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class WorkflowTestHarnessManualTriggerNode implements TriggerNode<WorkflowTestHarnessManualTriggerConfig> {
|
|
26
|
+
readonly kind = "trigger" as const;
|
|
27
|
+
readonly outputPorts = ["main"] as const;
|
|
28
|
+
|
|
29
|
+
async setup(_ctx: TriggerSetupContext<WorkflowTestHarnessManualTriggerConfig>): Promise<undefined> {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async execute(
|
|
34
|
+
items: Items,
|
|
35
|
+
_ctx: NodeExecutionContext<WorkflowTestHarnessManualTriggerConfig>,
|
|
36
|
+
): Promise<NodeOutputs> {
|
|
37
|
+
return { main: items };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TypeToken } from "../di";
|
|
2
|
+
|
|
3
|
+
export type DefinedNodeRegistrationContext = {
|
|
4
|
+
registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type DefinedNodeRegistration = {
|
|
8
|
+
register(context: DefinedNodeRegistrationContext): void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { DependencyContainer } from "tsyringe";
|
|
2
|
+
|
|
3
|
+
import type { Items, NodeId, RunnableNodeConfig, RunResult, WorkflowDefinition, WorkflowId } from "../types";
|
|
4
|
+
|
|
5
|
+
import { RegistrarEngineTestKitFactory } from "./RegistrarEngineTestKitFactory";
|
|
6
|
+
import type { EngineTestKitOptions, RegistrarEngineTestKitHandle } from "./RegistrarEngineTestKit.types";
|
|
7
|
+
import { WorkflowTestKitNodeRegistrationContextFactory } from "./WorkflowTestKitNodeRegistrationContextFactory";
|
|
8
|
+
import type { DefinedNodeRegistration } from "./WorkflowTestKit.types";
|
|
9
|
+
import { WorkflowTestKitRunNodeWorkflowFactory } from "./WorkflowTestKitRunNodeWorkflowFactory";
|
|
10
|
+
|
|
11
|
+
export type { DefinedNodeRegistration, DefinedNodeRegistrationContext } from "./WorkflowTestKit.types";
|
|
12
|
+
|
|
13
|
+
export type WorkflowTestKitOptions = EngineTestKitOptions;
|
|
14
|
+
|
|
15
|
+
export class WorkflowTestKit {
|
|
16
|
+
private readonly handle: RegistrarEngineTestKitHandle;
|
|
17
|
+
private readonly runNodeWorkflowFactory = new WorkflowTestKitRunNodeWorkflowFactory();
|
|
18
|
+
private readonly nodeRegistrationContextFactory = new WorkflowTestKitNodeRegistrationContextFactory();
|
|
19
|
+
|
|
20
|
+
constructor(options: WorkflowTestKitOptions = {}) {
|
|
21
|
+
this.handle = RegistrarEngineTestKitFactory.create(options);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get dependencyContainer(): DependencyContainer {
|
|
25
|
+
return this.handle.dependencyContainer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get engine(): RegistrarEngineTestKitHandle["engine"] {
|
|
29
|
+
return this.handle.engine;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get workflowRunner(): RegistrarEngineTestKitHandle["workflowRunner"] {
|
|
33
|
+
return this.handle.workflowRunner;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get liveWorkflowRepository(): RegistrarEngineTestKitHandle["liveWorkflowRepository"] {
|
|
37
|
+
return this.handle.liveWorkflowRepository;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get runStore(): RegistrarEngineTestKitHandle["runStore"] {
|
|
41
|
+
return this.handle.runStore;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Registers {@link import("../authoring/defineNode.types").DefinedNode} implementations on the same DI container used by the engine
|
|
46
|
+
* (same pattern as `plugin.register({ registerNode })` in the host).
|
|
47
|
+
*/
|
|
48
|
+
registerDefinedNodes(definitions: ReadonlyArray<DefinedNodeRegistration>): void {
|
|
49
|
+
const ctx = this.nodeRegistrationContextFactory.create(this.handle.dependencyContainer);
|
|
50
|
+
for (const def of definitions) {
|
|
51
|
+
def.register(ctx);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async run(args: { workflow: WorkflowDefinition; items: Items; startAt?: NodeId }): Promise<RunResult> {
|
|
56
|
+
await this.handle.start([args.workflow]);
|
|
57
|
+
return await this.handle.workflowRunner.runById({
|
|
58
|
+
workflowId: args.workflow.id,
|
|
59
|
+
startAt: args.startAt,
|
|
60
|
+
items: args.items,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async runNode(args: {
|
|
65
|
+
node: RunnableNodeConfig;
|
|
66
|
+
items: Items;
|
|
67
|
+
workflowId?: WorkflowId;
|
|
68
|
+
workflowName?: string;
|
|
69
|
+
}): Promise<RunResult> {
|
|
70
|
+
const wf = this.runNodeWorkflowFactory.build(args);
|
|
71
|
+
return await this.run({
|
|
72
|
+
workflow: wf,
|
|
73
|
+
items: args.items,
|
|
74
|
+
startAt: this.runNodeWorkflowFactory.defaultStartNodeId(),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DependencyContainer } from "tsyringe";
|
|
2
|
+
|
|
3
|
+
import type { TypeToken } from "../di";
|
|
4
|
+
|
|
5
|
+
import type { DefinedNodeRegistrationContext } from "./WorkflowTestKit.types";
|
|
6
|
+
|
|
7
|
+
export class WorkflowTestKitNodeRegistrationContextFactory {
|
|
8
|
+
create(dependencyContainer: DependencyContainer): DefinedNodeRegistrationContext {
|
|
9
|
+
return {
|
|
10
|
+
registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>) {
|
|
11
|
+
dependencyContainer.register(token, {
|
|
12
|
+
useClass: (implementation ?? token) as never,
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RunnableNodeConfig, WorkflowDefinition, WorkflowId } from "../types";
|
|
2
|
+
import { WorkflowBuilder } from "../workflow/dsl/WorkflowBuilder";
|
|
3
|
+
|
|
4
|
+
import { WorkflowTestHarnessManualTriggerConfig } from "./WorkflowTestHarnessManualTrigger";
|
|
5
|
+
|
|
6
|
+
const defaultInlineWorkflowId = "codemation.testing.workflowkit.inline" as WorkflowId;
|
|
7
|
+
const defaultInlineWorkflowName = "WorkflowTestKit inline";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Builds the minimal trigger → runnable workflow used by {@link import("./WorkflowTestKit").WorkflowTestKit.runNode}.
|
|
11
|
+
*/
|
|
12
|
+
export class WorkflowTestKitRunNodeWorkflowFactory {
|
|
13
|
+
build(args: { node: RunnableNodeConfig; workflowId?: WorkflowId; workflowName?: string }): WorkflowDefinition {
|
|
14
|
+
const workflowId = args.workflowId ?? defaultInlineWorkflowId;
|
|
15
|
+
const workflowName = args.workflowName ?? defaultInlineWorkflowName;
|
|
16
|
+
const trigger = new WorkflowTestHarnessManualTriggerConfig(
|
|
17
|
+
"WorkflowTestKit trigger",
|
|
18
|
+
"workflowkit.harness.trigger",
|
|
19
|
+
);
|
|
20
|
+
return new WorkflowBuilder({ id: workflowId, name: workflowName }).trigger(trigger).then(args.node).build();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
defaultStartNodeId(): string {
|
|
24
|
+
return "workflowkit.harness.trigger";
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/testing.ts
CHANGED
|
@@ -4,3 +4,22 @@
|
|
|
4
4
|
export { InMemoryLiveWorkflowRepository } from "./runtime/InMemoryLiveWorkflowRepository";
|
|
5
5
|
export { WorkflowSnapshotCodec as PersistedWorkflowSnapshotFactory } from "./workflowSnapshots/WorkflowSnapshotCodec";
|
|
6
6
|
export { RejectingCredentialSessionService } from "./testing/RejectingCredentialSessionService";
|
|
7
|
+
export { CapturingScheduler } from "./testing/CapturingScheduler";
|
|
8
|
+
export { PrefixedSequentialIdGenerator } from "./testing/PrefixedSequentialIdGenerator";
|
|
9
|
+
export {
|
|
10
|
+
type EngineTestKitOptions,
|
|
11
|
+
type RegistrarEngineTestKitHandle,
|
|
12
|
+
type RegistrarEngineTestKitOptions,
|
|
13
|
+
} from "./testing/RegistrarEngineTestKit.types";
|
|
14
|
+
export { RegistrarEngineTestKitFactory } from "./testing/RegistrarEngineTestKitFactory";
|
|
15
|
+
export { SubWorkflowRunnerConfig, SubWorkflowRunnerNode } from "./testing/SubWorkflowRunnerTestNode";
|
|
16
|
+
export {
|
|
17
|
+
WorkflowTestHarnessManualTriggerConfig,
|
|
18
|
+
WorkflowTestHarnessManualTriggerNode,
|
|
19
|
+
} from "./testing/WorkflowTestHarnessManualTrigger";
|
|
20
|
+
export {
|
|
21
|
+
type DefinedNodeRegistration,
|
|
22
|
+
type DefinedNodeRegistrationContext,
|
|
23
|
+
WorkflowTestKit,
|
|
24
|
+
type WorkflowTestKitOptions,
|
|
25
|
+
} from "./testing/WorkflowTestKitBuilder";
|
package/src/types/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "../contracts/NoRetryPolicy";
|
|
|
3
3
|
export * from "../contracts/RetryPolicy";
|
|
4
4
|
export * from "../contracts/ExpRetryPolicy";
|
|
5
5
|
export * from "../contracts/credentialTypes";
|
|
6
|
+
export * from "../contracts/executionPersistenceContracts";
|
|
6
7
|
export * from "../contracts/runtimeTypes";
|
|
7
8
|
export * from "../contracts/runFinishedAtFactory";
|
|
8
9
|
export * from "../contracts/runTypes";
|
|
@@ -24,6 +24,34 @@ export class ConnectionNodeIdFactory {
|
|
|
24
24
|
return nodeId.includes(`${this.connectionSegment}tool${this.connectionSegment}`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
static parseLanguageModelConnectionNodeId(nodeId: NodeId): Readonly<{ parentNodeId: NodeId }> | undefined {
|
|
28
|
+
if (!this.isLanguageModelConnectionNodeId(nodeId)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const suffix = `${this.connectionSegment}llm`;
|
|
32
|
+
const parentNodeId = nodeId.slice(0, -suffix.length);
|
|
33
|
+
return parentNodeId ? { parentNodeId } : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static parseToolConnectionNodeId(
|
|
37
|
+
nodeId: NodeId,
|
|
38
|
+
): Readonly<{ parentNodeId: NodeId; normalizedToolName: string }> | undefined {
|
|
39
|
+
if (!this.isToolConnectionNodeId(nodeId)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const marker = `${this.connectionSegment}tool${this.connectionSegment}`;
|
|
43
|
+
const idx = nodeId.lastIndexOf(marker);
|
|
44
|
+
if (idx < 0) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const parentNodeId = nodeId.slice(0, idx);
|
|
48
|
+
const normalizedToolName = nodeId.slice(idx + marker.length);
|
|
49
|
+
if (!parentNodeId || !normalizedToolName) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return { parentNodeId, normalizedToolName };
|
|
53
|
+
}
|
|
54
|
+
|
|
27
55
|
/** True when `nodeId` is a connection-owned child of `parentNodeId` (LLM or tool slot). */
|
|
28
56
|
static isConnectionOwnedDescendantOf(parentNodeId: NodeId, nodeId: NodeId): boolean {
|
|
29
57
|
return nodeId.startsWith(`${parentNodeId}${this.connectionSegment}`);
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
//#region src/workflowSnapshots/WorkflowSnapshotCodec.ts
|
|
3
|
-
var WorkflowSnapshotCodec = class {
|
|
4
|
-
constructor(tokenRegistry) {
|
|
5
|
-
this.tokenRegistry = tokenRegistry;
|
|
6
|
-
}
|
|
7
|
-
create(workflow) {
|
|
8
|
-
return {
|
|
9
|
-
id: workflow.id,
|
|
10
|
-
name: workflow.name,
|
|
11
|
-
workflowErrorHandlerConfigured: workflow.workflowErrorHandler !== void 0,
|
|
12
|
-
...workflow.connections !== void 0 && workflow.connections.length > 0 ? { connections: workflow.connections } : {},
|
|
13
|
-
nodes: workflow.nodes.map((node) => ({
|
|
14
|
-
id: node.id,
|
|
15
|
-
kind: node.kind,
|
|
16
|
-
name: node.name,
|
|
17
|
-
nodeTokenId: this.resolveTokenId(node.type),
|
|
18
|
-
configTokenId: this.resolveTokenId(node.config.type),
|
|
19
|
-
tokenName: this.resolveTokenName(node.type),
|
|
20
|
-
configTokenName: this.resolveTokenName(node.config.type),
|
|
21
|
-
config: this.serializeConfig(node.config)
|
|
22
|
-
})),
|
|
23
|
-
edges: workflow.edges.map((edge) => ({
|
|
24
|
-
from: {
|
|
25
|
-
nodeId: edge.from.nodeId,
|
|
26
|
-
output: edge.from.output
|
|
27
|
-
},
|
|
28
|
-
to: {
|
|
29
|
-
nodeId: edge.to.nodeId,
|
|
30
|
-
input: edge.to.input
|
|
31
|
-
}
|
|
32
|
-
}))
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
hydrate(snapshotNode, liveConfig) {
|
|
36
|
-
const hydrated = this.mergeValue(liveConfig, snapshotNode.config);
|
|
37
|
-
const configToken = this.tokenRegistry.resolve(snapshotNode.configTokenId);
|
|
38
|
-
Object.assign(hydrated, {
|
|
39
|
-
type: configToken ?? liveConfig.type,
|
|
40
|
-
kind: snapshotNode.kind
|
|
41
|
-
});
|
|
42
|
-
if (snapshotNode.name && !("name" in hydrated && hydrated.name)) Object.assign(hydrated, { name: snapshotNode.name });
|
|
43
|
-
return hydrated;
|
|
44
|
-
}
|
|
45
|
-
serializeConfig(config) {
|
|
46
|
-
try {
|
|
47
|
-
const cloned = JSON.parse(JSON.stringify(config));
|
|
48
|
-
this.injectTokenIds(cloned, config);
|
|
49
|
-
return cloned;
|
|
50
|
-
} catch {
|
|
51
|
-
const fallback = {
|
|
52
|
-
kind: config.kind,
|
|
53
|
-
name: config.name,
|
|
54
|
-
id: config.id,
|
|
55
|
-
icon: config.icon,
|
|
56
|
-
execution: config.execution
|
|
57
|
-
};
|
|
58
|
-
this.injectTokenIds(fallback, config);
|
|
59
|
-
return fallback;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
injectTokenIds(target, source) {
|
|
63
|
-
const type = this.asTypeToken(source.type);
|
|
64
|
-
if (type) target.tokenId = this.tokenRegistry.getTokenId(type) ?? this.resolveTokenName(type) ?? "unknown";
|
|
65
|
-
for (const [key, value] of Object.entries(source)) {
|
|
66
|
-
if (key === "type" || value == null) continue;
|
|
67
|
-
if (Array.isArray(value)) {
|
|
68
|
-
const targetArray = target[key];
|
|
69
|
-
if (Array.isArray(targetArray)) value.forEach((item, index) => {
|
|
70
|
-
if (item && typeof item === "object" && targetArray[index] && typeof targetArray[index] === "object") this.injectTokenIds(targetArray[index], item);
|
|
71
|
-
});
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (typeof value === "object") {
|
|
75
|
-
const targetValue = target[key];
|
|
76
|
-
if (targetValue && typeof targetValue === "object") this.injectTokenIds(targetValue, value);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
mergeValue(liveValue, snapshotValue) {
|
|
81
|
-
const liveRecord = this.asRecord(liveValue);
|
|
82
|
-
const snapshotRecord = this.asRecord(snapshotValue);
|
|
83
|
-
const hydrated = Object.create(liveValue && typeof liveValue === "object" ? Object.getPrototypeOf(liveValue) ?? Object.prototype : Object.prototype);
|
|
84
|
-
for (const [key, value] of Object.entries(snapshotRecord)) hydrated[key] = this.mergeNestedValue(liveRecord[key], value);
|
|
85
|
-
this.restoreNonSerializableProperties(liveRecord, hydrated);
|
|
86
|
-
this.restoreTypeProperty(hydrated);
|
|
87
|
-
return hydrated;
|
|
88
|
-
}
|
|
89
|
-
mergeNestedValue(liveValue, snapshotValue) {
|
|
90
|
-
if (Array.isArray(snapshotValue)) {
|
|
91
|
-
const liveArray = Array.isArray(liveValue) ? liveValue : [];
|
|
92
|
-
return snapshotValue.map((entry, index) => this.mergeNestedValue(liveArray[index], entry));
|
|
93
|
-
}
|
|
94
|
-
if (snapshotValue && typeof snapshotValue === "object") return this.mergeValue(liveValue, snapshotValue);
|
|
95
|
-
return snapshotValue;
|
|
96
|
-
}
|
|
97
|
-
restoreNonSerializableProperties(liveRecord, hydrated) {
|
|
98
|
-
for (const [key, value] of Object.entries(liveRecord)) if (typeof value === "function" || typeof value === "symbol") hydrated[key] = value;
|
|
99
|
-
}
|
|
100
|
-
restoreTypeProperty(record) {
|
|
101
|
-
const tokenId = typeof record.tokenId === "string" ? record.tokenId : void 0;
|
|
102
|
-
if (!tokenId) return;
|
|
103
|
-
const type = this.tokenRegistry.resolve(tokenId);
|
|
104
|
-
if (type) record.type = type;
|
|
105
|
-
}
|
|
106
|
-
resolveTokenId(token) {
|
|
107
|
-
return this.tokenRegistry.getTokenId(token) ?? this.resolveTokenName(token) ?? "unknown";
|
|
108
|
-
}
|
|
109
|
-
resolveTokenName(token) {
|
|
110
|
-
if (typeof token === "function" && token.name) return token.name;
|
|
111
|
-
if (typeof token === "string") return token;
|
|
112
|
-
}
|
|
113
|
-
asTypeToken(value) {
|
|
114
|
-
if (typeof value === "function" || typeof value === "string" || typeof value === "symbol") return value;
|
|
115
|
-
}
|
|
116
|
-
asRecord(value) {
|
|
117
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
118
|
-
return { ...value };
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
//#endregion
|
|
123
|
-
//#region src/runtime/InMemoryLiveWorkflowRepository.ts
|
|
124
|
-
var InMemoryLiveWorkflowRepository = class {
|
|
125
|
-
workflowsById = /* @__PURE__ */ new Map();
|
|
126
|
-
setWorkflows(workflows) {
|
|
127
|
-
this.workflowsById.clear();
|
|
128
|
-
for (const workflow of workflows) this.workflowsById.set(workflow.id, workflow);
|
|
129
|
-
}
|
|
130
|
-
list() {
|
|
131
|
-
return [...this.workflowsById.values()];
|
|
132
|
-
}
|
|
133
|
-
get(workflowId) {
|
|
134
|
-
return this.workflowsById.get(workflowId);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
//#endregion
|
|
139
|
-
Object.defineProperty(exports, 'InMemoryLiveWorkflowRepository', {
|
|
140
|
-
enumerable: true,
|
|
141
|
-
get: function () {
|
|
142
|
-
return InMemoryLiveWorkflowRepository;
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
Object.defineProperty(exports, 'WorkflowSnapshotCodec', {
|
|
146
|
-
enumerable: true,
|
|
147
|
-
get: function () {
|
|
148
|
-
return WorkflowSnapshotCodec;
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
//# sourceMappingURL=InMemoryLiveWorkflowRepository-BTzHpQ6e.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InMemoryLiveWorkflowRepository-BTzHpQ6e.cjs","names":["tokenRegistry: PersistedWorkflowTokenRegistryLike","fallback: Record<string, unknown>"],"sources":["../src/workflowSnapshots/WorkflowSnapshotCodec.ts","../src/runtime/InMemoryLiveWorkflowRepository.ts"],"sourcesContent":["import type { TypeToken } from \"../di\";\nimport type {\n NodeConfigBase,\n PersistedTokenId,\n PersistedWorkflowSnapshot,\n PersistedWorkflowSnapshotNode,\n PersistedWorkflowTokenRegistryLike,\n WorkflowDefinition,\n} from \"../types\";\n\nexport class WorkflowSnapshotCodec {\n constructor(private readonly tokenRegistry: PersistedWorkflowTokenRegistryLike) {}\n\n create(workflow: WorkflowDefinition): PersistedWorkflowSnapshot {\n return {\n id: workflow.id,\n name: workflow.name,\n workflowErrorHandlerConfigured: workflow.workflowErrorHandler !== undefined,\n ...(workflow.connections !== undefined && workflow.connections.length > 0\n ? { connections: workflow.connections }\n : {}),\n nodes: workflow.nodes.map((node) => ({\n id: node.id,\n kind: node.kind,\n name: node.name,\n nodeTokenId: this.resolveTokenId(node.type),\n configTokenId: this.resolveTokenId(node.config.type),\n tokenName: this.resolveTokenName(node.type),\n configTokenName: this.resolveTokenName(node.config.type),\n config: this.serializeConfig(node.config),\n })),\n edges: workflow.edges.map((edge) => ({\n from: { nodeId: edge.from.nodeId, output: edge.from.output },\n to: { nodeId: edge.to.nodeId, input: edge.to.input },\n })),\n };\n }\n\n hydrate(snapshotNode: PersistedWorkflowSnapshotNode, liveConfig: NodeConfigBase): NodeConfigBase {\n const hydrated = this.mergeValue(liveConfig, snapshotNode.config);\n const configToken = this.tokenRegistry.resolve(snapshotNode.configTokenId);\n Object.assign(hydrated, {\n type: configToken ?? liveConfig.type,\n kind: snapshotNode.kind,\n });\n if (snapshotNode.name && !(\"name\" in hydrated && hydrated.name)) {\n Object.assign(hydrated, { name: snapshotNode.name });\n }\n return hydrated as unknown as NodeConfigBase;\n }\n\n private serializeConfig(config: NodeConfigBase): unknown {\n try {\n const cloned = JSON.parse(JSON.stringify(config)) as Record<string, unknown>;\n this.injectTokenIds(cloned, config as unknown as Record<string, unknown>);\n return cloned;\n } catch {\n const fallback: Record<string, unknown> = {\n kind: config.kind,\n name: config.name,\n id: config.id,\n icon: config.icon,\n execution: config.execution,\n };\n this.injectTokenIds(fallback, config as unknown as Record<string, unknown>);\n return fallback;\n }\n }\n\n private injectTokenIds(target: Record<string, unknown>, source: Record<string, unknown>): void {\n const type = this.asTypeToken(source.type);\n if (type) {\n target.tokenId = this.tokenRegistry.getTokenId(type) ?? this.resolveTokenName(type) ?? \"unknown\";\n }\n for (const [key, value] of Object.entries(source)) {\n if (key === \"type\" || value == null) {\n continue;\n }\n if (Array.isArray(value)) {\n const targetArray = target[key];\n if (Array.isArray(targetArray)) {\n value.forEach((item, index) => {\n if (item && typeof item === \"object\" && targetArray[index] && typeof targetArray[index] === \"object\") {\n this.injectTokenIds(targetArray[index] as Record<string, unknown>, item as Record<string, unknown>);\n }\n });\n }\n continue;\n }\n if (typeof value === \"object\") {\n const targetValue = target[key];\n if (targetValue && typeof targetValue === \"object\") {\n this.injectTokenIds(targetValue as Record<string, unknown>, value as Record<string, unknown>);\n }\n }\n }\n }\n\n private mergeValue(liveValue: unknown, snapshotValue: unknown): Record<string, unknown> {\n const liveRecord = this.asRecord(liveValue);\n const snapshotRecord = this.asRecord(snapshotValue);\n const hydrated = Object.create(\n liveValue && typeof liveValue === \"object\"\n ? (Object.getPrototypeOf(liveValue) ?? Object.prototype)\n : Object.prototype,\n ) as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(snapshotRecord)) {\n hydrated[key] = this.mergeNestedValue(liveRecord[key], value);\n }\n\n this.restoreNonSerializableProperties(liveRecord, hydrated);\n this.restoreTypeProperty(hydrated);\n return hydrated;\n }\n\n private mergeNestedValue(liveValue: unknown, snapshotValue: unknown): unknown {\n if (Array.isArray(snapshotValue)) {\n const liveArray = Array.isArray(liveValue) ? liveValue : [];\n return snapshotValue.map((entry, index) => this.mergeNestedValue(liveArray[index], entry));\n }\n if (snapshotValue && typeof snapshotValue === \"object\") {\n return this.mergeValue(liveValue, snapshotValue);\n }\n return snapshotValue;\n }\n\n private restoreNonSerializableProperties(\n liveRecord: Record<string, unknown>,\n hydrated: Record<string, unknown>,\n ): void {\n for (const [key, value] of Object.entries(liveRecord)) {\n if (typeof value === \"function\" || typeof value === \"symbol\") {\n hydrated[key] = value;\n }\n }\n }\n\n private restoreTypeProperty(record: Record<string, unknown>): void {\n const tokenId = typeof record.tokenId === \"string\" ? record.tokenId : undefined;\n if (!tokenId) {\n return;\n }\n const type = this.tokenRegistry.resolve(tokenId as PersistedTokenId);\n if (type) {\n record.type = type;\n }\n }\n\n private resolveTokenId(token: TypeToken<unknown>): PersistedTokenId {\n return (this.tokenRegistry.getTokenId(token) ?? this.resolveTokenName(token) ?? \"unknown\") as PersistedTokenId;\n }\n\n private resolveTokenName(token: TypeToken<unknown>): string | undefined {\n if (typeof token === \"function\" && token.name) {\n return token.name;\n }\n if (typeof token === \"string\") {\n return token;\n }\n return undefined;\n }\n\n private asTypeToken(value: unknown): TypeToken<unknown> | undefined {\n if (typeof value === \"function\" || typeof value === \"string\" || typeof value === \"symbol\") {\n return value as TypeToken<unknown>;\n }\n return undefined;\n }\n\n private asRecord(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return {};\n }\n return { ...(value as Record<string, unknown>) };\n }\n}\n","import type { LiveWorkflowRepository, WorkflowDefinition, WorkflowId } from \"../types\";\n\nexport class InMemoryLiveWorkflowRepository implements LiveWorkflowRepository {\n private readonly workflowsById = new Map<WorkflowId, WorkflowDefinition>();\n\n setWorkflows(workflows: ReadonlyArray<WorkflowDefinition>): void {\n this.workflowsById.clear();\n for (const workflow of workflows) {\n this.workflowsById.set(workflow.id, workflow);\n }\n }\n\n list(): ReadonlyArray<WorkflowDefinition> {\n return [...this.workflowsById.values()];\n }\n\n get(workflowId: WorkflowId): WorkflowDefinition | undefined {\n return this.workflowsById.get(workflowId);\n }\n}\n"],"mappings":"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"}
|