@voyantjs/workflows 0.6.7 → 0.6.9
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/dist/auth/index.d.ts +26 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +137 -0
- package/dist/conditions.d.ts +29 -0
- package/dist/conditions.d.ts.map +1 -0
- package/dist/conditions.js +5 -0
- package/dist/handler/index.d.ts +104 -0
- package/dist/handler/index.d.ts.map +1 -0
- package/dist/handler/index.js +238 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/protocol/index.d.ts +187 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +7 -0
- package/dist/rate-limit/index.d.ts +40 -0
- package/dist/rate-limit/index.d.ts.map +1 -0
- package/dist/rate-limit/index.js +139 -0
- package/dist/runtime/ctx.d.ts +102 -0
- package/dist/runtime/ctx.d.ts.map +1 -0
- package/dist/runtime/ctx.js +607 -0
- package/dist/runtime/determinism.d.ts +19 -0
- package/dist/runtime/determinism.d.ts.map +1 -0
- package/dist/runtime/determinism.js +61 -0
- package/dist/runtime/errors.d.ts +21 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +45 -0
- package/dist/runtime/executor.d.ts +159 -0
- package/dist/runtime/executor.d.ts.map +1 -0
- package/dist/runtime/executor.js +225 -0
- package/dist/runtime/journal.d.ts +55 -0
- package/dist/runtime/journal.d.ts.map +1 -0
- package/dist/runtime/journal.js +28 -0
- package/dist/testing/index.d.ts +117 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +595 -0
- package/dist/trigger.d.ts +122 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +23 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/workflow.d.ts +212 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +46 -0
- package/package.json +30 -30
- package/src/auth/index.ts +46 -52
- package/src/conditions.ts +13 -13
- package/src/handler/index.ts +110 -106
- package/src/index.ts +7 -7
- package/src/protocol/index.ts +137 -71
- package/src/rate-limit/index.ts +77 -78
- package/src/runtime/ctx.ts +354 -342
- package/src/runtime/determinism.ts +27 -27
- package/src/runtime/errors.ts +17 -17
- package/src/runtime/executor.ts +179 -172
- package/src/runtime/journal.ts +25 -25
- package/src/testing/index.ts +268 -202
- package/src/trigger.ts +64 -71
- package/src/types.ts +16 -18
- package/src/workflow.ts +154 -152
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare const WAITPOINT_PENDING: unique symbol;
|
|
2
|
+
declare const RUN_CANCELLED: unique symbol;
|
|
3
|
+
declare const COMPENSATE_REQUESTED: unique symbol;
|
|
4
|
+
export declare class WaitpointPendingSignal extends Error {
|
|
5
|
+
readonly [WAITPOINT_PENDING]: true;
|
|
6
|
+
readonly waitpointId: string;
|
|
7
|
+
constructor(waitpointId: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class RunCancelledSignal extends Error {
|
|
10
|
+
readonly [RUN_CANCELLED]: true;
|
|
11
|
+
constructor(reason?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare function isWaitpointPending(err: unknown): err is WaitpointPendingSignal;
|
|
14
|
+
export declare function isRunCancelled(err: unknown): err is RunCancelledSignal;
|
|
15
|
+
export declare class CompensateRequestedSignal extends Error {
|
|
16
|
+
readonly [COMPENSATE_REQUESTED]: true;
|
|
17
|
+
constructor();
|
|
18
|
+
}
|
|
19
|
+
export declare function isCompensateRequested(err: unknown): err is CompensateRequestedSignal;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/runtime/errors.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,iBAAiB,eAAkD,CAAA;AACzE,QAAA,MAAM,aAAa,eAA8C,CAAA;AACjE,QAAA,MAAM,oBAAoB,eAAqD,CAAA;AAE/E,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,CAAC,iBAAiB,CAAC,EAAG,IAAI,CAAS;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;gBAChB,WAAW,EAAE,MAAM;CAKhC;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAG,IAAI,CAAS;gBAC5B,MAAM,CAAC,EAAE,MAAM;CAI5B;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,sBAAsB,CAM9E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,kBAAkB,CAMtE;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,CAAC,oBAAoB,CAAC,EAAG,IAAI,CAAS;;CAKhD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,yBAAyB,CAMpF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Internal runtime signal errors. Distinct from user-facing `@voyantjs/workflows-errors`:
|
|
2
|
+
// these are thrown inside the executor to unwind the workflow body on
|
|
3
|
+
// waitpoint yield or run cancellation. They must not be caught by user
|
|
4
|
+
// code (the executor re-throws if it observes one being swallowed).
|
|
5
|
+
const WAITPOINT_PENDING = Symbol.for("voyant.workflows.waitpointPending");
|
|
6
|
+
const RUN_CANCELLED = Symbol.for("voyant.workflows.runCancelled");
|
|
7
|
+
const COMPENSATE_REQUESTED = Symbol.for("voyant.workflows.compensateRequested");
|
|
8
|
+
export class WaitpointPendingSignal extends Error {
|
|
9
|
+
[WAITPOINT_PENDING] = true;
|
|
10
|
+
waitpointId;
|
|
11
|
+
constructor(waitpointId) {
|
|
12
|
+
super(`waitpoint pending: ${waitpointId}`);
|
|
13
|
+
this.name = "WaitpointPendingSignal";
|
|
14
|
+
this.waitpointId = waitpointId;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class RunCancelledSignal extends Error {
|
|
18
|
+
[RUN_CANCELLED] = true;
|
|
19
|
+
constructor(reason) {
|
|
20
|
+
super(reason ?? "run cancelled");
|
|
21
|
+
this.name = "RunCancelledSignal";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function isWaitpointPending(err) {
|
|
25
|
+
return (typeof err === "object" &&
|
|
26
|
+
err !== null &&
|
|
27
|
+
err[WAITPOINT_PENDING] === true);
|
|
28
|
+
}
|
|
29
|
+
export function isRunCancelled(err) {
|
|
30
|
+
return (typeof err === "object" &&
|
|
31
|
+
err !== null &&
|
|
32
|
+
err[RUN_CANCELLED] === true);
|
|
33
|
+
}
|
|
34
|
+
export class CompensateRequestedSignal extends Error {
|
|
35
|
+
[COMPENSATE_REQUESTED] = true;
|
|
36
|
+
constructor() {
|
|
37
|
+
super("compensate requested");
|
|
38
|
+
this.name = "CompensateRequestedSignal";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function isCompensateRequested(err) {
|
|
42
|
+
return (typeof err === "object" &&
|
|
43
|
+
err !== null &&
|
|
44
|
+
err[COMPENSATE_REQUESTED] === true);
|
|
45
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { SerializedError } from "../protocol/index.js";
|
|
2
|
+
import { type RateLimiter } from "../rate-limit/index.js";
|
|
3
|
+
import type { RunStatus, RunTrigger, WaitpointKind } from "../types.js";
|
|
4
|
+
import type { WorkflowDefinition } from "../workflow.js";
|
|
5
|
+
import { type RuntimeEnvironment } from "./ctx.js";
|
|
6
|
+
import { RunCancelledSignal, WaitpointPendingSignal } from "./errors.js";
|
|
7
|
+
import type { JournalSlice, StepJournalEntry } from "./journal.js";
|
|
8
|
+
export type StepRunner = (
|
|
9
|
+
/**
|
|
10
|
+
* Executes a step body and returns the journal entry to record.
|
|
11
|
+
*
|
|
12
|
+
* In-process runners (the default edge runner, local-dev passthrough)
|
|
13
|
+
* call `fn(stepCtx)` directly. Dispatching runners (e.g. the CF
|
|
14
|
+
* Container runner) ignore `fn` — they can't serialize a closure —
|
|
15
|
+
* and use the run/workflow identity + step options to address the
|
|
16
|
+
* remote container and POST the required context.
|
|
17
|
+
*/
|
|
18
|
+
args: {
|
|
19
|
+
stepId: string;
|
|
20
|
+
attempt: number;
|
|
21
|
+
input: unknown;
|
|
22
|
+
fn: (stepCtx: import("../workflow.js").StepContext) => Promise<unknown>;
|
|
23
|
+
stepCtx: import("../workflow.js").StepContext;
|
|
24
|
+
/** Identity of the run — used by dispatching runners. */
|
|
25
|
+
runId: string;
|
|
26
|
+
workflowId: string;
|
|
27
|
+
workflowVersion: string;
|
|
28
|
+
/** Project / organization id from the runtime environment — used by
|
|
29
|
+
* dispatching runners to resolve per-tenant bundle storage keys. */
|
|
30
|
+
projectId: string;
|
|
31
|
+
organizationId: string;
|
|
32
|
+
/** Merged step options (runtime, machine, timeout, …). */
|
|
33
|
+
options: import("../workflow.js").StepOptions<unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* Current journal slice at dispatch time — steps already completed,
|
|
36
|
+
* waitpoints already resolved, etc. Dispatching runners pass this
|
|
37
|
+
* to the remote executor so body replay there short-circuits on
|
|
38
|
+
* cached steps, and the container can stop cleanly after the
|
|
39
|
+
* target step runs.
|
|
40
|
+
*/
|
|
41
|
+
journal: JournalSlice;
|
|
42
|
+
}) => Promise<StepJournalEntry>;
|
|
43
|
+
export interface WaitpointRegistration {
|
|
44
|
+
clientWaitpointId: string;
|
|
45
|
+
kind: WaitpointKind;
|
|
46
|
+
meta: Record<string, unknown>;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface MetadataMutation {
|
|
50
|
+
op: "set" | "increment" | "append" | "remove";
|
|
51
|
+
key: string;
|
|
52
|
+
value?: unknown;
|
|
53
|
+
target?: "self" | "parent" | "root";
|
|
54
|
+
}
|
|
55
|
+
export interface CompensationReport {
|
|
56
|
+
stepId: string;
|
|
57
|
+
status: "ok" | "err";
|
|
58
|
+
error?: SerializedError;
|
|
59
|
+
durationMs: number;
|
|
60
|
+
}
|
|
61
|
+
export interface StreamChunk {
|
|
62
|
+
streamId: string;
|
|
63
|
+
seq: number;
|
|
64
|
+
encoding: "text" | "json" | "base64";
|
|
65
|
+
chunk: unknown;
|
|
66
|
+
final: boolean;
|
|
67
|
+
at: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ExecuteWorkflowStepRequest {
|
|
70
|
+
runId: string;
|
|
71
|
+
workflowId: string;
|
|
72
|
+
workflowVersion: string;
|
|
73
|
+
input: unknown;
|
|
74
|
+
journal: JournalSlice;
|
|
75
|
+
invocationCount: number;
|
|
76
|
+
environment: RuntimeEnvironment;
|
|
77
|
+
triggeredBy: RunTrigger;
|
|
78
|
+
runStartedAt: number;
|
|
79
|
+
tags: string[];
|
|
80
|
+
abortSignal?: AbortSignal;
|
|
81
|
+
/**
|
|
82
|
+
* Default step executor (the "edge" runtime) — runs step bodies
|
|
83
|
+
* in-process. Used for any step whose `options.runtime` is unset or
|
|
84
|
+
* explicitly `"edge"`.
|
|
85
|
+
*/
|
|
86
|
+
stepRunner: StepRunner;
|
|
87
|
+
/**
|
|
88
|
+
* Optional runner for steps declared with `options.runtime === "node"`.
|
|
89
|
+
* Typical impl dispatches to a Cloudflare Container sized for the
|
|
90
|
+
* step (or, in local dev, an in-process passthrough).
|
|
91
|
+
*
|
|
92
|
+
* If a step requests `"node"` and this is unset, the step fails with
|
|
93
|
+
* `NODE_RUNTIME_UNAVAILABLE` — declaring a runtime and then silently
|
|
94
|
+
* falling back to edge would hide deployment bugs.
|
|
95
|
+
*/
|
|
96
|
+
nodeStepRunner?: StepRunner;
|
|
97
|
+
/**
|
|
98
|
+
* Optional rate limiter. When a step declares `options.rateLimit`,
|
|
99
|
+
* the executor calls `rateLimiter.acquire(...)` before invoking the
|
|
100
|
+
* step runner. Without a limiter, a step that declares `rateLimit`
|
|
101
|
+
* fails with `RATE_LIMITER_MISSING` — declaring a limit and not
|
|
102
|
+
* enforcing it would be silently dangerous.
|
|
103
|
+
*/
|
|
104
|
+
rateLimiter?: RateLimiter;
|
|
105
|
+
/** `() => number` used for compensation durations. Defaults to Date.now. */
|
|
106
|
+
now?: () => number;
|
|
107
|
+
/**
|
|
108
|
+
* Optional per-chunk callback fired synchronously from
|
|
109
|
+
* `ctx.stream.*` as each chunk is produced. Enables live streaming
|
|
110
|
+
* (dashboards, queues) in-process before the invocation completes.
|
|
111
|
+
* Chunks are still accumulated in the response's `streamChunks`
|
|
112
|
+
* array so the at-end delivery keeps working.
|
|
113
|
+
*/
|
|
114
|
+
onStreamChunk?: (chunk: StreamChunk) => void;
|
|
115
|
+
}
|
|
116
|
+
export type ExecuteWorkflowStepResponse = {
|
|
117
|
+
status: "completed";
|
|
118
|
+
output: unknown;
|
|
119
|
+
metadataUpdates: MetadataMutation[];
|
|
120
|
+
journal: JournalSlice;
|
|
121
|
+
streamChunks: StreamChunk[];
|
|
122
|
+
} | {
|
|
123
|
+
status: "failed";
|
|
124
|
+
error: SerializedError;
|
|
125
|
+
metadataUpdates: MetadataMutation[];
|
|
126
|
+
journal: JournalSlice;
|
|
127
|
+
streamChunks: StreamChunk[];
|
|
128
|
+
} | {
|
|
129
|
+
status: "cancelled";
|
|
130
|
+
metadataUpdates: MetadataMutation[];
|
|
131
|
+
journal: JournalSlice;
|
|
132
|
+
compensations: CompensationReport[];
|
|
133
|
+
streamChunks: StreamChunk[];
|
|
134
|
+
} | {
|
|
135
|
+
status: "waiting";
|
|
136
|
+
waitpoints: WaitpointRegistration[];
|
|
137
|
+
metadataUpdates: MetadataMutation[];
|
|
138
|
+
journal: JournalSlice;
|
|
139
|
+
streamChunks: StreamChunk[];
|
|
140
|
+
} | {
|
|
141
|
+
status: "compensated";
|
|
142
|
+
/** Only set when compensation was triggered by an uncaught body error. */
|
|
143
|
+
error?: SerializedError;
|
|
144
|
+
compensations: CompensationReport[];
|
|
145
|
+
metadataUpdates: MetadataMutation[];
|
|
146
|
+
journal: JournalSlice;
|
|
147
|
+
streamChunks: StreamChunk[];
|
|
148
|
+
} | {
|
|
149
|
+
status: "compensation_failed";
|
|
150
|
+
error?: SerializedError;
|
|
151
|
+
compensations: CompensationReport[];
|
|
152
|
+
metadataUpdates: MetadataMutation[];
|
|
153
|
+
journal: JournalSlice;
|
|
154
|
+
streamChunks: StreamChunk[];
|
|
155
|
+
};
|
|
156
|
+
export declare function executeWorkflowStep(def: WorkflowDefinition, req: ExecuteWorkflowStepRequest): Promise<ExecuteWorkflowStepResponse>;
|
|
157
|
+
export type { RunStatus };
|
|
158
|
+
export { RunCancelledSignal, WaitpointPendingSignal };
|
|
159
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/runtime/executor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAe,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAmC,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAEnF,OAAO,EAIL,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAElE,MAAM,MAAM,UAAU,GAAG;AACvB;;;;;;;;GAQG;AACH,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;IACd,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,gBAAgB,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACvE,OAAO,EAAE,OAAO,gBAAgB,EAAE,WAAW,CAAA;IAC7C,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB;yEACqE;IACrE,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,0DAA0D;IAC1D,OAAO,EAAE,OAAO,gBAAgB,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACtD;;;;;;OAMG;IACH,OAAO,EAAE,YAAY,CAAA;CACtB,KACE,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE9B,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,MAAM,CAAA;IACzB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,KAAK,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAC7C,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,IAAI,GAAG,KAAK,CAAA;IACpB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAA;IACpC,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,EAAE,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,YAAY,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,kBAAkB,CAAA;IAC/B,WAAW,EAAE,UAAU,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;OAIG;IACH,UAAU,EAAE,UAAU,CAAA;IACtB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,UAAU,CAAA;IAC3B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;CAC7C;AAED,MAAM,MAAM,2BAA2B,GACnC;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,eAAe,EAAE,gBAAgB,EAAE,CAAA;IACnC,OAAO,EAAE,YAAY,CAAA;IACrB,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B,GACD;IACE,MAAM,EAAE,QAAQ,CAAA;IAChB,KAAK,EAAE,eAAe,CAAA;IACtB,eAAe,EAAE,gBAAgB,EAAE,CAAA;IACnC,OAAO,EAAE,YAAY,CAAA;IACrB,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B,GACD;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,eAAe,EAAE,gBAAgB,EAAE,CAAA;IACnC,OAAO,EAAE,YAAY,CAAA;IACrB,aAAa,EAAE,kBAAkB,EAAE,CAAA;IACnC,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B,GACD;IACE,MAAM,EAAE,SAAS,CAAA;IACjB,UAAU,EAAE,qBAAqB,EAAE,CAAA;IACnC,eAAe,EAAE,gBAAgB,EAAE,CAAA;IACnC,OAAO,EAAE,YAAY,CAAA;IACrB,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B,GACD;IACE,MAAM,EAAE,aAAa,CAAA;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,aAAa,EAAE,kBAAkB,EAAE,CAAA;IACnC,eAAe,EAAE,gBAAgB,EAAE,CAAA;IACnC,OAAO,EAAE,YAAY,CAAA;IACrB,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B,GACD;IACE,MAAM,EAAE,qBAAqB,CAAA;IAC7B,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,aAAa,EAAE,kBAAkB,EAAE,CAAA;IACnC,eAAe,EAAE,gBAAgB,EAAE,CAAA;IACnC,OAAO,EAAE,YAAY,CAAA;IACrB,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B,CAAA;AAQL,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,0BAA0B,GAC9B,OAAO,CAAC,2BAA2B,CAAC,CAoKtC;AA4ED,YAAY,EAAE,SAAS,EAAE,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAA"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// The workflow-step executor. Takes a registered workflow + journal and
|
|
2
|
+
// runs the body exactly once, yielding a response envelope.
|
|
3
|
+
//
|
|
4
|
+
// Corresponds to the tenant-side handler of POST /__voyant/workflow-step
|
|
5
|
+
// described in docs/runtime-protocol.md §2.1.
|
|
6
|
+
import { durationToMs } from "../rate-limit/index.js";
|
|
7
|
+
import { buildCtx } from "./ctx.js";
|
|
8
|
+
import { createClock, createRandom } from "./determinism.js";
|
|
9
|
+
import { isCompensateRequested, isRunCancelled, isWaitpointPending, RunCancelledSignal, WaitpointPendingSignal, } from "./errors.js";
|
|
10
|
+
export async function executeWorkflowStep(def, req) {
|
|
11
|
+
const abortSignal = req.abortSignal ?? new AbortController().signal;
|
|
12
|
+
const now = req.now ?? (() => Date.now());
|
|
13
|
+
const clock = createClock(req.runStartedAt);
|
|
14
|
+
const random = createRandom(req.runId);
|
|
15
|
+
const waitpoints = [];
|
|
16
|
+
const metadataUpdates = [];
|
|
17
|
+
const compensable = [];
|
|
18
|
+
const streamChunks = [];
|
|
19
|
+
const retryOverride = {
|
|
20
|
+
current: def.config.retry,
|
|
21
|
+
};
|
|
22
|
+
const callbacks = {
|
|
23
|
+
invocationCount: req.invocationCount,
|
|
24
|
+
abortSignal,
|
|
25
|
+
async runStep(args) {
|
|
26
|
+
if (args.options.rateLimit) {
|
|
27
|
+
await acquireRateLimit({
|
|
28
|
+
spec: args.options.rateLimit,
|
|
29
|
+
stepId: args.stepId,
|
|
30
|
+
input: req.input,
|
|
31
|
+
runId: req.runId,
|
|
32
|
+
projectId: req.environment.project.id,
|
|
33
|
+
limiter: req.rateLimiter,
|
|
34
|
+
signal: abortSignal,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const runtime = args.options.runtime ?? "edge";
|
|
38
|
+
const runner = runtime === "node" ? req.nodeStepRunner : req.stepRunner;
|
|
39
|
+
if (!runner) {
|
|
40
|
+
const e = new Error(`step "${args.stepId}" declared runtime="node" but the handler has no nodeStepRunner wired; ` +
|
|
41
|
+
`pass { nodeStepRunner } to createStepHandler() or remove options.runtime`);
|
|
42
|
+
e.code = "NODE_RUNTIME_UNAVAILABLE";
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
const entry = await runner({
|
|
46
|
+
stepId: args.stepId,
|
|
47
|
+
attempt: args.attempt,
|
|
48
|
+
input: args.input,
|
|
49
|
+
fn: args.fn,
|
|
50
|
+
stepCtx: args.stepCtx,
|
|
51
|
+
runId: req.runId,
|
|
52
|
+
workflowId: req.workflowId,
|
|
53
|
+
workflowVersion: req.workflowVersion,
|
|
54
|
+
projectId: req.environment.project.id,
|
|
55
|
+
organizationId: req.environment.organization.id,
|
|
56
|
+
options: args.options,
|
|
57
|
+
journal: req.journal,
|
|
58
|
+
});
|
|
59
|
+
// Stamp the runtime on the journal entry so downstream consumers
|
|
60
|
+
// (journal persistence, dashboard events) can report where each
|
|
61
|
+
// step actually ran.
|
|
62
|
+
entry.runtime = runtime;
|
|
63
|
+
return entry;
|
|
64
|
+
},
|
|
65
|
+
registerWaitpoint(args) {
|
|
66
|
+
waitpoints.push(args);
|
|
67
|
+
},
|
|
68
|
+
pushMetadata(op) {
|
|
69
|
+
metadataUpdates.push(op);
|
|
70
|
+
},
|
|
71
|
+
recordCompensable(args) {
|
|
72
|
+
compensable.push(args);
|
|
73
|
+
},
|
|
74
|
+
compensableLength() {
|
|
75
|
+
return compensable.length;
|
|
76
|
+
},
|
|
77
|
+
spliceCompensable(fromIndex) {
|
|
78
|
+
return compensable.splice(fromIndex);
|
|
79
|
+
},
|
|
80
|
+
pushStreamChunk(args) {
|
|
81
|
+
const chunk = { ...args, at: now() };
|
|
82
|
+
streamChunks.push(chunk);
|
|
83
|
+
// Fire the live hook synchronously. Errors are swallowed — a
|
|
84
|
+
// misbehaving subscriber must not break the workflow body.
|
|
85
|
+
if (req.onStreamChunk) {
|
|
86
|
+
try {
|
|
87
|
+
req.onStreamChunk(chunk);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* ignore */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
const ctx = buildCtx({
|
|
96
|
+
env: req.environment,
|
|
97
|
+
journal: req.journal,
|
|
98
|
+
callbacks,
|
|
99
|
+
clock,
|
|
100
|
+
random,
|
|
101
|
+
retryOverride,
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
const output = await def.config.run(req.input, ctx);
|
|
105
|
+
// If the body registered a waitpoint but a user try/catch swallowed
|
|
106
|
+
// the internal yield signal, honour the waitpoint — the body can't
|
|
107
|
+
// both register a waitpoint and complete in the same invocation.
|
|
108
|
+
if (waitpoints.length > 0) {
|
|
109
|
+
return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks };
|
|
110
|
+
}
|
|
111
|
+
return { status: "completed", output, metadataUpdates, journal: req.journal, streamChunks };
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (isWaitpointPending(err)) {
|
|
115
|
+
return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks };
|
|
116
|
+
}
|
|
117
|
+
// Same guard for the error path: a swallowed signal shouldn't let
|
|
118
|
+
// the body claim failure while waitpoints are pending.
|
|
119
|
+
if (waitpoints.length > 0 && !isRunCancelled(err) && !isCompensateRequested(err)) {
|
|
120
|
+
return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks };
|
|
121
|
+
}
|
|
122
|
+
if (isRunCancelled(err)) {
|
|
123
|
+
// Default: compensate on cancel. Terminal status stays `cancelled`
|
|
124
|
+
// to reflect why the run ended.
|
|
125
|
+
const compensations = await runCompensations(compensable, now);
|
|
126
|
+
return {
|
|
127
|
+
status: "cancelled",
|
|
128
|
+
metadataUpdates,
|
|
129
|
+
journal: req.journal,
|
|
130
|
+
compensations,
|
|
131
|
+
streamChunks,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (isCompensateRequested(err)) {
|
|
135
|
+
const compensations = await runCompensations(compensable, now);
|
|
136
|
+
const anyErr = compensations.some((c) => c.status === "err");
|
|
137
|
+
return {
|
|
138
|
+
status: anyErr ? "compensation_failed" : "compensated",
|
|
139
|
+
compensations,
|
|
140
|
+
metadataUpdates,
|
|
141
|
+
journal: req.journal,
|
|
142
|
+
streamChunks,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// Uncaught user error. Run compensations LIFO; adjust terminal status
|
|
146
|
+
// based on whether compensations were registered and all succeeded.
|
|
147
|
+
const compensations = await runCompensations(compensable, now);
|
|
148
|
+
const serialized = serializeError(err);
|
|
149
|
+
if (compensations.length === 0) {
|
|
150
|
+
return {
|
|
151
|
+
status: "failed",
|
|
152
|
+
error: serialized,
|
|
153
|
+
metadataUpdates,
|
|
154
|
+
journal: req.journal,
|
|
155
|
+
streamChunks,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const anyErr = compensations.some((c) => c.status === "err");
|
|
159
|
+
return {
|
|
160
|
+
status: anyErr ? "compensation_failed" : "compensated",
|
|
161
|
+
error: serialized,
|
|
162
|
+
compensations,
|
|
163
|
+
metadataUpdates,
|
|
164
|
+
journal: req.journal,
|
|
165
|
+
streamChunks,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function runCompensations(compensable, now) {
|
|
170
|
+
const reports = [];
|
|
171
|
+
// Reverse order: last-completed compensates first.
|
|
172
|
+
for (let i = compensable.length - 1; i >= 0; i--) {
|
|
173
|
+
const c = compensable[i];
|
|
174
|
+
const startedAt = now();
|
|
175
|
+
try {
|
|
176
|
+
await c.compensate(c.output);
|
|
177
|
+
reports.push({ stepId: c.stepId, status: "ok", durationMs: now() - startedAt });
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
reports.push({
|
|
181
|
+
stepId: c.stepId,
|
|
182
|
+
status: "err",
|
|
183
|
+
error: serializeError(err),
|
|
184
|
+
durationMs: now() - startedAt,
|
|
185
|
+
});
|
|
186
|
+
// Continue with remaining compensations; don't abort on one failure.
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return reports;
|
|
190
|
+
}
|
|
191
|
+
function serializeError(err) {
|
|
192
|
+
if (err instanceof Error) {
|
|
193
|
+
const code = err.code;
|
|
194
|
+
const cause = err.cause;
|
|
195
|
+
return {
|
|
196
|
+
category: "USER_ERROR",
|
|
197
|
+
code: typeof code === "string" ? code : "UNKNOWN",
|
|
198
|
+
message: err.message,
|
|
199
|
+
name: err.name,
|
|
200
|
+
stack: err.stack,
|
|
201
|
+
cause: cause !== undefined ? serializeError(cause) : undefined,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return { category: "USER_ERROR", code: "UNKNOWN", message: String(err) };
|
|
205
|
+
}
|
|
206
|
+
async function acquireRateLimit(args) {
|
|
207
|
+
const ctx = { run: { id: args.runId }, project: { id: args.projectId } };
|
|
208
|
+
const key = typeof args.spec.key === "function" ? args.spec.key(args.input, ctx) : args.spec.key;
|
|
209
|
+
const limit = typeof args.spec.limit === "function" ? args.spec.limit(args.input) : args.spec.limit;
|
|
210
|
+
const units = args.spec.units === undefined
|
|
211
|
+
? 1
|
|
212
|
+
: typeof args.spec.units === "function"
|
|
213
|
+
? args.spec.units(args.input)
|
|
214
|
+
: args.spec.units;
|
|
215
|
+
const windowMs = durationToMs(args.spec.window);
|
|
216
|
+
const onLimit = args.spec.onLimit ?? "queue";
|
|
217
|
+
if (!args.limiter) {
|
|
218
|
+
const e = new Error(`step "${args.stepId}" declared options.rateLimit but the handler has no rateLimiter wired; ` +
|
|
219
|
+
`pass { rateLimiter } to createStepHandler()`);
|
|
220
|
+
e.code = "RATE_LIMITER_MISSING";
|
|
221
|
+
throw e;
|
|
222
|
+
}
|
|
223
|
+
await args.limiter.acquire({ key, limit, units, windowMs, onLimit, signal: args.signal });
|
|
224
|
+
}
|
|
225
|
+
export { RunCancelledSignal, WaitpointPendingSignal };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { SerializedError } from "../protocol/index.js";
|
|
2
|
+
import type { WaitpointKind } from "../types.js";
|
|
3
|
+
export interface StepJournalEntry {
|
|
4
|
+
attempt: number;
|
|
5
|
+
status: "ok" | "err";
|
|
6
|
+
output?: unknown;
|
|
7
|
+
error?: SerializedError;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
finishedAt: number;
|
|
10
|
+
/**
|
|
11
|
+
* Which runtime actually executed the step. Set by the executor when
|
|
12
|
+
* routing between `stepRunner` (edge) and `nodeStepRunner`.
|
|
13
|
+
* Informational only — doesn't affect replay.
|
|
14
|
+
*/
|
|
15
|
+
runtime?: "edge" | "node";
|
|
16
|
+
}
|
|
17
|
+
export interface WaitpointResolutionEntry {
|
|
18
|
+
kind: WaitpointKind;
|
|
19
|
+
resolvedAt: number;
|
|
20
|
+
matchedEventId?: string;
|
|
21
|
+
payload?: unknown;
|
|
22
|
+
source: "live" | "inbox" | "replay";
|
|
23
|
+
/** Populated for RUN waitpoints when the child run ended in a failure state. */
|
|
24
|
+
error?: SerializedError;
|
|
25
|
+
}
|
|
26
|
+
export interface CompensationJournalEntry {
|
|
27
|
+
status: "ok" | "err";
|
|
28
|
+
finishedAt: number;
|
|
29
|
+
error?: SerializedError;
|
|
30
|
+
}
|
|
31
|
+
export interface JournalSlice {
|
|
32
|
+
stepResults: Record<string, StepJournalEntry>;
|
|
33
|
+
waitpointsResolved: Record<string, WaitpointResolutionEntry>;
|
|
34
|
+
compensationsRun: Record<string, CompensationJournalEntry>;
|
|
35
|
+
metadataState: Record<string, unknown>;
|
|
36
|
+
/**
|
|
37
|
+
* Stream ids whose generator has already been consumed in a prior
|
|
38
|
+
* invocation. The orchestrator already has the chunks on the run
|
|
39
|
+
* record; replaying the body must skip re-iterating the source
|
|
40
|
+
* (generators often have side effects — LLM calls, file reads,
|
|
41
|
+
* billable API usage).
|
|
42
|
+
*/
|
|
43
|
+
streamsCompleted: Record<string, {
|
|
44
|
+
chunkCount: number;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare function emptyJournal(): JournalSlice;
|
|
48
|
+
/**
|
|
49
|
+
* Clone the journal into a slice that the body sees. Each step /
|
|
50
|
+
* waitpoint the body replays is *consumed* from the slice so we can
|
|
51
|
+
* detect leftover journal entries that the code no longer produces
|
|
52
|
+
* (which is a versioning hazard — see §6.7 of design.md).
|
|
53
|
+
*/
|
|
54
|
+
export declare function forkJournal(j: JournalSlice): JournalSlice;
|
|
55
|
+
//# sourceMappingURL=journal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../../src/runtime/journal.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,IAAI,GAAG,KAAK,CAAA;IACpB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;IACnC,gFAAgF;IAChF,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,IAAI,GAAG,KAAK,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IAC7C,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAA;IAC5D,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAA;IAC1D,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACzD;AAED,wBAAgB,YAAY,IAAI,YAAY,CAQ3C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,YAAY,GAAG,YAAY,CAQzD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Tenant-side view of the journal sent by the orchestrator on every
|
|
2
|
+
// `/__voyant/workflow-step` invocation.
|
|
3
|
+
//
|
|
4
|
+
// Wire shape defined in docs/runtime-protocol.md §2.1 (JournalSlice).
|
|
5
|
+
export function emptyJournal() {
|
|
6
|
+
return {
|
|
7
|
+
stepResults: {},
|
|
8
|
+
waitpointsResolved: {},
|
|
9
|
+
compensationsRun: {},
|
|
10
|
+
metadataState: {},
|
|
11
|
+
streamsCompleted: {},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Clone the journal into a slice that the body sees. Each step /
|
|
16
|
+
* waitpoint the body replays is *consumed* from the slice so we can
|
|
17
|
+
* detect leftover journal entries that the code no longer produces
|
|
18
|
+
* (which is a versioning hazard — see §6.7 of design.md).
|
|
19
|
+
*/
|
|
20
|
+
export function forkJournal(j) {
|
|
21
|
+
return {
|
|
22
|
+
stepResults: { ...j.stepResults },
|
|
23
|
+
waitpointsResolved: { ...j.waitpointsResolved },
|
|
24
|
+
compensationsRun: { ...j.compensationsRun },
|
|
25
|
+
metadataState: { ...j.metadataState },
|
|
26
|
+
streamsCompleted: { ...j.streamsCompleted },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { SerializedError } from "../protocol/index.js";
|
|
2
|
+
import { type CompensationReport, type StreamChunk, type WaitpointRegistration } from "../runtime/executor.js";
|
|
3
|
+
import type { JournalSlice } from "../runtime/journal.js";
|
|
4
|
+
import type { RunStatus } from "../types.js";
|
|
5
|
+
import type { EnvironmentContext, MetadataValue, StepContext, WorkflowHandle } from "../workflow.js";
|
|
6
|
+
export interface TestOptions<_TIn> {
|
|
7
|
+
/** Map of stepId → function run in place of the real step body. */
|
|
8
|
+
steps?: Record<string, (stepCtx: StepContext) => unknown | Promise<unknown>>;
|
|
9
|
+
/**
|
|
10
|
+
* Map of eventType → payload (or payload array). First
|
|
11
|
+
* match-per-eventType wins.
|
|
12
|
+
*/
|
|
13
|
+
waitForEvent?: Record<string, unknown | unknown[]>;
|
|
14
|
+
/** Map of signalName → payload. */
|
|
15
|
+
waitForSignal?: Record<string, unknown>;
|
|
16
|
+
/** Map of tokenId → payload. */
|
|
17
|
+
waitForToken?: Record<string, unknown>;
|
|
18
|
+
/** Workflow invoke stubs keyed by child workflow id. */
|
|
19
|
+
invoke?: Record<string, unknown>;
|
|
20
|
+
/** Fake env bindings, passed through to `ctx.environment` for tests. */
|
|
21
|
+
env?: Record<string, unknown>;
|
|
22
|
+
environment?: Partial<EnvironmentContext>;
|
|
23
|
+
/** Fixed wall-clock basis for the run. Defaults to Date.now(). */
|
|
24
|
+
now?: () => number;
|
|
25
|
+
random?: () => number;
|
|
26
|
+
/** Max resumption cycles. Defaults to 16 — guards runaway loops. */
|
|
27
|
+
maxInvocations?: number;
|
|
28
|
+
/**
|
|
29
|
+
* When true, the harness stops and returns a "waiting" TestResult as
|
|
30
|
+
* soon as any registered waitpoint has no fixture resolution (instead
|
|
31
|
+
* of throwing). Callers can then persist the run and resume it later
|
|
32
|
+
* via `resumeWorkflowForTest` once the waitpoint is resolved from a
|
|
33
|
+
* live source (e.g. a dashboard HTTP injection).
|
|
34
|
+
*
|
|
35
|
+
* DATETIME waitpoints (sleeps) are always auto-resolved regardless of
|
|
36
|
+
* this flag, since a local dev loop is not the right place to
|
|
37
|
+
* synthesize wall-clock delays.
|
|
38
|
+
*/
|
|
39
|
+
pauseOnWait?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface TestResult<TOut> {
|
|
42
|
+
status: Extract<RunStatus, "completed" | "failed" | "cancelled" | "compensated" | "compensation_failed" | "waiting">;
|
|
43
|
+
output?: TOut;
|
|
44
|
+
error?: {
|
|
45
|
+
category: SerializedError["category"];
|
|
46
|
+
code: string;
|
|
47
|
+
message: string;
|
|
48
|
+
};
|
|
49
|
+
steps: {
|
|
50
|
+
id: string;
|
|
51
|
+
status: "ok" | "err" | "skipped";
|
|
52
|
+
duration: number;
|
|
53
|
+
output?: unknown;
|
|
54
|
+
}[];
|
|
55
|
+
events: {
|
|
56
|
+
type: string;
|
|
57
|
+
at: number;
|
|
58
|
+
data: unknown;
|
|
59
|
+
}[];
|
|
60
|
+
metadata: Record<string, MetadataValue>;
|
|
61
|
+
compensations: CompensationReport[];
|
|
62
|
+
/** Chunks emitted via `ctx.stream()` / `ctx.stream.{text,json,bytes}`, grouped by streamId in emission order. */
|
|
63
|
+
streams: Record<string, StreamChunk[]>;
|
|
64
|
+
invocations: number;
|
|
65
|
+
/**
|
|
66
|
+
* Populated when `status === "waiting"`. Holds the persisted executor
|
|
67
|
+
* state needed to resume the run via `resumeWorkflowForTest`.
|
|
68
|
+
*/
|
|
69
|
+
pause?: {
|
|
70
|
+
journal: JournalSlice;
|
|
71
|
+
pendingWaitpoints: WaitpointRegistration[];
|
|
72
|
+
startedAt: number;
|
|
73
|
+
invocationCount: number;
|
|
74
|
+
metadataAppliedCount: number;
|
|
75
|
+
fixtureCursors: {
|
|
76
|
+
event: Record<string, number>;
|
|
77
|
+
signal: Record<string, number>;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export declare function runWorkflowForTest<TIn, TOut>(workflow: WorkflowHandle<TIn, TOut>, input: TIn, opts?: TestOptions<TIn>): Promise<TestResult<TOut>>;
|
|
82
|
+
/**
|
|
83
|
+
* A single injected waitpoint resolution — the tenant-side analogue of
|
|
84
|
+
* the orchestrator delivering an event, signal, or token payload to a
|
|
85
|
+
* parked run. The matcher (`kind` + `key`) identifies which pending
|
|
86
|
+
* waitpoint to resolve; `payload` is the value surfaced to the body.
|
|
87
|
+
*/
|
|
88
|
+
export type WaitpointInjection = {
|
|
89
|
+
kind: "EVENT";
|
|
90
|
+
eventType: string;
|
|
91
|
+
payload?: unknown;
|
|
92
|
+
} | {
|
|
93
|
+
kind: "SIGNAL";
|
|
94
|
+
name: string;
|
|
95
|
+
payload?: unknown;
|
|
96
|
+
} | {
|
|
97
|
+
kind: "MANUAL";
|
|
98
|
+
tokenId: string;
|
|
99
|
+
payload?: unknown;
|
|
100
|
+
};
|
|
101
|
+
export interface ResumeOptions<TIn> extends TestOptions<TIn> {
|
|
102
|
+
/** Persisted pause state returned from a previous `runWorkflowForTest` or `resumeWorkflowForTest`. */
|
|
103
|
+
pause: NonNullable<TestResult<unknown>["pause"]>;
|
|
104
|
+
/** The single waitpoint resolution to inject on resume. */
|
|
105
|
+
injection: WaitpointInjection;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resume a parked run. Matches the injection against one of the
|
|
109
|
+
* `pause.pendingWaitpoints`, records it in the journal, and re-enters
|
|
110
|
+
* the executor loop until the next pause or terminal state.
|
|
111
|
+
*/
|
|
112
|
+
export declare function resumeWorkflowForTest<TIn, TOut>(workflow: WorkflowHandle<TIn, TOut>, input: TIn, opts: ResumeOptions<TIn>): Promise<TestResult<TOut>>;
|
|
113
|
+
export interface FixtureCursors {
|
|
114
|
+
event: Map<string, number>;
|
|
115
|
+
signal: Map<string, number>;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=index.d.ts.map
|