@voyant-travel/workflows 0.107.10

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 (120) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +52 -0
  3. package/README.md +79 -0
  4. package/dist/auth/index.d.ts +125 -0
  5. package/dist/auth/index.d.ts.map +1 -0
  6. package/dist/auth/index.js +352 -0
  7. package/dist/bindings.d.ts +119 -0
  8. package/dist/bindings.d.ts.map +1 -0
  9. package/dist/bindings.js +19 -0
  10. package/dist/client.d.ts +135 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +305 -0
  13. package/dist/conditions.d.ts +29 -0
  14. package/dist/conditions.d.ts.map +1 -0
  15. package/dist/conditions.js +5 -0
  16. package/dist/config.d.ts +93 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +7 -0
  19. package/dist/driver.d.ts +237 -0
  20. package/dist/driver.d.ts.map +1 -0
  21. package/dist/driver.js +53 -0
  22. package/dist/errors.d.ts +58 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +76 -0
  25. package/dist/events/compile.d.ts +34 -0
  26. package/dist/events/compile.d.ts.map +1 -0
  27. package/dist/events/compile.js +204 -0
  28. package/dist/events/index.d.ts +8 -0
  29. package/dist/events/index.d.ts.map +1 -0
  30. package/dist/events/index.js +11 -0
  31. package/dist/events/input-mapper.d.ts +24 -0
  32. package/dist/events/input-mapper.d.ts.map +1 -0
  33. package/dist/events/input-mapper.js +169 -0
  34. package/dist/events/manifest-builder.d.ts +42 -0
  35. package/dist/events/manifest-builder.d.ts.map +1 -0
  36. package/dist/events/manifest-builder.js +313 -0
  37. package/dist/events/payload-hash.d.ts +46 -0
  38. package/dist/events/payload-hash.d.ts.map +1 -0
  39. package/dist/events/payload-hash.js +98 -0
  40. package/dist/events/predicate.d.ts +77 -0
  41. package/dist/events/predicate.d.ts.map +1 -0
  42. package/dist/events/predicate.js +347 -0
  43. package/dist/events/registry.d.ts +37 -0
  44. package/dist/events/registry.d.ts.map +1 -0
  45. package/dist/events/registry.js +47 -0
  46. package/dist/handler/index.d.ts +114 -0
  47. package/dist/handler/index.d.ts.map +1 -0
  48. package/dist/handler/index.js +267 -0
  49. package/dist/handler/resume.d.ts +41 -0
  50. package/dist/handler/resume.d.ts.map +1 -0
  51. package/dist/handler/resume.js +44 -0
  52. package/dist/http-ingest.d.ts +54 -0
  53. package/dist/http-ingest.d.ts.map +1 -0
  54. package/dist/http-ingest.js +214 -0
  55. package/dist/index.d.ts +6 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +10 -0
  58. package/dist/protocol/index.d.ts +345 -0
  59. package/dist/protocol/index.d.ts.map +1 -0
  60. package/dist/protocol/index.js +110 -0
  61. package/dist/rate-limit/index.d.ts +40 -0
  62. package/dist/rate-limit/index.d.ts.map +1 -0
  63. package/dist/rate-limit/index.js +139 -0
  64. package/dist/runtime/ctx.d.ts +111 -0
  65. package/dist/runtime/ctx.d.ts.map +1 -0
  66. package/dist/runtime/ctx.js +624 -0
  67. package/dist/runtime/determinism.d.ts +19 -0
  68. package/dist/runtime/determinism.d.ts.map +1 -0
  69. package/dist/runtime/determinism.js +61 -0
  70. package/dist/runtime/errors.d.ts +21 -0
  71. package/dist/runtime/errors.d.ts.map +1 -0
  72. package/dist/runtime/errors.js +45 -0
  73. package/dist/runtime/executor.d.ts +166 -0
  74. package/dist/runtime/executor.d.ts.map +1 -0
  75. package/dist/runtime/executor.js +226 -0
  76. package/dist/runtime/journal.d.ts +56 -0
  77. package/dist/runtime/journal.d.ts.map +1 -0
  78. package/dist/runtime/journal.js +28 -0
  79. package/dist/testing/index.d.ts +117 -0
  80. package/dist/testing/index.d.ts.map +1 -0
  81. package/dist/testing/index.js +599 -0
  82. package/dist/trigger.d.ts +37 -0
  83. package/dist/trigger.d.ts.map +1 -0
  84. package/dist/trigger.js +11 -0
  85. package/dist/types.d.ts +63 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +3 -0
  88. package/dist/workflow.d.ts +222 -0
  89. package/dist/workflow.d.ts.map +1 -0
  90. package/dist/workflow.js +55 -0
  91. package/package.json +120 -0
  92. package/src/auth/index.ts +398 -0
  93. package/src/bindings.ts +135 -0
  94. package/src/client.ts +498 -0
  95. package/src/conditions.ts +43 -0
  96. package/src/config.ts +114 -0
  97. package/src/driver.ts +277 -0
  98. package/src/errors.ts +109 -0
  99. package/src/events/compile.ts +268 -0
  100. package/src/events/index.ts +42 -0
  101. package/src/events/input-mapper.ts +201 -0
  102. package/src/events/manifest-builder.ts +372 -0
  103. package/src/events/payload-hash.ts +110 -0
  104. package/src/events/predicate.ts +390 -0
  105. package/src/events/registry.ts +86 -0
  106. package/src/handler/index.ts +413 -0
  107. package/src/handler/resume.ts +100 -0
  108. package/src/http-ingest.ts +299 -0
  109. package/src/index.ts +18 -0
  110. package/src/protocol/index.ts +483 -0
  111. package/src/rate-limit/index.ts +181 -0
  112. package/src/runtime/ctx.ts +876 -0
  113. package/src/runtime/determinism.ts +75 -0
  114. package/src/runtime/errors.ts +58 -0
  115. package/src/runtime/executor.ts +442 -0
  116. package/src/runtime/journal.ts +80 -0
  117. package/src/testing/index.ts +796 -0
  118. package/src/trigger.ts +63 -0
  119. package/src/types.ts +80 -0
  120. package/src/workflow.ts +328 -0
@@ -0,0 +1,413 @@
1
+ // @voyant-travel/workflows/handler
2
+ //
3
+ // The tenant side of the runtime protocol (see docs/runtime-protocol.md §2).
4
+ // The orchestrator invokes `POST /__voyant/workflow-step`; the tenant
5
+ // Worker responds by running the workflow body once (one invocation of
6
+ // the executor) and returning the executor response as JSON.
7
+ //
8
+ // export default { fetch: createStepHandler() }
9
+ //
10
+ // is enough to make a tenant Worker protocol-conformant. Auth is
11
+ // optional at the SDK level: in production, wire the HMAC verifier
12
+ // bundled by `voyant build`; for local dev, leave it unset.
13
+ //
14
+ // The executor's native response shape is returned verbatim — the wire
15
+ // document calls for compensated/compensation_failed to be folded into
16
+ // "failed" for the first draft, but since the draft is not yet locked
17
+ // and the executor-shape already round-trips losslessly, we keep the
18
+ // full discriminated union here. The orchestrator adapter can collapse.
19
+
20
+ import {
21
+ type ExecuteWorkflowStepRequest,
22
+ type ExecuteWorkflowStepResponse,
23
+ executeWorkflowStep,
24
+ type StepRunner,
25
+ } from "../runtime/executor.js"
26
+
27
+ export type { StepJournalEntry } from "../runtime/journal.js"
28
+ export type { ExecuteWorkflowStepRequest, ExecuteWorkflowStepResponse, StepRunner }
29
+ export { executeWorkflowStep }
30
+
31
+ import {
32
+ PROTOCOL_VERSION,
33
+ type ProtocolVersion,
34
+ type WorkflowActivationMetadata,
35
+ } from "../protocol/index.js"
36
+ import type { RateLimiter } from "../rate-limit/index.js"
37
+ import type { RuntimeEnvironment } from "../runtime/ctx.js"
38
+ import type { JournalSlice, StepJournalEntry } from "../runtime/journal.js"
39
+ import type { RunTrigger } from "../types.js"
40
+ import { getWorkflow } from "../workflow.js"
41
+
42
+ export interface StepHandlerDeps {
43
+ /**
44
+ * Optional. Called before parsing the body. Should throw / reject
45
+ * if the request is not from a trusted orchestrator. In production
46
+ * this verifies the `X-Voyant-Dispatch-Auth` HMAC against a public
47
+ * key embedded by `voyant build`.
48
+ */
49
+ verifyRequest?: (req: Request) => void | Promise<void>
50
+ /** Injectable clock. Defaults to Date.now. */
51
+ now?: () => number
52
+ /** Optional structured logger. */
53
+ logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void
54
+ /**
55
+ * Rate limiter shared across step invocations. Required when any
56
+ * registered workflow declares `options.rateLimit` on a step; see
57
+ * `createInMemoryRateLimiter` in `@voyant-travel/workflows/rate-limit` for
58
+ * the reference impl. One instance per Worker process is the
59
+ * intended cardinality — state is kept in the limiter's closure.
60
+ */
61
+ rateLimiter?: RateLimiter
62
+ /**
63
+ * Runner for steps declared with `options.runtime === "node"`.
64
+ * Leave unset for handlers that only run edge steps; any node step
65
+ * will then fail with `NODE_RUNTIME_UNAVAILABLE`.
66
+ *
67
+ * Typical impl dispatches to a separate sandboxed context:
68
+ * - Local dev: an in-process passthrough (same Node process).
69
+ * - CF production: a Cloudflare Container binding, via
70
+ * `createCfContainerStepRunner` from `@voyant-travel/workflows-orchestrator-cloudflare`.
71
+ *
72
+ * This is bring-your-own because the right dispatch shape depends on
73
+ * the target runtime; the executor only cares that a runner exists.
74
+ */
75
+ nodeStepRunner?: StepRunner
76
+ /**
77
+ * Read-only service resolver, surfaced to step bodies as `ctx.services`.
78
+ * The framework's `createApp()` wires this from its `ModuleContainer`;
79
+ * raw orchestrator harnesses (tests, ad-hoc scripts) typically leave
80
+ * it unset, in which case `ctx.services.resolve(...)` throws with a
81
+ * clear message. See architecture doc §11.
82
+ */
83
+ services?: import("../driver.js").ServiceResolver
84
+ }
85
+
86
+ /** The HTTP request body the orchestrator sends. */
87
+ export interface WorkflowStepRequest {
88
+ protocolVersion: ProtocolVersion
89
+ runId: string
90
+ workflowId: string
91
+ workflowVersion: string
92
+ invocationCount: number
93
+ input: unknown
94
+ journal: JournalSlice
95
+ environment: "production" | "preview" | "development"
96
+ deadline: number
97
+ tenantMeta: {
98
+ tenantId: string
99
+ projectId: string
100
+ organizationId: string
101
+ projectSlug?: string
102
+ organizationSlug?: string
103
+ }
104
+ runMeta: {
105
+ number: number
106
+ attempt: number
107
+ triggeredBy: RunTrigger
108
+ tags: string[]
109
+ startedAt: number
110
+ }
111
+ activation?: WorkflowActivationMetadata
112
+ }
113
+
114
+ /** The JSON response body the tenant returns. */
115
+ export type WorkflowStepResponse = ExecuteWorkflowStepResponse
116
+
117
+ /** Error-response envelope used for HTTP 4xx/5xx. */
118
+ export interface StepHandlerError {
119
+ error: string
120
+ message: string
121
+ details?: unknown
122
+ }
123
+
124
+ export {
125
+ type BuildResumeStepRequestInput,
126
+ type BuildResumeStepRequestResult,
127
+ buildResumeStepRequest,
128
+ } from "./resume.js"
129
+
130
+ /** Build an HTTP fetch-style handler. */
131
+ export function createStepHandler(deps: StepHandlerDeps = {}): (req: Request) => Promise<Response> {
132
+ return async (req) => {
133
+ if (req.method !== "POST") {
134
+ return jsonResponse(405, errorBody("method_not_allowed", "POST required"))
135
+ }
136
+ try {
137
+ if (deps.verifyRequest) await deps.verifyRequest(req)
138
+ } catch (err) {
139
+ deps.logger?.("warn", "step handler: auth rejected", {
140
+ error: err instanceof Error ? err.message : String(err),
141
+ })
142
+ return jsonResponse(401, errorBody("unauthorized", errMessage(err)))
143
+ }
144
+ let raw: unknown
145
+ try {
146
+ raw = await req.json()
147
+ } catch (err) {
148
+ return jsonResponse(400, errorBody("invalid_json", errMessage(err)))
149
+ }
150
+ // The incoming Request carries its own AbortSignal; threading it
151
+ // through lets `ctx.signal` observe client-side aborts (orchestrator
152
+ // cancellations, closed fetches, etc.) during step execution.
153
+ const out = await runStepInner(raw, deps, { signal: req.signal })
154
+ return jsonResponse(out.status, out.body)
155
+ }
156
+ }
157
+
158
+ /** Per-invocation options available to callers of the transport-free entry point. */
159
+ export interface StepRequestOptions {
160
+ /** AbortSignal forwarded to `ctx.signal` inside the step body. */
161
+ signal?: AbortSignal
162
+ /**
163
+ * Fires synchronously from `ctx.stream.*` as each chunk is produced.
164
+ * Used by orchestrators that want to broadcast chunks live
165
+ * (dashboards, queues) before the invocation returns.
166
+ */
167
+ onStreamChunk?: (chunk: import("../runtime/executor.js").StreamChunk) => void
168
+ }
169
+
170
+ /**
171
+ * Transport-free entry point. Callers that already parsed the body
172
+ * (e.g. local orchestrator in-memory, tests) invoke this directly.
173
+ * Returns either the step response or an error envelope with the HTTP
174
+ * status the caller should use.
175
+ */
176
+ export async function handleStepRequest(
177
+ raw: unknown,
178
+ deps: StepHandlerDeps = {},
179
+ opts: StepRequestOptions = {},
180
+ ): Promise<
181
+ { status: number; body: WorkflowStepResponse } | { status: number; body: StepHandlerError }
182
+ > {
183
+ return runStepInner(raw, deps, opts)
184
+ }
185
+
186
+ async function runStepInner(
187
+ raw: unknown,
188
+ deps: StepHandlerDeps,
189
+ opts: StepRequestOptions = {},
190
+ ): Promise<
191
+ { status: number; body: WorkflowStepResponse } | { status: number; body: StepHandlerError }
192
+ > {
193
+ const parsed = parseRequest(raw)
194
+ if (!parsed.ok) return { status: 400, body: errorBody("invalid_request", parsed.message) }
195
+
196
+ const reqBody = parsed.value
197
+ if (reqBody.protocolVersion !== PROTOCOL_VERSION) {
198
+ return {
199
+ status: 426,
200
+ body: errorBody(
201
+ "protocol_version_mismatch",
202
+ `tenant supports protocol ${PROTOCOL_VERSION}, got ${String(reqBody.protocolVersion)}`,
203
+ ),
204
+ }
205
+ }
206
+
207
+ const def = getWorkflow(reqBody.workflowId)
208
+ if (!def) {
209
+ return {
210
+ status: 404,
211
+ body: errorBody(
212
+ "workflow_not_found",
213
+ `workflow "${reqBody.workflowId}" is not registered in this bundle`,
214
+ ),
215
+ }
216
+ }
217
+
218
+ const now = deps.now ?? (() => Date.now())
219
+ const stepRunner = createInProcessStepRunner(now)
220
+
221
+ const runtimeEnv: RuntimeEnvironment = {
222
+ run: {
223
+ id: reqBody.runId,
224
+ number: reqBody.runMeta.number,
225
+ attempt: reqBody.runMeta.attempt,
226
+ triggeredBy: reqBody.runMeta.triggeredBy,
227
+ tags: reqBody.runMeta.tags,
228
+ startedAt: reqBody.runMeta.startedAt,
229
+ },
230
+ workflow: { id: reqBody.workflowId, version: reqBody.workflowVersion },
231
+ environment: { name: reqBody.environment },
232
+ project: {
233
+ id: reqBody.tenantMeta.projectId,
234
+ slug: reqBody.tenantMeta.projectSlug ?? reqBody.tenantMeta.projectId,
235
+ },
236
+ organization: {
237
+ id: reqBody.tenantMeta.organizationId,
238
+ slug: reqBody.tenantMeta.organizationSlug ?? reqBody.tenantMeta.organizationId,
239
+ },
240
+ }
241
+
242
+ try {
243
+ const response = await executeWorkflowStep(def, {
244
+ runId: reqBody.runId,
245
+ workflowId: reqBody.workflowId,
246
+ workflowVersion: reqBody.workflowVersion,
247
+ input: reqBody.input,
248
+ journal: reqBody.journal,
249
+ invocationCount: reqBody.invocationCount,
250
+ environment: runtimeEnv,
251
+ triggeredBy: reqBody.runMeta.triggeredBy,
252
+ runStartedAt: reqBody.runMeta.startedAt,
253
+ tags: reqBody.runMeta.tags,
254
+ stepRunner,
255
+ nodeStepRunner: deps.nodeStepRunner,
256
+ rateLimiter: deps.rateLimiter,
257
+ services: deps.services,
258
+ now,
259
+ abortSignal: opts.signal,
260
+ onStreamChunk: opts.onStreamChunk,
261
+ })
262
+ return { status: 200, body: response }
263
+ } catch (err) {
264
+ deps.logger?.("error", "step handler: executor threw", {
265
+ error: err instanceof Error ? err.message : String(err),
266
+ })
267
+ return {
268
+ status: 500,
269
+ body: errorBody("executor_error", errMessage(err)),
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Build a step runner that executes the step body in the same
276
+ * process. Suitable for `runtime: "edge"`. Container-runtime steps
277
+ * will swap this for a dispatching runner that POSTs to a pod.
278
+ */
279
+ function createInProcessStepRunner(now: () => number): StepRunner {
280
+ return async ({ stepId: _stepId, attempt, fn, stepCtx }): Promise<StepJournalEntry> => {
281
+ const startedAt = now()
282
+ try {
283
+ const output = await fn(stepCtx)
284
+ return {
285
+ attempt,
286
+ status: "ok",
287
+ output,
288
+ startedAt,
289
+ finishedAt: now(),
290
+ }
291
+ } catch (err) {
292
+ const e = err as Error
293
+ const code =
294
+ typeof (err as { code?: unknown }).code === "string"
295
+ ? (err as { code: string }).code
296
+ : "UNKNOWN"
297
+ const retryAfter = (err as { retryAfter?: unknown }).retryAfter
298
+ return {
299
+ attempt,
300
+ status: "err",
301
+ error: {
302
+ category: "USER_ERROR",
303
+ code,
304
+ message: e?.message ?? String(err),
305
+ name: e?.name,
306
+ stack: e?.stack,
307
+ data: retryAfter !== undefined ? { retryAfter } : undefined,
308
+ },
309
+ startedAt,
310
+ finishedAt: now(),
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ // ---- Parsing ----
317
+
318
+ function parseRequest(
319
+ raw: unknown,
320
+ ): { ok: true; value: WorkflowStepRequest } | { ok: false; message: string } {
321
+ if (raw === null || typeof raw !== "object") {
322
+ return { ok: false, message: "body must be a JSON object" }
323
+ }
324
+ const r = raw as Record<string, unknown>
325
+ const required: (keyof WorkflowStepRequest)[] = [
326
+ "protocolVersion",
327
+ "runId",
328
+ "workflowId",
329
+ "workflowVersion",
330
+ "invocationCount",
331
+ "journal",
332
+ "environment",
333
+ "deadline",
334
+ "tenantMeta",
335
+ "runMeta",
336
+ ]
337
+ for (const k of required) {
338
+ if (!(k in r)) return { ok: false, message: `missing required field "${k}"` }
339
+ }
340
+ if (typeof r.protocolVersion !== "number") {
341
+ return { ok: false, message: "`protocolVersion` must be a number" }
342
+ }
343
+ if (typeof r.runId !== "string" || r.runId.length === 0) {
344
+ return { ok: false, message: "`runId` must be a non-empty string" }
345
+ }
346
+ if (typeof r.workflowId !== "string" || r.workflowId.length === 0) {
347
+ return { ok: false, message: "`workflowId` must be a non-empty string" }
348
+ }
349
+ if (typeof r.workflowVersion !== "string" || r.workflowVersion.length === 0) {
350
+ return { ok: false, message: "`workflowVersion` must be a non-empty string" }
351
+ }
352
+ if (typeof r.invocationCount !== "number" || r.invocationCount < 1) {
353
+ return { ok: false, message: "`invocationCount` must be >= 1" }
354
+ }
355
+ if (typeof r.deadline !== "number") {
356
+ return { ok: false, message: "`deadline` must be a number" }
357
+ }
358
+ if (!r.journal || typeof r.journal !== "object") {
359
+ return { ok: false, message: "`journal` must be an object" }
360
+ }
361
+ const env = r.environment
362
+ if (env !== "production" && env !== "preview" && env !== "development") {
363
+ return { ok: false, message: "`environment` must be production | preview | development" }
364
+ }
365
+ if (!r.tenantMeta || typeof r.tenantMeta !== "object") {
366
+ return { ok: false, message: "`tenantMeta` must be an object" }
367
+ }
368
+ if (!r.runMeta || typeof r.runMeta !== "object") {
369
+ return { ok: false, message: "`runMeta` must be an object" }
370
+ }
371
+ return {
372
+ ok: true,
373
+ value: {
374
+ protocolVersion: r.protocolVersion as ProtocolVersion,
375
+ runId: r.runId,
376
+ workflowId: r.workflowId,
377
+ workflowVersion: r.workflowVersion,
378
+ invocationCount: r.invocationCount,
379
+ input: r.input,
380
+ journal: r.journal as JournalSlice,
381
+ environment: env,
382
+ deadline: r.deadline,
383
+ tenantMeta: r.tenantMeta as WorkflowStepRequest["tenantMeta"],
384
+ runMeta: r.runMeta as WorkflowStepRequest["runMeta"],
385
+ activation: isObjectRecord(r.activation)
386
+ ? (r.activation as WorkflowActivationMetadata)
387
+ : undefined,
388
+ },
389
+ }
390
+ }
391
+
392
+ // ---- Helpers ----
393
+
394
+ function jsonResponse(status: number, body: unknown): Response {
395
+ return new Response(JSON.stringify(body), {
396
+ status,
397
+ headers: { "content-type": "application/json; charset=utf-8" },
398
+ })
399
+ }
400
+
401
+ function errorBody(error: string, message: string, details?: unknown): StepHandlerError {
402
+ const out: StepHandlerError = { error, message }
403
+ if (details !== undefined) out.details = details
404
+ return out
405
+ }
406
+
407
+ function errMessage(err: unknown): string {
408
+ return err instanceof Error ? err.message : String(err)
409
+ }
410
+
411
+ function isObjectRecord(value: unknown): value is Record<string, unknown> {
412
+ return typeof value === "object" && value !== null && !Array.isArray(value)
413
+ }
@@ -0,0 +1,100 @@
1
+ import {
2
+ applyWorkflowResumeToJournal,
3
+ PROTOCOL_VERSION,
4
+ type ProtocolVersion,
5
+ type WorkflowActivationFreshness,
6
+ type WorkflowBundleReference,
7
+ type WorkflowJournalReference,
8
+ type WorkflowPayloadReference,
9
+ type WorkflowWaitpointResumeTarget,
10
+ type WorkflowWaitpointSnapshot,
11
+ } from "../protocol/index.js"
12
+ import type { JournalSlice } from "../runtime/journal.js"
13
+ import type { WorkflowStepRequest } from "./index.js"
14
+
15
+ export interface BuildResumeStepRequestInput {
16
+ runId: string
17
+ workflowId: string
18
+ workflowVersion: string
19
+ input: unknown
20
+ journal: JournalSlice
21
+ pendingWaitpoints: readonly WorkflowWaitpointResumeTarget[]
22
+ waitpointId?: string
23
+ waitpointKey?: string
24
+ parkedAt?: number
25
+ resumePayload?: unknown
26
+ resumePayloadRef?: WorkflowPayloadReference
27
+ resolvedAt?: number
28
+ matchedEventId?: string
29
+ source?: "live" | "inbox" | "replay"
30
+ protocolVersion?: ProtocolVersion
31
+ invocationCount: number
32
+ environment: "production" | "preview" | "development"
33
+ deadline: number
34
+ tenantMeta: WorkflowStepRequest["tenantMeta"]
35
+ runMeta: WorkflowStepRequest["runMeta"]
36
+ workflowReleaseId?: string
37
+ releaseId?: string
38
+ bundle?: WorkflowBundleReference
39
+ journalRef?: WorkflowJournalReference
40
+ freshness?: WorkflowActivationFreshness
41
+ }
42
+
43
+ export type BuildResumeStepRequestResult =
44
+ | {
45
+ ok: true
46
+ request: WorkflowStepRequest
47
+ waitpoint: WorkflowWaitpointSnapshot
48
+ }
49
+ | {
50
+ ok: false
51
+ code: "missing_waitpoint_selector" | "waitpoint_not_found"
52
+ message: string
53
+ }
54
+
55
+ export function buildResumeStepRequest(
56
+ input: BuildResumeStepRequestInput,
57
+ ): BuildResumeStepRequestResult {
58
+ const applied = applyWorkflowResumeToJournal({
59
+ journal: input.journal,
60
+ waitpoints: input.pendingWaitpoints,
61
+ waitpointId: input.waitpointId,
62
+ waitpointKey: input.waitpointKey,
63
+ parkedAt: input.parkedAt,
64
+ payload: input.resumePayload,
65
+ payloadRef: input.resumePayloadRef,
66
+ resolvedAt: input.resolvedAt,
67
+ matchedEventId: input.matchedEventId,
68
+ source: input.source,
69
+ })
70
+
71
+ if (!applied.ok) return applied
72
+
73
+ return {
74
+ ok: true,
75
+ waitpoint: applied.waitpoint,
76
+ request: {
77
+ protocolVersion: input.protocolVersion ?? PROTOCOL_VERSION,
78
+ runId: input.runId,
79
+ workflowId: input.workflowId,
80
+ workflowVersion: input.workflowVersion,
81
+ invocationCount: input.invocationCount,
82
+ input: input.input,
83
+ journal: applied.journal,
84
+ environment: input.environment,
85
+ deadline: input.deadline,
86
+ tenantMeta: input.tenantMeta,
87
+ runMeta: input.runMeta,
88
+ activation: {
89
+ kind: "resume",
90
+ workflowReleaseId: input.workflowReleaseId,
91
+ releaseId: input.releaseId,
92
+ bundle: input.bundle,
93
+ journalRef: input.journalRef,
94
+ waitpoint: applied.waitpoint,
95
+ resumePayloadRef: input.resumePayloadRef,
96
+ freshness: input.freshness,
97
+ },
98
+ },
99
+ }
100
+ }