@voyantjs/workflows 0.0.0 → 0.6.8

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
package/src/auth/index.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  // const verify = await createHmacVerifier(env.VOYANT_SIGNING_KEY);
23
23
  // export default { fetch: createStepHandler({ verifyRequest: verify }) };
24
24
 
25
- export const AUTH_HEADER = "x-voyant-dispatch-auth" as const;
25
+ export const AUTH_HEADER = "x-voyant-dispatch-auth" as const
26
26
 
27
27
  /**
28
28
  * Returns a verifier that accepts `Authorization: Bearer <token>`
@@ -34,45 +34,41 @@ export const AUTH_HEADER = "x-voyant-dispatch-auth" as const;
34
34
  * Intended for dev + single-tenant deployments. Production should
35
35
  * issue per-tenant, short-lived tokens from a control plane.
36
36
  */
37
- export function createBearerVerifier(
38
- validTokens: readonly string[],
39
- ): (req: Request) => void {
37
+ export function createBearerVerifier(validTokens: readonly string[]): (req: Request) => void {
40
38
  if (validTokens.length === 0) {
41
- throw new Error("createBearerVerifier: need at least one valid token");
39
+ throw new Error("createBearerVerifier: need at least one valid token")
42
40
  }
43
41
  return (req) => {
44
- const header = req.headers.get("authorization");
45
- if (!header) throw new Error("missing Authorization header");
46
- const match = /^Bearer (.+)$/.exec(header);
42
+ const header = req.headers.get("authorization")
43
+ if (!header) throw new Error("missing Authorization header")
44
+ const match = /^Bearer (.+)$/.exec(header)
47
45
  if (!match) {
48
- throw new Error("Authorization header must use the Bearer scheme");
46
+ throw new Error("Authorization header must use the Bearer scheme")
49
47
  }
50
- const presented = match[1]!;
48
+ const presented = match[1]!
51
49
  for (const valid of validTokens) {
52
- if (constantTimeEquals(presented, valid)) return;
50
+ if (constantTimeEquals(presented, valid)) return
53
51
  }
54
- throw new Error("bearer token does not match any configured value");
55
- };
52
+ throw new Error("bearer token does not match any configured value")
53
+ }
56
54
  }
57
55
 
58
56
  function constantTimeEquals(a: string, b: string): boolean {
59
- if (a.length !== b.length) return false;
60
- let diff = 0;
57
+ if (a.length !== b.length) return false
58
+ let diff = 0
61
59
  for (let i = 0; i < a.length; i++) {
62
- diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
60
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i)
63
61
  }
64
- return diff === 0;
62
+ return diff === 0
65
63
  }
66
64
 
67
65
  /** Returns a signer: `(body: string) => Promise<string>` (base64 HMAC-SHA256). */
68
- export async function createHmacSigner(
69
- secret: string,
70
- ): Promise<(body: string) => Promise<string>> {
71
- const key = await importKey(secret, ["sign"]);
66
+ export async function createHmacSigner(secret: string): Promise<(body: string) => Promise<string>> {
67
+ const key = await importKey(secret, ["sign"])
72
68
  return async (body) => {
73
- const sig = await crypto.subtle.sign("HMAC", key, encode(body));
74
- return toBase64(sig);
75
- };
69
+ const sig = await crypto.subtle.sign("HMAC", key, encode(body))
70
+ return toBase64(sig)
71
+ }
76
72
  }
77
73
 
78
74
  /**
@@ -85,27 +81,25 @@ export async function createHmacSigner(
85
81
  * need the body downstream should pre-clone: `req.clone()` before
86
82
  * passing in.
87
83
  */
88
- export async function createHmacVerifier(
89
- secret: string,
90
- ): Promise<(req: Request) => Promise<void>> {
91
- const key = await importKey(secret, ["verify"]);
84
+ export async function createHmacVerifier(secret: string): Promise<(req: Request) => Promise<void>> {
85
+ const key = await importKey(secret, ["verify"])
92
86
  return async (req) => {
93
- const header = req.headers.get(AUTH_HEADER);
87
+ const header = req.headers.get(AUTH_HEADER)
94
88
  if (!header) {
95
- throw new Error(`missing ${AUTH_HEADER} header`);
89
+ throw new Error(`missing ${AUTH_HEADER} header`)
96
90
  }
97
- let sig: ArrayBuffer;
91
+ let sig: ArrayBuffer
98
92
  try {
99
- sig = fromBase64(header);
93
+ sig = fromBase64(header)
100
94
  } catch {
101
- throw new Error(`malformed ${AUTH_HEADER} header (expected base64)`);
95
+ throw new Error(`malformed ${AUTH_HEADER} header (expected base64)`)
102
96
  }
103
- const body = await req.clone().text();
104
- const ok = await crypto.subtle.verify("HMAC", key, sig, encode(body));
97
+ const body = await req.clone().text()
98
+ const ok = await crypto.subtle.verify("HMAC", key, sig, encode(body))
105
99
  if (!ok) {
106
- throw new Error(`${AUTH_HEADER} signature does not match request body`);
100
+ throw new Error(`${AUTH_HEADER} signature does not match request body`)
107
101
  }
108
- };
102
+ }
109
103
  }
110
104
 
111
105
  // ---- Internals ----
@@ -115,7 +109,7 @@ async function importKey(
115
109
  usages: readonly ("sign" | "verify")[],
116
110
  ): Promise<CryptoKey> {
117
111
  if (secret.length === 0) {
118
- throw new Error("HMAC secret must be a non-empty string");
112
+ throw new Error("HMAC secret must be a non-empty string")
119
113
  }
120
114
  return crypto.subtle.importKey(
121
115
  "raw",
@@ -123,7 +117,7 @@ async function importKey(
123
117
  { name: "HMAC", hash: "SHA-256" },
124
118
  false,
125
119
  usages as KeyUsage[],
126
- );
120
+ )
127
121
  }
128
122
 
129
123
  /**
@@ -133,24 +127,24 @@ async function importKey(
133
127
  * Copying into a new ArrayBuffer sidesteps the nominal mismatch.
134
128
  */
135
129
  function encode(s: string): ArrayBuffer {
136
- const view = new TextEncoder().encode(s);
137
- const buf = new ArrayBuffer(view.byteLength);
138
- new Uint8Array(buf).set(view);
139
- return buf;
130
+ const view = new TextEncoder().encode(s)
131
+ const buf = new ArrayBuffer(view.byteLength)
132
+ new Uint8Array(buf).set(view)
133
+ return buf
140
134
  }
141
135
 
142
136
  function toBase64(buffer: ArrayBuffer): string {
143
137
  // btoa is available in every modern runtime (Node 16+, Workers, browsers).
144
- const bytes = new Uint8Array(buffer);
145
- let bin = "";
146
- for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]!);
147
- return btoa(bin);
138
+ const bytes = new Uint8Array(buffer)
139
+ let bin = ""
140
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]!)
141
+ return btoa(bin)
148
142
  }
149
143
 
150
144
  function fromBase64(s: string): ArrayBuffer {
151
- const bin = atob(s);
152
- const buf = new ArrayBuffer(bin.length);
153
- const view = new Uint8Array(buf);
154
- for (let i = 0; i < bin.length; i++) view[i] = bin.charCodeAt(i);
155
- return buf;
145
+ const bin = atob(s)
146
+ const buf = new ArrayBuffer(bin.length)
147
+ const view = new Uint8Array(buf)
148
+ for (let i = 0; i < bin.length; i++) view[i] = bin.charCodeAt(i)
149
+ return buf
156
150
  }
package/src/conditions.ts CHANGED
@@ -2,33 +2,33 @@
2
2
  // and in event-filter match expressions.
3
3
  // Authoritative contract in docs/sdk-surface.md §4.
4
4
 
5
- import type { Duration, RunStatus } from "./types.js";
5
+ import type { Duration, RunStatus } from "./types.js"
6
6
 
7
7
  export interface EventCondition {
8
- event: string;
9
- match?: Record<string, unknown> | ((payload: unknown) => boolean);
8
+ event: string
9
+ match?: Record<string, unknown> | ((payload: unknown) => boolean)
10
10
  }
11
11
 
12
12
  export interface SignalCondition {
13
- signal: string;
14
- match?: Record<string, unknown> | ((payload: unknown) => boolean);
13
+ signal: string
14
+ match?: Record<string, unknown> | ((payload: unknown) => boolean)
15
15
  }
16
16
 
17
17
  export interface TimeCondition {
18
- after?: Duration | Date;
19
- before?: Duration | Date;
18
+ after?: Duration | Date
19
+ before?: Duration | Date
20
20
  }
21
21
 
22
22
  export interface RunStatusCondition {
23
- run: { id: string; status: RunStatus[] };
23
+ run: { id: string; status: RunStatus[] }
24
24
  }
25
25
 
26
26
  export interface OrCondition {
27
- or: Condition[];
27
+ or: Condition[]
28
28
  }
29
29
 
30
30
  export interface AndCondition {
31
- and: Condition[];
31
+ and: Condition[]
32
32
  }
33
33
 
34
34
  export type Condition =
@@ -37,7 +37,7 @@ export type Condition =
37
37
  | TimeCondition
38
38
  | RunStatusCondition
39
39
  | OrCondition
40
- | AndCondition;
40
+ | AndCondition
41
41
 
42
- export const or = (...conditions: Condition[]): OrCondition => ({ or: conditions });
43
- export const and = (...conditions: Condition[]): AndCondition => ({ and: conditions });
42
+ export const or = (...conditions: Condition[]): OrCondition => ({ or: conditions })
43
+ export const and = (...conditions: Condition[]): AndCondition => ({ and: conditions })
@@ -17,17 +17,23 @@
17
17
  // and the executor-shape already round-trips losslessly, we keep the
18
18
  // full discriminated union here. The orchestrator adapter can collapse.
19
19
 
20
- import { executeWorkflowStep, type ExecuteWorkflowStepRequest, type ExecuteWorkflowStepResponse, type StepRunner } from "../runtime/executor.js";
20
+ import {
21
+ type ExecuteWorkflowStepRequest,
22
+ type ExecuteWorkflowStepResponse,
23
+ executeWorkflowStep,
24
+ type StepRunner,
25
+ } from "../runtime/executor.js"
21
26
 
22
- export { executeWorkflowStep };
23
- export type { StepRunner, ExecuteWorkflowStepRequest, ExecuteWorkflowStepResponse };
24
- export type { StepJournalEntry } from "../runtime/journal.js";
25
- import type { JournalSlice, StepJournalEntry } from "../runtime/journal.js";
26
- import type { RuntimeEnvironment } from "../runtime/ctx.js";
27
- import { getWorkflow } from "../workflow.js";
28
- import type { RunTrigger } from "../types.js";
29
- import type { RateLimiter } from "../rate-limit/index.js";
30
- import { PROTOCOL_VERSION, type ProtocolVersion } from "../protocol/index.js";
27
+ export type { StepJournalEntry } from "../runtime/journal.js"
28
+ export type { ExecuteWorkflowStepRequest, ExecuteWorkflowStepResponse, StepRunner }
29
+ export { executeWorkflowStep }
30
+
31
+ import { PROTOCOL_VERSION, type ProtocolVersion } from "../protocol/index.js"
32
+ import type { RateLimiter } from "../rate-limit/index.js"
33
+ import type { RuntimeEnvironment } from "../runtime/ctx.js"
34
+ import type { JournalSlice, StepJournalEntry } from "../runtime/journal.js"
35
+ import type { RunTrigger } from "../types.js"
36
+ import { getWorkflow } from "../workflow.js"
31
37
 
32
38
  export interface StepHandlerDeps {
33
39
  /**
@@ -36,11 +42,11 @@ export interface StepHandlerDeps {
36
42
  * this verifies the `X-Voyant-Dispatch-Auth` HMAC against a public
37
43
  * key embedded by `voyant build`.
38
44
  */
39
- verifyRequest?: (req: Request) => void | Promise<void>;
45
+ verifyRequest?: (req: Request) => void | Promise<void>
40
46
  /** Injectable clock. Defaults to Date.now. */
41
- now?: () => number;
47
+ now?: () => number
42
48
  /** Optional structured logger. */
43
- logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void;
49
+ logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void
44
50
  /**
45
51
  * Rate limiter shared across step invocations. Required when any
46
52
  * registered workflow declares `options.rateLimit` on a step; see
@@ -48,7 +54,7 @@ export interface StepHandlerDeps {
48
54
  * the reference impl. One instance per Worker process is the
49
55
  * intended cardinality — state is kept in the limiter's closure.
50
56
  */
51
- rateLimiter?: RateLimiter;
57
+ rateLimiter?: RateLimiter
52
58
  /**
53
59
  * Runner for steps declared with `options.runtime === "node"`.
54
60
  * Leave unset for handlers that only run edge steps; any node step
@@ -62,88 +68,84 @@ export interface StepHandlerDeps {
62
68
  * This is bring-your-own because the right dispatch shape depends on
63
69
  * the target runtime; the executor only cares that a runner exists.
64
70
  */
65
- nodeStepRunner?: StepRunner;
71
+ nodeStepRunner?: StepRunner
66
72
  }
67
73
 
68
74
  /** The HTTP request body the orchestrator sends. */
69
75
  export interface WorkflowStepRequest {
70
- protocolVersion: ProtocolVersion;
71
- runId: string;
72
- workflowId: string;
73
- workflowVersion: string;
74
- invocationCount: number;
75
- input: unknown;
76
- journal: JournalSlice;
77
- environment: "production" | "preview" | "development";
78
- deadline: number;
76
+ protocolVersion: ProtocolVersion
77
+ runId: string
78
+ workflowId: string
79
+ workflowVersion: string
80
+ invocationCount: number
81
+ input: unknown
82
+ journal: JournalSlice
83
+ environment: "production" | "preview" | "development"
84
+ deadline: number
79
85
  tenantMeta: {
80
- tenantId: string;
81
- projectId: string;
82
- organizationId: string;
83
- projectSlug?: string;
84
- organizationSlug?: string;
85
- };
86
+ tenantId: string
87
+ projectId: string
88
+ organizationId: string
89
+ projectSlug?: string
90
+ organizationSlug?: string
91
+ }
86
92
  runMeta: {
87
- number: number;
88
- attempt: number;
89
- triggeredBy: RunTrigger;
90
- tags: string[];
91
- startedAt: number;
92
- };
93
+ number: number
94
+ attempt: number
95
+ triggeredBy: RunTrigger
96
+ tags: string[]
97
+ startedAt: number
98
+ }
93
99
  }
94
100
 
95
101
  /** The JSON response body the tenant returns. */
96
- export type WorkflowStepResponse = ExecuteWorkflowStepResponse;
102
+ export type WorkflowStepResponse = ExecuteWorkflowStepResponse
97
103
 
98
104
  /** Error-response envelope used for HTTP 4xx/5xx. */
99
105
  export interface StepHandlerError {
100
- error: string;
101
- message: string;
102
- details?: unknown;
106
+ error: string
107
+ message: string
108
+ details?: unknown
103
109
  }
104
110
 
105
111
  /** Build an HTTP fetch-style handler. */
106
- export function createStepHandler(
107
- deps: StepHandlerDeps = {},
108
- ): (req: Request) => Promise<Response> {
112
+ export function createStepHandler(deps: StepHandlerDeps = {}): (req: Request) => Promise<Response> {
109
113
  return async (req) => {
110
114
  if (req.method !== "POST") {
111
- return jsonResponse(405, errorBody("method_not_allowed", "POST required"));
115
+ return jsonResponse(405, errorBody("method_not_allowed", "POST required"))
112
116
  }
113
117
  try {
114
- if (deps.verifyRequest) await deps.verifyRequest(req);
118
+ if (deps.verifyRequest) await deps.verifyRequest(req)
115
119
  } catch (err) {
116
120
  deps.logger?.("warn", "step handler: auth rejected", {
117
121
  error: err instanceof Error ? err.message : String(err),
118
- });
119
- return jsonResponse(401, errorBody("unauthorized", errMessage(err)));
122
+ })
123
+ return jsonResponse(401, errorBody("unauthorized", errMessage(err)))
120
124
  }
121
- let raw: unknown;
125
+ let raw: unknown
122
126
  try {
123
- raw = await req.json();
127
+ raw = await req.json()
124
128
  } catch (err) {
125
- return jsonResponse(400, errorBody("invalid_json", errMessage(err)));
129
+ return jsonResponse(400, errorBody("invalid_json", errMessage(err)))
126
130
  }
127
131
  // The incoming Request carries its own AbortSignal; threading it
128
132
  // through lets `ctx.signal` observe client-side aborts (orchestrator
129
133
  // cancellations, closed fetches, etc.) during step execution.
130
- const out = await runStepInner(raw, deps, { signal: req.signal });
131
- return jsonResponse(out.status, out.body);
132
- };
134
+ const out = await runStepInner(raw, deps, { signal: req.signal })
135
+ return jsonResponse(out.status, out.body)
136
+ }
133
137
  }
134
138
 
135
139
  /** Per-invocation options available to callers of the transport-free entry point. */
136
140
  export interface StepRequestOptions {
137
141
  /** AbortSignal forwarded to `ctx.signal` inside the step body. */
138
- signal?: AbortSignal;
142
+ signal?: AbortSignal
139
143
  /**
140
144
  * Fires synchronously from `ctx.stream.*` as each chunk is produced.
141
145
  * Used by orchestrators that want to broadcast chunks live
142
146
  * (dashboards, queues) before the invocation returns.
143
147
  */
144
- onStreamChunk?: (
145
- chunk: import("../runtime/executor.js").StreamChunk,
146
- ) => void;
148
+ onStreamChunk?: (chunk: import("../runtime/executor.js").StreamChunk) => void
147
149
  }
148
150
 
149
151
  /**
@@ -157,10 +159,9 @@ export async function handleStepRequest(
157
159
  deps: StepHandlerDeps = {},
158
160
  opts: StepRequestOptions = {},
159
161
  ): Promise<
160
- | { status: number; body: WorkflowStepResponse }
161
- | { status: number; body: StepHandlerError }
162
+ { status: number; body: WorkflowStepResponse } | { status: number; body: StepHandlerError }
162
163
  > {
163
- return runStepInner(raw, deps, opts);
164
+ return runStepInner(raw, deps, opts)
164
165
  }
165
166
 
166
167
  async function runStepInner(
@@ -168,13 +169,12 @@ async function runStepInner(
168
169
  deps: StepHandlerDeps,
169
170
  opts: StepRequestOptions = {},
170
171
  ): Promise<
171
- | { status: number; body: WorkflowStepResponse }
172
- | { status: number; body: StepHandlerError }
172
+ { status: number; body: WorkflowStepResponse } | { status: number; body: StepHandlerError }
173
173
  > {
174
- const parsed = parseRequest(raw);
175
- if (!parsed.ok) return { status: 400, body: errorBody("invalid_request", parsed.message) };
174
+ const parsed = parseRequest(raw)
175
+ if (!parsed.ok) return { status: 400, body: errorBody("invalid_request", parsed.message) }
176
176
 
177
- const reqBody = parsed.value;
177
+ const reqBody = parsed.value
178
178
  if (reqBody.protocolVersion !== PROTOCOL_VERSION) {
179
179
  return {
180
180
  status: 426,
@@ -182,19 +182,22 @@ async function runStepInner(
182
182
  "protocol_version_mismatch",
183
183
  `tenant supports protocol ${PROTOCOL_VERSION}, got ${String(reqBody.protocolVersion)}`,
184
184
  ),
185
- };
185
+ }
186
186
  }
187
187
 
188
- const def = getWorkflow(reqBody.workflowId);
188
+ const def = getWorkflow(reqBody.workflowId)
189
189
  if (!def) {
190
190
  return {
191
191
  status: 404,
192
- body: errorBody("workflow_not_found", `workflow "${reqBody.workflowId}" is not registered in this bundle`),
193
- };
192
+ body: errorBody(
193
+ "workflow_not_found",
194
+ `workflow "${reqBody.workflowId}" is not registered in this bundle`,
195
+ ),
196
+ }
194
197
  }
195
198
 
196
- const now = deps.now ?? (() => Date.now());
197
- const stepRunner = createInProcessStepRunner(now);
199
+ const now = deps.now ?? (() => Date.now())
200
+ const stepRunner = createInProcessStepRunner(now)
198
201
 
199
202
  const runtimeEnv: RuntimeEnvironment = {
200
203
  run: {
@@ -215,7 +218,7 @@ async function runStepInner(
215
218
  id: reqBody.tenantMeta.organizationId,
216
219
  slug: reqBody.tenantMeta.organizationSlug ?? reqBody.tenantMeta.organizationId,
217
220
  },
218
- };
221
+ }
219
222
 
220
223
  try {
221
224
  const response = await executeWorkflowStep(def, {
@@ -235,16 +238,16 @@ async function runStepInner(
235
238
  now,
236
239
  abortSignal: opts.signal,
237
240
  onStreamChunk: opts.onStreamChunk,
238
- });
239
- return { status: 200, body: response };
241
+ })
242
+ return { status: 200, body: response }
240
243
  } catch (err) {
241
244
  deps.logger?.("error", "step handler: executor threw", {
242
245
  error: err instanceof Error ? err.message : String(err),
243
- });
246
+ })
244
247
  return {
245
248
  status: 500,
246
249
  body: errorBody("executor_error", errMessage(err)),
247
- };
250
+ }
248
251
  }
249
252
  }
250
253
 
@@ -254,23 +257,24 @@ async function runStepInner(
254
257
  * will swap this for a dispatching runner that POSTs to a pod.
255
258
  */
256
259
  function createInProcessStepRunner(now: () => number): StepRunner {
257
- return async ({ stepId, attempt, fn, stepCtx }): Promise<StepJournalEntry> => {
258
- const startedAt = now();
260
+ return async ({ stepId: _stepId, attempt, fn, stepCtx }): Promise<StepJournalEntry> => {
261
+ const startedAt = now()
259
262
  try {
260
- const output = await fn(stepCtx);
263
+ const output = await fn(stepCtx)
261
264
  return {
262
265
  attempt,
263
266
  status: "ok",
264
267
  output,
265
268
  startedAt,
266
269
  finishedAt: now(),
267
- };
270
+ }
268
271
  } catch (err) {
269
- const e = err as Error;
270
- const code = typeof (err as { code?: unknown }).code === "string"
271
- ? (err as { code: string }).code
272
- : "UNKNOWN";
273
- const retryAfter = (err as { retryAfter?: unknown }).retryAfter;
272
+ const e = err as Error
273
+ const code =
274
+ typeof (err as { code?: unknown }).code === "string"
275
+ ? (err as { code: string }).code
276
+ : "UNKNOWN"
277
+ const retryAfter = (err as { retryAfter?: unknown }).retryAfter
274
278
  return {
275
279
  attempt,
276
280
  status: "err",
@@ -284,9 +288,9 @@ function createInProcessStepRunner(now: () => number): StepRunner {
284
288
  },
285
289
  startedAt,
286
290
  finishedAt: now(),
287
- };
291
+ }
288
292
  }
289
- };
293
+ }
290
294
  }
291
295
 
292
296
  // ---- Parsing ----
@@ -295,9 +299,9 @@ function parseRequest(
295
299
  raw: unknown,
296
300
  ): { ok: true; value: WorkflowStepRequest } | { ok: false; message: string } {
297
301
  if (raw === null || typeof raw !== "object") {
298
- return { ok: false, message: "body must be a JSON object" };
302
+ return { ok: false, message: "body must be a JSON object" }
299
303
  }
300
- const r = raw as Record<string, unknown>;
304
+ const r = raw as Record<string, unknown>
301
305
  const required: (keyof WorkflowStepRequest)[] = [
302
306
  "protocolVersion",
303
307
  "runId",
@@ -309,36 +313,36 @@ function parseRequest(
309
313
  "deadline",
310
314
  "tenantMeta",
311
315
  "runMeta",
312
- ];
316
+ ]
313
317
  for (const k of required) {
314
- if (!(k in r)) return { ok: false, message: `missing required field "${k}"` };
318
+ if (!(k in r)) return { ok: false, message: `missing required field "${k}"` }
315
319
  }
316
320
  if (typeof r.protocolVersion !== "number") {
317
- return { ok: false, message: "`protocolVersion` must be a number" };
321
+ return { ok: false, message: "`protocolVersion` must be a number" }
318
322
  }
319
323
  if (typeof r.runId !== "string" || r.runId.length === 0) {
320
- return { ok: false, message: "`runId` must be a non-empty string" };
324
+ return { ok: false, message: "`runId` must be a non-empty string" }
321
325
  }
322
326
  if (typeof r.workflowId !== "string" || r.workflowId.length === 0) {
323
- return { ok: false, message: "`workflowId` must be a non-empty string" };
327
+ return { ok: false, message: "`workflowId` must be a non-empty string" }
324
328
  }
325
329
  if (typeof r.invocationCount !== "number" || r.invocationCount < 1) {
326
- return { ok: false, message: "`invocationCount` must be >= 1" };
330
+ return { ok: false, message: "`invocationCount` must be >= 1" }
327
331
  }
328
332
  if (!r.journal || typeof r.journal !== "object") {
329
- return { ok: false, message: "`journal` must be an object" };
333
+ return { ok: false, message: "`journal` must be an object" }
330
334
  }
331
- const env = r.environment;
335
+ const env = r.environment
332
336
  if (env !== "production" && env !== "preview" && env !== "development") {
333
- return { ok: false, message: "`environment` must be production | preview | development" };
337
+ return { ok: false, message: "`environment` must be production | preview | development" }
334
338
  }
335
339
  if (!r.tenantMeta || typeof r.tenantMeta !== "object") {
336
- return { ok: false, message: "`tenantMeta` must be an object" };
340
+ return { ok: false, message: "`tenantMeta` must be an object" }
337
341
  }
338
342
  if (!r.runMeta || typeof r.runMeta !== "object") {
339
- return { ok: false, message: "`runMeta` must be an object" };
343
+ return { ok: false, message: "`runMeta` must be an object" }
340
344
  }
341
- return { ok: true, value: r as unknown as WorkflowStepRequest };
345
+ return { ok: true, value: r as unknown as WorkflowStepRequest }
342
346
  }
343
347
 
344
348
  // ---- Helpers ----
@@ -347,15 +351,15 @@ function jsonResponse(status: number, body: unknown): Response {
347
351
  return new Response(JSON.stringify(body), {
348
352
  status,
349
353
  headers: { "content-type": "application/json; charset=utf-8" },
350
- });
354
+ })
351
355
  }
352
356
 
353
357
  function errorBody(error: string, message: string, details?: unknown): StepHandlerError {
354
- const out: StepHandlerError = { error, message };
355
- if (details !== undefined) out.details = details;
356
- return out;
358
+ const out: StepHandlerError = { error, message }
359
+ if (details !== undefined) out.details = details
360
+ return out
357
361
  }
358
362
 
359
363
  function errMessage(err: unknown): string {
360
- return err instanceof Error ? err.message : String(err);
364
+ return err instanceof Error ? err.message : String(err)
361
365
  }
package/src/index.ts CHANGED
@@ -4,15 +4,15 @@
4
4
  // docs/sdk-surface.md §2–§8
5
5
  // docs/design.md §3–§4
6
6
 
7
- export * from "./types.js";
8
- export * from "./workflow.js";
9
- export * from "./trigger.js";
10
- export * from "./conditions.js";
11
7
  export {
12
8
  FatalError,
13
- RetryableError,
14
- TimeoutError,
15
9
  HookConflictError,
16
10
  QuotaExceededError,
11
+ RetryableError,
12
+ TimeoutError,
17
13
  ValidationError,
18
- } from "@voyantjs/workflows-errors";
14
+ } from "@voyantjs/workflows-errors"
15
+ export * from "./conditions.js"
16
+ export * from "./trigger.js"
17
+ export * from "./types.js"
18
+ export * from "./workflow.js"