@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.
Files changed (61) hide show
  1. package/dist/auth/index.d.ts +26 -0
  2. package/dist/auth/index.d.ts.map +1 -0
  3. package/dist/auth/index.js +137 -0
  4. package/dist/conditions.d.ts +29 -0
  5. package/dist/conditions.d.ts.map +1 -0
  6. package/dist/conditions.js +5 -0
  7. package/dist/handler/index.d.ts +104 -0
  8. package/dist/handler/index.d.ts.map +1 -0
  9. package/dist/handler/index.js +238 -0
  10. package/dist/index.d.ts +6 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +10 -0
  13. package/dist/protocol/index.d.ts +187 -0
  14. package/dist/protocol/index.d.ts.map +1 -0
  15. package/dist/protocol/index.js +7 -0
  16. package/dist/rate-limit/index.d.ts +40 -0
  17. package/dist/rate-limit/index.d.ts.map +1 -0
  18. package/dist/rate-limit/index.js +139 -0
  19. package/dist/runtime/ctx.d.ts +102 -0
  20. package/dist/runtime/ctx.d.ts.map +1 -0
  21. package/dist/runtime/ctx.js +607 -0
  22. package/dist/runtime/determinism.d.ts +19 -0
  23. package/dist/runtime/determinism.d.ts.map +1 -0
  24. package/dist/runtime/determinism.js +61 -0
  25. package/dist/runtime/errors.d.ts +21 -0
  26. package/dist/runtime/errors.d.ts.map +1 -0
  27. package/dist/runtime/errors.js +45 -0
  28. package/dist/runtime/executor.d.ts +159 -0
  29. package/dist/runtime/executor.d.ts.map +1 -0
  30. package/dist/runtime/executor.js +225 -0
  31. package/dist/runtime/journal.d.ts +55 -0
  32. package/dist/runtime/journal.d.ts.map +1 -0
  33. package/dist/runtime/journal.js +28 -0
  34. package/dist/testing/index.d.ts +117 -0
  35. package/dist/testing/index.d.ts.map +1 -0
  36. package/dist/testing/index.js +595 -0
  37. package/dist/trigger.d.ts +122 -0
  38. package/dist/trigger.d.ts.map +1 -0
  39. package/dist/trigger.js +23 -0
  40. package/dist/types.d.ts +63 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +3 -0
  43. package/dist/workflow.d.ts +212 -0
  44. package/dist/workflow.d.ts.map +1 -0
  45. package/dist/workflow.js +46 -0
  46. package/package.json +30 -30
  47. package/src/auth/index.ts +46 -52
  48. package/src/conditions.ts +13 -13
  49. package/src/handler/index.ts +110 -106
  50. package/src/index.ts +7 -7
  51. package/src/protocol/index.ts +137 -71
  52. package/src/rate-limit/index.ts +77 -78
  53. package/src/runtime/ctx.ts +354 -342
  54. package/src/runtime/determinism.ts +27 -27
  55. package/src/runtime/errors.ts +17 -17
  56. package/src/runtime/executor.ts +179 -172
  57. package/src/runtime/journal.ts +25 -25
  58. package/src/testing/index.ts +268 -202
  59. package/src/trigger.ts +64 -71
  60. package/src/types.ts +16 -18
  61. package/src/workflow.ts +154 -152
@@ -4,22 +4,22 @@
4
4
  // Corresponds to the tenant-side handler of POST /__voyant/workflow-step
5
5
  // described in docs/runtime-protocol.md §2.1.
6
6
 
7
- import type { RunTrigger, RunStatus, WaitpointKind } from "../types.js";
8
- import type { SerializedError } from "../protocol/index.js";
9
- import type { WorkflowDefinition, StepOptions } from "../workflow.js";
10
- import type { JournalSlice, StepJournalEntry } from "./journal.js";
11
- import { createClock, createRandom } from "./determinism.js";
12
- import { buildCtx, type RuntimeCallbacks, type RuntimeEnvironment } from "./ctx.js";
7
+ import type { SerializedError } from "../protocol/index.js"
8
+ import { durationToMs, type RateLimiter } from "../rate-limit/index.js"
9
+ import type { RunStatus, RunTrigger, WaitpointKind } from "../types.js"
10
+ import type { StepOptions, WorkflowDefinition } from "../workflow.js"
11
+ import { buildCtx, type RuntimeCallbacks, type RuntimeEnvironment } from "./ctx.js"
12
+ import { createClock, createRandom } from "./determinism.js"
13
13
  import {
14
- WaitpointPendingSignal,
15
- RunCancelledSignal,
16
- isWaitpointPending,
17
- isRunCancelled,
18
14
  isCompensateRequested,
19
- } from "./errors.js";
20
- import { durationToMs, type RateLimiter } from "../rate-limit/index.js";
15
+ isRunCancelled,
16
+ isWaitpointPending,
17
+ RunCancelledSignal,
18
+ WaitpointPendingSignal,
19
+ } from "./errors.js"
20
+ import type { JournalSlice, StepJournalEntry } from "./journal.js"
21
21
 
22
- export interface StepRunner {
22
+ export type StepRunner = (
23
23
  /**
24
24
  * Executes a step body and returns the journal entry to record.
25
25
  *
@@ -29,22 +29,22 @@ export interface StepRunner {
29
29
  * and use the run/workflow identity + step options to address the
30
30
  * remote container and POST the required context.
31
31
  */
32
- (args: {
33
- stepId: string;
34
- attempt: number;
35
- input: unknown;
36
- fn: (stepCtx: import("../workflow.js").StepContext) => Promise<unknown>;
37
- stepCtx: import("../workflow.js").StepContext;
32
+ args: {
33
+ stepId: string
34
+ attempt: number
35
+ input: unknown
36
+ fn: (stepCtx: import("../workflow.js").StepContext) => Promise<unknown>
37
+ stepCtx: import("../workflow.js").StepContext
38
38
  /** Identity of the run — used by dispatching runners. */
39
- runId: string;
40
- workflowId: string;
41
- workflowVersion: string;
39
+ runId: string
40
+ workflowId: string
41
+ workflowVersion: string
42
42
  /** Project / organization id from the runtime environment — used by
43
43
  * dispatching runners to resolve per-tenant bundle storage keys. */
44
- projectId: string;
45
- organizationId: string;
44
+ projectId: string
45
+ organizationId: string
46
46
  /** Merged step options (runtime, machine, timeout, …). */
47
- options: import("../workflow.js").StepOptions<unknown>;
47
+ options: import("../workflow.js").StepOptions<unknown>
48
48
  /**
49
49
  * Current journal slice at dispatch time — steps already completed,
50
50
  * waitpoints already resolved, etc. Dispatching runners pass this
@@ -52,58 +52,58 @@ export interface StepRunner {
52
52
  * cached steps, and the container can stop cleanly after the
53
53
  * target step runs.
54
54
  */
55
- journal: JournalSlice;
56
- }): Promise<StepJournalEntry>;
57
- }
55
+ journal: JournalSlice
56
+ },
57
+ ) => Promise<StepJournalEntry>
58
58
 
59
59
  export interface WaitpointRegistration {
60
- clientWaitpointId: string;
61
- kind: WaitpointKind;
62
- meta: Record<string, unknown>;
63
- timeoutMs?: number;
60
+ clientWaitpointId: string
61
+ kind: WaitpointKind
62
+ meta: Record<string, unknown>
63
+ timeoutMs?: number
64
64
  }
65
65
 
66
66
  export interface MetadataMutation {
67
- op: "set" | "increment" | "append" | "remove";
68
- key: string;
69
- value?: unknown;
70
- target?: "self" | "parent" | "root";
67
+ op: "set" | "increment" | "append" | "remove"
68
+ key: string
69
+ value?: unknown
70
+ target?: "self" | "parent" | "root"
71
71
  }
72
72
 
73
73
  export interface CompensationReport {
74
- stepId: string;
75
- status: "ok" | "err";
76
- error?: SerializedError;
77
- durationMs: number;
74
+ stepId: string
75
+ status: "ok" | "err"
76
+ error?: SerializedError
77
+ durationMs: number
78
78
  }
79
79
 
80
80
  export interface StreamChunk {
81
- streamId: string;
82
- seq: number;
83
- encoding: "text" | "json" | "base64";
84
- chunk: unknown;
85
- final: boolean;
86
- at: number;
81
+ streamId: string
82
+ seq: number
83
+ encoding: "text" | "json" | "base64"
84
+ chunk: unknown
85
+ final: boolean
86
+ at: number
87
87
  }
88
88
 
89
89
  export interface ExecuteWorkflowStepRequest {
90
- runId: string;
91
- workflowId: string;
92
- workflowVersion: string;
93
- input: unknown;
94
- journal: JournalSlice;
95
- invocationCount: number;
96
- environment: RuntimeEnvironment;
97
- triggeredBy: RunTrigger;
98
- runStartedAt: number;
99
- tags: string[];
100
- abortSignal?: AbortSignal;
90
+ runId: string
91
+ workflowId: string
92
+ workflowVersion: string
93
+ input: unknown
94
+ journal: JournalSlice
95
+ invocationCount: number
96
+ environment: RuntimeEnvironment
97
+ triggeredBy: RunTrigger
98
+ runStartedAt: number
99
+ tags: string[]
100
+ abortSignal?: AbortSignal
101
101
  /**
102
102
  * Default step executor (the "edge" runtime) — runs step bodies
103
103
  * in-process. Used for any step whose `options.runtime` is unset or
104
104
  * explicitly `"edge"`.
105
105
  */
106
- stepRunner: StepRunner;
106
+ stepRunner: StepRunner
107
107
  /**
108
108
  * Optional runner for steps declared with `options.runtime === "node"`.
109
109
  * Typical impl dispatches to a Cloudflare Container sized for the
@@ -113,7 +113,7 @@ export interface ExecuteWorkflowStepRequest {
113
113
  * `NODE_RUNTIME_UNAVAILABLE` — declaring a runtime and then silently
114
114
  * falling back to edge would hide deployment bugs.
115
115
  */
116
- nodeStepRunner?: StepRunner;
116
+ nodeStepRunner?: StepRunner
117
117
  /**
118
118
  * Optional rate limiter. When a step declares `options.rateLimit`,
119
119
  * the executor calls `rateLimiter.acquire(...)` before invoking the
@@ -121,9 +121,9 @@ export interface ExecuteWorkflowStepRequest {
121
121
  * fails with `RATE_LIMITER_MISSING` — declaring a limit and not
122
122
  * enforcing it would be silently dangerous.
123
123
  */
124
- rateLimiter?: RateLimiter;
124
+ rateLimiter?: RateLimiter
125
125
  /** `() => number` used for compensation durations. Defaults to Date.now. */
126
- now?: () => number;
126
+ now?: () => number
127
127
  /**
128
128
  * Optional per-chunk callback fired synchronously from
129
129
  * `ctx.stream.*` as each chunk is produced. Enables live streaming
@@ -131,75 +131,77 @@ export interface ExecuteWorkflowStepRequest {
131
131
  * Chunks are still accumulated in the response's `streamChunks`
132
132
  * array so the at-end delivery keeps working.
133
133
  */
134
- onStreamChunk?: (chunk: StreamChunk) => void;
134
+ onStreamChunk?: (chunk: StreamChunk) => void
135
135
  }
136
136
 
137
137
  export type ExecuteWorkflowStepResponse =
138
138
  | {
139
- status: "completed";
140
- output: unknown;
141
- metadataUpdates: MetadataMutation[];
142
- journal: JournalSlice;
143
- streamChunks: StreamChunk[];
139
+ status: "completed"
140
+ output: unknown
141
+ metadataUpdates: MetadataMutation[]
142
+ journal: JournalSlice
143
+ streamChunks: StreamChunk[]
144
144
  }
145
145
  | {
146
- status: "failed";
147
- error: SerializedError;
148
- metadataUpdates: MetadataMutation[];
149
- journal: JournalSlice;
150
- streamChunks: StreamChunk[];
146
+ status: "failed"
147
+ error: SerializedError
148
+ metadataUpdates: MetadataMutation[]
149
+ journal: JournalSlice
150
+ streamChunks: StreamChunk[]
151
151
  }
152
152
  | {
153
- status: "cancelled";
154
- metadataUpdates: MetadataMutation[];
155
- journal: JournalSlice;
156
- compensations: CompensationReport[];
157
- streamChunks: StreamChunk[];
153
+ status: "cancelled"
154
+ metadataUpdates: MetadataMutation[]
155
+ journal: JournalSlice
156
+ compensations: CompensationReport[]
157
+ streamChunks: StreamChunk[]
158
158
  }
159
159
  | {
160
- status: "waiting";
161
- waitpoints: WaitpointRegistration[];
162
- metadataUpdates: MetadataMutation[];
163
- journal: JournalSlice;
164
- streamChunks: StreamChunk[];
160
+ status: "waiting"
161
+ waitpoints: WaitpointRegistration[]
162
+ metadataUpdates: MetadataMutation[]
163
+ journal: JournalSlice
164
+ streamChunks: StreamChunk[]
165
165
  }
166
166
  | {
167
- status: "compensated";
167
+ status: "compensated"
168
168
  /** Only set when compensation was triggered by an uncaught body error. */
169
- error?: SerializedError;
170
- compensations: CompensationReport[];
171
- metadataUpdates: MetadataMutation[];
172
- journal: JournalSlice;
173
- streamChunks: StreamChunk[];
169
+ error?: SerializedError
170
+ compensations: CompensationReport[]
171
+ metadataUpdates: MetadataMutation[]
172
+ journal: JournalSlice
173
+ streamChunks: StreamChunk[]
174
174
  }
175
175
  | {
176
- status: "compensation_failed";
177
- error?: SerializedError;
178
- compensations: CompensationReport[];
179
- metadataUpdates: MetadataMutation[];
180
- journal: JournalSlice;
181
- streamChunks: StreamChunk[];
182
- };
176
+ status: "compensation_failed"
177
+ error?: SerializedError
178
+ compensations: CompensationReport[]
179
+ metadataUpdates: MetadataMutation[]
180
+ journal: JournalSlice
181
+ streamChunks: StreamChunk[]
182
+ }
183
183
 
184
184
  interface Compensable {
185
- stepId: string;
186
- output: unknown;
187
- compensate: (output: unknown) => Promise<void>;
185
+ stepId: string
186
+ output: unknown
187
+ compensate: (output: unknown) => Promise<void>
188
188
  }
189
189
 
190
190
  export async function executeWorkflowStep(
191
191
  def: WorkflowDefinition,
192
192
  req: ExecuteWorkflowStepRequest,
193
193
  ): Promise<ExecuteWorkflowStepResponse> {
194
- const abortSignal = req.abortSignal ?? new AbortController().signal;
195
- const now = req.now ?? (() => Date.now());
196
- const clock = createClock(req.runStartedAt);
197
- const random = createRandom(req.runId);
198
- const waitpoints: WaitpointRegistration[] = [];
199
- const metadataUpdates: MetadataMutation[] = [];
200
- const compensable: Compensable[] = [];
201
- const streamChunks: StreamChunk[] = [];
202
- const retryOverride: { current: import("../types.js").RetryPolicy | undefined } = { current: def.config.retry };
194
+ const abortSignal = req.abortSignal ?? new AbortController().signal
195
+ const now = req.now ?? (() => Date.now())
196
+ const clock = createClock(req.runStartedAt)
197
+ const random = createRandom(req.runId)
198
+ const waitpoints: WaitpointRegistration[] = []
199
+ const metadataUpdates: MetadataMutation[] = []
200
+ const compensable: Compensable[] = []
201
+ const streamChunks: StreamChunk[] = []
202
+ const retryOverride: { current: import("../types.js").RetryPolicy | undefined } = {
203
+ current: def.config.retry,
204
+ }
203
205
 
204
206
  const callbacks: RuntimeCallbacks = {
205
207
  invocationCount: req.invocationCount,
@@ -214,17 +216,17 @@ export async function executeWorkflowStep(
214
216
  projectId: req.environment.project.id,
215
217
  limiter: req.rateLimiter,
216
218
  signal: abortSignal,
217
- });
219
+ })
218
220
  }
219
- const runtime = args.options.runtime ?? "edge";
220
- const runner = runtime === "node" ? req.nodeStepRunner : req.stepRunner;
221
+ const runtime = args.options.runtime ?? "edge"
222
+ const runner = runtime === "node" ? req.nodeStepRunner : req.stepRunner
221
223
  if (!runner) {
222
224
  const e = new Error(
223
225
  `step "${args.stepId}" declared runtime="node" but the handler has no nodeStepRunner wired; ` +
224
226
  `pass { nodeStepRunner } to createStepHandler() or remove options.runtime`,
225
- );
226
- (e as Error & { code?: string }).code = "NODE_RUNTIME_UNAVAILABLE";
227
- throw e;
227
+ )
228
+ ;(e as Error & { code?: string }).code = "NODE_RUNTIME_UNAVAILABLE"
229
+ throw e
228
230
  }
229
231
  const entry = await runner({
230
232
  stepId: args.stepId,
@@ -239,42 +241,42 @@ export async function executeWorkflowStep(
239
241
  organizationId: req.environment.organization.id,
240
242
  options: args.options,
241
243
  journal: req.journal,
242
- });
244
+ })
243
245
  // Stamp the runtime on the journal entry so downstream consumers
244
246
  // (journal persistence, dashboard events) can report where each
245
247
  // step actually ran.
246
- entry.runtime = runtime;
247
- return entry;
248
+ entry.runtime = runtime
249
+ return entry
248
250
  },
249
251
  registerWaitpoint(args) {
250
- waitpoints.push(args);
252
+ waitpoints.push(args)
251
253
  },
252
254
  pushMetadata(op) {
253
- metadataUpdates.push(op);
255
+ metadataUpdates.push(op)
254
256
  },
255
257
  recordCompensable(args) {
256
- compensable.push(args);
258
+ compensable.push(args)
257
259
  },
258
260
  compensableLength(): number {
259
- return compensable.length;
261
+ return compensable.length
260
262
  },
261
263
  spliceCompensable(fromIndex: number): Compensable[] {
262
- return compensable.splice(fromIndex);
264
+ return compensable.splice(fromIndex)
263
265
  },
264
266
  pushStreamChunk(args) {
265
- const chunk = { ...args, at: now() };
266
- streamChunks.push(chunk);
267
+ const chunk = { ...args, at: now() }
268
+ streamChunks.push(chunk)
267
269
  // Fire the live hook synchronously. Errors are swallowed — a
268
270
  // misbehaving subscriber must not break the workflow body.
269
271
  if (req.onStreamChunk) {
270
272
  try {
271
- req.onStreamChunk(chunk);
273
+ req.onStreamChunk(chunk)
272
274
  } catch {
273
275
  /* ignore */
274
276
  }
275
277
  }
276
278
  },
277
- };
279
+ }
278
280
 
279
281
  const ctx = buildCtx({
280
282
  env: req.environment,
@@ -283,48 +285,54 @@ export async function executeWorkflowStep(
283
285
  clock,
284
286
  random,
285
287
  retryOverride,
286
- });
288
+ })
287
289
 
288
290
  try {
289
- const output = await def.config.run(req.input, ctx);
291
+ const output = await def.config.run(req.input, ctx)
290
292
  // If the body registered a waitpoint but a user try/catch swallowed
291
293
  // the internal yield signal, honour the waitpoint — the body can't
292
294
  // both register a waitpoint and complete in the same invocation.
293
295
  if (waitpoints.length > 0) {
294
- return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks };
296
+ return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks }
295
297
  }
296
- return { status: "completed", output, metadataUpdates, journal: req.journal, streamChunks };
298
+ return { status: "completed", output, metadataUpdates, journal: req.journal, streamChunks }
297
299
  } catch (err) {
298
300
  if (isWaitpointPending(err)) {
299
- return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks };
301
+ return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks }
300
302
  }
301
303
  // Same guard for the error path: a swallowed signal shouldn't let
302
304
  // the body claim failure while waitpoints are pending.
303
305
  if (waitpoints.length > 0 && !isRunCancelled(err) && !isCompensateRequested(err)) {
304
- return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks };
306
+ return { status: "waiting", waitpoints, metadataUpdates, journal: req.journal, streamChunks }
305
307
  }
306
308
  if (isRunCancelled(err)) {
307
309
  // Default: compensate on cancel. Terminal status stays `cancelled`
308
310
  // to reflect why the run ended.
309
- const compensations = await runCompensations(compensable, now);
310
- return { status: "cancelled", metadataUpdates, journal: req.journal, compensations, streamChunks };
311
+ const compensations = await runCompensations(compensable, now)
312
+ return {
313
+ status: "cancelled",
314
+ metadataUpdates,
315
+ journal: req.journal,
316
+ compensations,
317
+ streamChunks,
318
+ }
311
319
  }
312
320
  if (isCompensateRequested(err)) {
313
- const compensations = await runCompensations(compensable, now);
314
- const anyErr = compensations.some((c) => c.status === "err");
321
+ const compensations = await runCompensations(compensable, now)
322
+ const anyErr = compensations.some((c) => c.status === "err")
315
323
  return {
316
324
  status: anyErr ? "compensation_failed" : "compensated",
317
325
  compensations,
318
326
  metadataUpdates,
319
327
  journal: req.journal,
320
328
  streamChunks,
321
- };
329
+ }
322
330
  }
323
331
 
324
332
  // Uncaught user error. Run compensations LIFO; adjust terminal status
325
333
  // based on whether compensations were registered and all succeeded.
326
- const compensations = await runCompensations(compensable, now);
327
- const serialized = serializeError(err);
334
+ const compensations = await runCompensations(compensable, now)
335
+ const serialized = serializeError(err)
328
336
 
329
337
  if (compensations.length === 0) {
330
338
  return {
@@ -333,10 +341,10 @@ export async function executeWorkflowStep(
333
341
  metadataUpdates,
334
342
  journal: req.journal,
335
343
  streamChunks,
336
- };
344
+ }
337
345
  }
338
346
 
339
- const anyErr = compensations.some((c) => c.status === "err");
347
+ const anyErr = compensations.some((c) => c.status === "err")
340
348
  return {
341
349
  status: anyErr ? "compensation_failed" : "compensated",
342
350
  error: serialized,
@@ -344,7 +352,7 @@ export async function executeWorkflowStep(
344
352
  metadataUpdates,
345
353
  journal: req.journal,
346
354
  streamChunks,
347
- };
355
+ }
348
356
  }
349
357
  }
350
358
 
@@ -352,31 +360,31 @@ async function runCompensations(
352
360
  compensable: readonly Compensable[],
353
361
  now: () => number,
354
362
  ): Promise<CompensationReport[]> {
355
- const reports: CompensationReport[] = [];
363
+ const reports: CompensationReport[] = []
356
364
  // Reverse order: last-completed compensates first.
357
365
  for (let i = compensable.length - 1; i >= 0; i--) {
358
- const c = compensable[i]!;
359
- const startedAt = now();
366
+ const c = compensable[i]!
367
+ const startedAt = now()
360
368
  try {
361
- await c.compensate(c.output);
362
- reports.push({ stepId: c.stepId, status: "ok", durationMs: now() - startedAt });
369
+ await c.compensate(c.output)
370
+ reports.push({ stepId: c.stepId, status: "ok", durationMs: now() - startedAt })
363
371
  } catch (err) {
364
372
  reports.push({
365
373
  stepId: c.stepId,
366
374
  status: "err",
367
375
  error: serializeError(err),
368
376
  durationMs: now() - startedAt,
369
- });
377
+ })
370
378
  // Continue with remaining compensations; don't abort on one failure.
371
379
  }
372
380
  }
373
- return reports;
381
+ return reports
374
382
  }
375
383
 
376
384
  function serializeError(err: unknown): SerializedError {
377
385
  if (err instanceof Error) {
378
- const code = (err as { code?: string }).code;
379
- const cause = (err as { cause?: unknown }).cause;
386
+ const code = (err as { code?: string }).code
387
+ const cause = (err as { cause?: unknown }).cause
380
388
  return {
381
389
  category: "USER_ERROR",
382
390
  code: typeof code === "string" ? code : "UNKNOWN",
@@ -384,44 +392,43 @@ function serializeError(err: unknown): SerializedError {
384
392
  name: err.name,
385
393
  stack: err.stack,
386
394
  cause: cause !== undefined ? serializeError(cause) : undefined,
387
- };
395
+ }
388
396
  }
389
- return { category: "USER_ERROR", code: "UNKNOWN", message: String(err) };
397
+ return { category: "USER_ERROR", code: "UNKNOWN", message: String(err) }
390
398
  }
391
399
 
392
400
  async function acquireRateLimit(args: {
393
- spec: NonNullable<StepOptions<unknown>["rateLimit"]>;
394
- stepId: string;
395
- input: unknown;
396
- runId: string;
397
- projectId: string;
398
- limiter: RateLimiter | undefined;
399
- signal: AbortSignal;
401
+ spec: NonNullable<StepOptions<unknown>["rateLimit"]>
402
+ stepId: string
403
+ input: unknown
404
+ runId: string
405
+ projectId: string
406
+ limiter: RateLimiter | undefined
407
+ signal: AbortSignal
400
408
  }): Promise<void> {
401
- const ctx = { run: { id: args.runId }, project: { id: args.projectId } };
402
- const key =
403
- typeof args.spec.key === "function" ? args.spec.key(args.input, ctx) : args.spec.key;
409
+ const ctx = { run: { id: args.runId }, project: { id: args.projectId } }
410
+ const key = typeof args.spec.key === "function" ? args.spec.key(args.input, ctx) : args.spec.key
404
411
  const limit =
405
- typeof args.spec.limit === "function" ? args.spec.limit(args.input) : args.spec.limit;
412
+ typeof args.spec.limit === "function" ? args.spec.limit(args.input) : args.spec.limit
406
413
  const units =
407
414
  args.spec.units === undefined
408
415
  ? 1
409
416
  : typeof args.spec.units === "function"
410
417
  ? args.spec.units(args.input)
411
- : args.spec.units;
412
- const windowMs = durationToMs(args.spec.window);
413
- const onLimit = args.spec.onLimit ?? "queue";
418
+ : args.spec.units
419
+ const windowMs = durationToMs(args.spec.window)
420
+ const onLimit = args.spec.onLimit ?? "queue"
414
421
 
415
422
  if (!args.limiter) {
416
423
  const e = new Error(
417
424
  `step "${args.stepId}" declared options.rateLimit but the handler has no rateLimiter wired; ` +
418
425
  `pass { rateLimiter } to createStepHandler()`,
419
- );
420
- (e as Error & { code?: string }).code = "RATE_LIMITER_MISSING";
421
- throw e;
426
+ )
427
+ ;(e as Error & { code?: string }).code = "RATE_LIMITER_MISSING"
428
+ throw e
422
429
  }
423
- await args.limiter.acquire({ key, limit, units, windowMs, onLimit, signal: args.signal });
430
+ await args.limiter.acquire({ key, limit, units, windowMs, onLimit, signal: args.signal })
424
431
  }
425
432
 
426
- export { WaitpointPendingSignal, RunCancelledSignal };
427
- export type { RunStatus };
433
+ export type { RunStatus }
434
+ export { RunCancelledSignal, WaitpointPendingSignal }
@@ -3,45 +3,45 @@
3
3
  //
4
4
  // Wire shape defined in docs/runtime-protocol.md §2.1 (JournalSlice).
5
5
 
6
- import type { WaitpointKind } from "../types.js";
7
- import type { SerializedError } from "../protocol/index.js";
6
+ import type { SerializedError } from "../protocol/index.js"
7
+ import type { WaitpointKind } from "../types.js"
8
8
 
9
9
  export interface StepJournalEntry {
10
- attempt: number;
11
- status: "ok" | "err";
12
- output?: unknown;
13
- error?: SerializedError;
14
- startedAt: number;
15
- finishedAt: number;
10
+ attempt: number
11
+ status: "ok" | "err"
12
+ output?: unknown
13
+ error?: SerializedError
14
+ startedAt: number
15
+ finishedAt: number
16
16
  /**
17
17
  * Which runtime actually executed the step. Set by the executor when
18
18
  * routing between `stepRunner` (edge) and `nodeStepRunner`.
19
19
  * Informational only — doesn't affect replay.
20
20
  */
21
- runtime?: "edge" | "node";
21
+ runtime?: "edge" | "node"
22
22
  }
23
23
 
24
24
  export interface WaitpointResolutionEntry {
25
- kind: WaitpointKind;
26
- resolvedAt: number;
27
- matchedEventId?: string;
28
- payload?: unknown;
29
- source: "live" | "inbox" | "replay";
25
+ kind: WaitpointKind
26
+ resolvedAt: number
27
+ matchedEventId?: string
28
+ payload?: unknown
29
+ source: "live" | "inbox" | "replay"
30
30
  /** Populated for RUN waitpoints when the child run ended in a failure state. */
31
- error?: SerializedError;
31
+ error?: SerializedError
32
32
  }
33
33
 
34
34
  export interface CompensationJournalEntry {
35
- status: "ok" | "err";
36
- finishedAt: number;
37
- error?: SerializedError;
35
+ status: "ok" | "err"
36
+ finishedAt: number
37
+ error?: SerializedError
38
38
  }
39
39
 
40
40
  export interface JournalSlice {
41
- stepResults: Record<string, StepJournalEntry>;
42
- waitpointsResolved: Record<string, WaitpointResolutionEntry>;
43
- compensationsRun: Record<string, CompensationJournalEntry>;
44
- metadataState: Record<string, unknown>;
41
+ stepResults: Record<string, StepJournalEntry>
42
+ waitpointsResolved: Record<string, WaitpointResolutionEntry>
43
+ compensationsRun: Record<string, CompensationJournalEntry>
44
+ metadataState: Record<string, unknown>
45
45
  /**
46
46
  * Stream ids whose generator has already been consumed in a prior
47
47
  * invocation. The orchestrator already has the chunks on the run
@@ -49,7 +49,7 @@ export interface JournalSlice {
49
49
  * (generators often have side effects — LLM calls, file reads,
50
50
  * billable API usage).
51
51
  */
52
- streamsCompleted: Record<string, { chunkCount: number }>;
52
+ streamsCompleted: Record<string, { chunkCount: number }>
53
53
  }
54
54
 
55
55
  export function emptyJournal(): JournalSlice {
@@ -59,7 +59,7 @@ export function emptyJournal(): JournalSlice {
59
59
  compensationsRun: {},
60
60
  metadataState: {},
61
61
  streamsCompleted: {},
62
- };
62
+ }
63
63
  }
64
64
 
65
65
  /**
@@ -75,5 +75,5 @@ export function forkJournal(j: JournalSlice): JournalSlice {
75
75
  compensationsRun: { ...j.compensationsRun },
76
76
  metadataState: { ...j.metadataState },
77
77
  streamsCompleted: { ...j.streamsCompleted },
78
- };
78
+ }
79
79
  }