@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
package/src/client.ts ADDED
@@ -0,0 +1,498 @@
1
+ // Client-safe workflow API for app/server code that triggers or forwards
2
+ // workflow activity to a managed Voyant Cloud runtime.
3
+
4
+ import type { IngestEventArgs, IngestEventResponse, WorkflowDriver } from "./driver.js"
5
+ import type { WorkflowManifest } from "./protocol/index.js"
6
+ import type { Duration, EnvironmentName, RunStatus } from "./types.js"
7
+ import type { WorkflowHandle } from "./workflow.js"
8
+
9
+ export interface WorkflowsClient {
10
+ trigger<TIn, TOut>(
11
+ workflow: WorkflowHandle<TIn, TOut> | string,
12
+ input: TIn,
13
+ opts?: TriggerOptions,
14
+ ): Promise<Run<TOut>>
15
+
16
+ signal(runId: string, name: string, payload: unknown, opts?: { nonce?: string }): Promise<void>
17
+ completeToken(tokenId: string, payload: unknown): Promise<void>
18
+
19
+ cancel(runId: string, opts?: { compensate?: boolean; reason?: string }): Promise<void>
20
+ retry(runId: string, opts: { mode: "re-trigger" | "resume" }): Promise<Run>
21
+ replay(runId: string, opts?: { fromStepId?: string; input?: unknown }): Promise<Run>
22
+
23
+ get(runId: string): Promise<RunDetail>
24
+ list(opts?: ListRunsOptions): Promise<{ runs: RunSummary[]; nextCursor?: string }>
25
+
26
+ mintAccessToken(opts: MintAccessTokenOptions): Promise<PublicAccessToken>
27
+ }
28
+
29
+ export interface TriggerOptions {
30
+ idempotencyKey?: string
31
+ delay?: Duration | Date
32
+ debounce?: { key: string; delay: Duration; mode?: "leading" | "trailing" }
33
+ ttl?: Duration
34
+ tags?: string[]
35
+ priority?: number
36
+ concurrencyKey?: string
37
+ lockToVersion?: string
38
+ environment?: EnvironmentName
39
+ issuePublicAccessToken?: boolean
40
+ }
41
+
42
+ export interface Run<TOut = unknown> {
43
+ id: string
44
+ workflowId: string
45
+ status: RunStatus
46
+ startedAt: number
47
+ accessToken?: string
48
+ /** Phantom; used only for TypeScript inference. */
49
+ readonly __output?: TOut
50
+ }
51
+
52
+ export interface RunSummary {
53
+ id: string
54
+ workflowId: string
55
+ status: RunStatus
56
+ startedAt: number
57
+ completedAt?: number
58
+ tags: string[]
59
+ environment: EnvironmentName
60
+ }
61
+
62
+ export interface RunDetail<TOut = unknown> extends RunSummary {
63
+ version: string
64
+ input: unknown
65
+ output?: TOut
66
+ error?: unknown
67
+ durationMs?: number
68
+ }
69
+
70
+ export interface ListRunsOptions {
71
+ workflowId?: string
72
+ status?: RunStatus | RunStatus[]
73
+ environment?: EnvironmentName
74
+ tag?: string
75
+ since?: Date | number
76
+ until?: Date | number
77
+ cursor?: string
78
+ limit?: number
79
+ }
80
+
81
+ export interface MintAccessTokenOptions {
82
+ target:
83
+ | { kind: "run"; runId: string }
84
+ | { kind: "workflow"; workflowId: string }
85
+ | { kind: "tag"; tag: string }
86
+ scope: ("read" | "trigger" | "cancel")[]
87
+ ttl?: Duration
88
+ }
89
+
90
+ export interface PublicAccessToken {
91
+ token: string
92
+ exp: number
93
+ }
94
+
95
+ export interface CloudWorkflowsClientEnv {
96
+ VOYANT_CLOUD_WORKFLOWS_URL?: string
97
+ VOYANT_CLOUD_WORKFLOW_TRIGGER_TOKEN?: string
98
+ VOYANT_CLOUD_APP_SLUG?: string
99
+ VOYANT_CLOUD_ENVIRONMENT?: string
100
+ }
101
+
102
+ export interface CloudWorkflowsClientOptions {
103
+ baseUrl?: string
104
+ triggerToken?: string
105
+ appSlug?: string
106
+ environment?: EnvironmentName
107
+ fetch?: typeof fetch
108
+ env?: CloudWorkflowsClientEnv
109
+ }
110
+
111
+ export interface CloudWorkflowDriverOptions extends CloudWorkflowsClientOptions {
112
+ /**
113
+ * Managed Cloud deployments should leave this disabled. Workflow releases
114
+ * are created by the deployment/control-plane path, not by the app runtime.
115
+ * The enabled mode exists only for explicitly wired adapters that own their
116
+ * release-registration boundary.
117
+ */
118
+ manifestRegistration?: "disabled" | "enabled"
119
+ }
120
+
121
+ const DEFAULT_ENVIRONMENT: EnvironmentName = "production"
122
+
123
+ let configuredClient: WorkflowsClient | undefined
124
+
125
+ /**
126
+ * Install the process-local `workflows` client implementation used by the
127
+ * root `@voyant-travel/workflows` singleton. Apps may call this during boot, or
128
+ * rely on deployment-injected Voyant Cloud environment variables.
129
+ */
130
+ export function configureWorkflowsClient(client: WorkflowsClient): void {
131
+ configuredClient = client
132
+ }
133
+
134
+ export function getConfiguredWorkflowsClient(): WorkflowsClient | undefined {
135
+ return configuredClient
136
+ }
137
+
138
+ export const workflows: WorkflowsClient = new Proxy({} as WorkflowsClient, {
139
+ get(_, method: keyof WorkflowsClient & string) {
140
+ return (...args: unknown[]) => {
141
+ const client = configuredClient ?? tryCreateCloudClientFromEnv()
142
+ if (!client) {
143
+ throw new Error(
144
+ `@voyant-travel/workflows: workflows.${method}() requires a configured workflows client. ` +
145
+ `Use configureWorkflowsClient(createCloudWorkflowsClient(...)) or provide the ` +
146
+ `VOYANT_CLOUD_WORKFLOWS_URL, VOYANT_CLOUD_WORKFLOW_TRIGGER_TOKEN, ` +
147
+ `VOYANT_CLOUD_APP_SLUG, and VOYANT_CLOUD_ENVIRONMENT deployment variables.`,
148
+ )
149
+ }
150
+ const fn = client[method]
151
+ if (typeof fn !== "function") {
152
+ throw new Error(`@voyant-travel/workflows: workflows.${method} is not implemented`)
153
+ }
154
+ return (fn as (...inner: unknown[]) => unknown)(...args)
155
+ }
156
+ },
157
+ })
158
+
159
+ export function createCloudWorkflowsClient(
160
+ options: CloudWorkflowsClientOptions = {},
161
+ ): WorkflowsClient {
162
+ const config = resolveCloudConfig(options)
163
+ const httpFetch = options.fetch ?? globalThis.fetch
164
+ if (typeof httpFetch !== "function") {
165
+ throw new Error("@voyant-travel/workflows/client: global fetch is unavailable")
166
+ }
167
+
168
+ return {
169
+ async trigger<TIn, TOut>(
170
+ workflow: WorkflowHandle<TIn, TOut> | string,
171
+ input: TIn,
172
+ opts?: TriggerOptions,
173
+ ): Promise<Run<TOut>> {
174
+ const workflowId = workflowIdOf(workflow)
175
+ const environment = opts?.environment ?? config.environment
176
+ const res = await cloudFetch(httpFetch, config, {
177
+ method: "POST",
178
+ path: `/apps/${encodeURIComponent(config.appSlug)}/${environment}/workflows/${encodeURIComponent(
179
+ workflowId,
180
+ )}/runs`,
181
+ body: {
182
+ input,
183
+ options: serializeTriggerOptions(opts),
184
+ },
185
+ idempotencyKey: opts?.idempotencyKey,
186
+ })
187
+ return normalizeRun<TOut>(await readJson(res), workflowId)
188
+ },
189
+
190
+ signal() {
191
+ return unsupported("signal")
192
+ },
193
+ completeToken() {
194
+ return unsupported("completeToken")
195
+ },
196
+ cancel() {
197
+ return unsupported("cancel")
198
+ },
199
+ retry() {
200
+ return unsupported("retry")
201
+ },
202
+ replay() {
203
+ return unsupported("replay")
204
+ },
205
+ get() {
206
+ return unsupported("get")
207
+ },
208
+ list() {
209
+ return unsupported("list")
210
+ },
211
+ mintAccessToken() {
212
+ return unsupported("mintAccessToken")
213
+ },
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Cloud-mode driver for framework event forwarding. It does not execute runs
219
+ * locally; it forwards triggers/events/manifests to the hosted runtime using
220
+ * the same deployment-scoped credentials as the client.
221
+ */
222
+ export function createCloudWorkflowDriver(
223
+ options: CloudWorkflowDriverOptions = {},
224
+ ): WorkflowDriver {
225
+ const client = createCloudWorkflowsClient(options)
226
+ const config = resolveCloudConfig(options)
227
+ const httpFetch = options.fetch ?? globalThis.fetch
228
+ const manifestRegistration = options.manifestRegistration ?? "disabled"
229
+
230
+ return {
231
+ async registerManifest(args): Promise<{ versionId: string }> {
232
+ if (manifestRegistration === "disabled") {
233
+ return { versionId: args.manifest.versionId }
234
+ }
235
+ const res = await cloudFetch(httpFetch, config, {
236
+ method: "POST",
237
+ path: `/apps/${encodeURIComponent(config.appSlug)}/${args.environment}/workflow-releases`,
238
+ body: { manifest: args.manifest },
239
+ })
240
+ const body = await readJson(res)
241
+ return {
242
+ versionId:
243
+ readString(body, ["versionId"]) ??
244
+ readString(body, ["data", "versionId"]) ??
245
+ args.manifest.versionId,
246
+ }
247
+ },
248
+ trigger(workflow, input, opts) {
249
+ return client.trigger(workflow, input, opts)
250
+ },
251
+ async ingestEvent(args: IngestEventArgs): Promise<IngestEventResponse> {
252
+ const eventId =
253
+ typeof args.envelope.metadata?.eventId === "string"
254
+ ? args.envelope.metadata.eventId
255
+ : args.idempotencyKey
256
+ const res = await cloudFetch(httpFetch, config, {
257
+ method: "POST",
258
+ path: `/apps/${encodeURIComponent(config.appSlug)}/${args.environment}/events`,
259
+ body: {
260
+ envelope: args.envelope,
261
+ idempotencyKey: args.idempotencyKey,
262
+ },
263
+ idempotencyKey: eventId,
264
+ })
265
+ return (await readJson(res)) as IngestEventResponse
266
+ },
267
+ async getManifest(args): Promise<WorkflowManifest | null> {
268
+ const res = await cloudFetch(httpFetch, config, {
269
+ method: "GET",
270
+ path: `/apps/${encodeURIComponent(config.appSlug)}/${args.environment}/workflow-releases/current`,
271
+ allowStatuses: [404],
272
+ })
273
+ if (res.status === 404) return null
274
+ const body = await readJson(res)
275
+ return (readRecord(body, ["manifest"]) ??
276
+ readRecord(body, ["data", "manifest"]) ??
277
+ body) as WorkflowManifest
278
+ },
279
+ }
280
+ }
281
+
282
+ interface ResolvedCloudConfig {
283
+ baseUrl: string
284
+ triggerToken: string
285
+ appSlug: string
286
+ environment: EnvironmentName
287
+ }
288
+
289
+ interface CloudFetchArgs {
290
+ method: "GET" | "POST"
291
+ path: string
292
+ body?: unknown
293
+ idempotencyKey?: string
294
+ allowStatuses?: number[]
295
+ }
296
+
297
+ function tryCreateCloudClientFromEnv(): WorkflowsClient | undefined {
298
+ const env = defaultEnv()
299
+ if (
300
+ !env.VOYANT_CLOUD_WORKFLOWS_URL ||
301
+ !env.VOYANT_CLOUD_WORKFLOW_TRIGGER_TOKEN ||
302
+ !env.VOYANT_CLOUD_APP_SLUG
303
+ ) {
304
+ return undefined
305
+ }
306
+ const client = createCloudWorkflowsClient({ env })
307
+ configuredClient = client
308
+ return client
309
+ }
310
+
311
+ function resolveCloudConfig(options: CloudWorkflowsClientOptions): ResolvedCloudConfig {
312
+ const env = { ...defaultEnv(), ...options.env }
313
+ const baseUrl = options.baseUrl ?? env.VOYANT_CLOUD_WORKFLOWS_URL
314
+ const triggerToken = options.triggerToken ?? env.VOYANT_CLOUD_WORKFLOW_TRIGGER_TOKEN
315
+ const appSlug = options.appSlug ?? env.VOYANT_CLOUD_APP_SLUG
316
+ const environment = parseEnvironment(
317
+ options.environment ?? env.VOYANT_CLOUD_ENVIRONMENT ?? DEFAULT_ENVIRONMENT,
318
+ )
319
+
320
+ const missing = [
321
+ ["baseUrl", baseUrl],
322
+ ["triggerToken", triggerToken],
323
+ ["appSlug", appSlug],
324
+ ]
325
+ .filter(([, value]) => !value)
326
+ .map(([name]) => name)
327
+ if (missing.length > 0) {
328
+ throw new Error(
329
+ `@voyant-travel/workflows/client: missing Cloud workflow configuration: ${missing.join(", ")}`,
330
+ )
331
+ }
332
+
333
+ return {
334
+ baseUrl: stripTrailingSlash(requireConfigValue(baseUrl, "baseUrl")),
335
+ triggerToken: requireConfigValue(triggerToken, "triggerToken"),
336
+ appSlug: requireConfigValue(appSlug, "appSlug"),
337
+ environment,
338
+ }
339
+ }
340
+
341
+ function requireConfigValue(value: string | undefined, name: string): string {
342
+ if (!value) {
343
+ throw new Error(
344
+ `@voyant-travel/workflows/client: missing Cloud workflow configuration: ${name}`,
345
+ )
346
+ }
347
+ return value
348
+ }
349
+
350
+ async function cloudFetch(
351
+ httpFetch: typeof fetch,
352
+ config: ResolvedCloudConfig,
353
+ args: CloudFetchArgs,
354
+ ): Promise<Response> {
355
+ const headers = new Headers({
356
+ accept: "application/json",
357
+ authorization: `Bearer ${config.triggerToken}`,
358
+ })
359
+ if (args.body !== undefined) headers.set("content-type", "application/json")
360
+ if (args.idempotencyKey) headers.set("idempotency-key", args.idempotencyKey)
361
+
362
+ const res = await httpFetch(`${config.baseUrl}/cloud/v1${args.path}`, {
363
+ method: args.method,
364
+ headers,
365
+ body: args.body === undefined ? undefined : JSON.stringify(args.body),
366
+ })
367
+ if (!res.ok && !args.allowStatuses?.includes(res.status)) {
368
+ throw new Error(
369
+ `Voyant Cloud workflows request failed: ${res.status} ${res.statusText} ${await res.text()}`,
370
+ )
371
+ }
372
+ return res
373
+ }
374
+
375
+ async function readJson(res: Response): Promise<unknown> {
376
+ const text = await res.text()
377
+ if (text.length === 0) return {}
378
+ return JSON.parse(text) as unknown
379
+ }
380
+
381
+ function normalizeRun<TOut>(body: unknown, fallbackWorkflowId: string): Run<TOut> {
382
+ const source =
383
+ readRecord(body, ["data", "run"]) ??
384
+ readRecord(body, ["run"]) ??
385
+ readRecord(body, ["data"]) ??
386
+ asRecord(body)
387
+ const id = readString(source, ["id"]) ?? readString(source, ["runId"])
388
+ if (!id) {
389
+ throw new Error("Voyant Cloud workflows response did not include a run id")
390
+ }
391
+ return {
392
+ id,
393
+ workflowId:
394
+ readString(source, ["workflowId"]) ??
395
+ readString(source, ["workflow", "id"]) ??
396
+ fallbackWorkflowId,
397
+ status: parseRunStatus(readString(source, ["status"]) ?? "pending"),
398
+ startedAt: readNumber(source, ["startedAt"]) ?? Date.now(),
399
+ accessToken: readString(source, ["accessToken"]),
400
+ }
401
+ }
402
+
403
+ function serializeTriggerOptions(opts: TriggerOptions | undefined): Record<string, unknown> {
404
+ if (!opts) return {}
405
+ const out: Record<string, unknown> = {}
406
+ for (const [key, value] of Object.entries(opts)) {
407
+ if (value === undefined || key === "environment") continue
408
+ out[key] = value instanceof Date ? value.toISOString() : value
409
+ }
410
+ return out
411
+ }
412
+
413
+ function workflowIdOf(workflow: WorkflowHandle<unknown, unknown> | string): string {
414
+ return typeof workflow === "string" ? workflow : workflow.id
415
+ }
416
+
417
+ function unsupported(method: string): never {
418
+ throw new Error(
419
+ `@voyant-travel/workflows/client: workflows.${method}() is not supported by the ` +
420
+ `managed Cloud trigger client yet.`,
421
+ )
422
+ }
423
+
424
+ function defaultEnv(): CloudWorkflowsClientEnv {
425
+ const processEnv = (
426
+ globalThis as typeof globalThis & {
427
+ process?: { env?: CloudWorkflowsClientEnv }
428
+ }
429
+ ).process?.env
430
+ return processEnv ?? {}
431
+ }
432
+
433
+ function parseEnvironment(value: string): EnvironmentName {
434
+ if (value === "production" || value === "preview" || value === "development") return value
435
+ throw new Error(
436
+ `@voyant-travel/workflows/client: invalid environment "${value}"; expected production, preview, or development`,
437
+ )
438
+ }
439
+
440
+ function parseRunStatus(value: string): RunStatus {
441
+ const normalized = value.toLowerCase()
442
+ if (
443
+ normalized === "pending" ||
444
+ normalized === "queued" ||
445
+ normalized === "running" ||
446
+ normalized === "waiting" ||
447
+ normalized === "completed" ||
448
+ normalized === "failed" ||
449
+ normalized === "cancelled" ||
450
+ normalized === "cancelled_by_dev_reload" ||
451
+ normalized === "cancelled_by_version_sunset" ||
452
+ normalized === "compensated" ||
453
+ normalized === "compensation_failed" ||
454
+ normalized === "timed_out"
455
+ ) {
456
+ return normalized
457
+ }
458
+ return "pending"
459
+ }
460
+
461
+ function stripTrailingSlash(value: string): string {
462
+ return value.replace(/\/+$/, "")
463
+ }
464
+
465
+ function asRecord(value: unknown): Record<string, unknown> {
466
+ return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {}
467
+ }
468
+
469
+ function readRecord(value: unknown, path: string[]): Record<string, unknown> | undefined {
470
+ const current = readAtPath(value, path)
471
+ return typeof current === "object" && current !== null
472
+ ? (current as Record<string, unknown>)
473
+ : undefined
474
+ }
475
+
476
+ function readString(value: unknown, path: string[]): string | undefined {
477
+ const current = readAtPath(value, path)
478
+ return typeof current === "string" && current.length > 0 ? current : undefined
479
+ }
480
+
481
+ function readNumber(value: unknown, path: string[]): number | undefined {
482
+ const current = readAtPath(value, path)
483
+ if (typeof current === "number") return current
484
+ if (typeof current === "string" && current.length > 0) {
485
+ const parsed = Number(current)
486
+ if (Number.isFinite(parsed)) return parsed
487
+ }
488
+ return undefined
489
+ }
490
+
491
+ function readAtPath(value: unknown, path: string[]): unknown {
492
+ let current: unknown = value
493
+ for (const part of path) {
494
+ if (typeof current !== "object" || current === null) return undefined
495
+ current = (current as Record<string, unknown>)[part]
496
+ }
497
+ return current
498
+ }
@@ -0,0 +1,43 @@
1
+ // Condition types used in step options (waitFor / cancelIf / skipIf)
2
+ // and in event-filter match expressions.
3
+ // Authoritative contract in docs/sdk-surface.md §4.
4
+
5
+ import type { Duration, RunStatus } from "./types.js"
6
+
7
+ export interface EventCondition {
8
+ event: string
9
+ match?: Record<string, unknown> | ((payload: unknown) => boolean)
10
+ }
11
+
12
+ export interface SignalCondition {
13
+ signal: string
14
+ match?: Record<string, unknown> | ((payload: unknown) => boolean)
15
+ }
16
+
17
+ export interface TimeCondition {
18
+ after?: Duration | Date
19
+ before?: Duration | Date
20
+ }
21
+
22
+ export interface RunStatusCondition {
23
+ run: { id: string; status: RunStatus[] }
24
+ }
25
+
26
+ export interface OrCondition {
27
+ or: Condition[]
28
+ }
29
+
30
+ export interface AndCondition {
31
+ and: Condition[]
32
+ }
33
+
34
+ export type Condition =
35
+ | EventCondition
36
+ | SignalCondition
37
+ | TimeCondition
38
+ | RunStatusCondition
39
+ | OrCondition
40
+ | AndCondition
41
+
42
+ export const or = (...conditions: Condition[]): OrCondition => ({ or: conditions })
43
+ export const and = (...conditions: Condition[]): AndCondition => ({ and: conditions })
package/src/config.ts ADDED
@@ -0,0 +1,114 @@
1
+ // @voyant-travel/workflows/config
2
+ //
3
+ // Types and `defineConfig` helper for `voyant.config.ts`.
4
+ // Contract defined in docs/sdk-surface.md §10 and docs/design.md §5.4.3.
5
+
6
+ export type Duration = number | `${number}${"ms" | "s" | "m" | "h" | "d" | "w"}`
7
+
8
+ /** Cloudflare Container instance types — see `@voyant-travel/workflows` for the size table. */
9
+ export type MachineType =
10
+ | "lite"
11
+ | "basic"
12
+ | "standard-1"
13
+ | "standard-2"
14
+ | "standard-3"
15
+ | "standard-4"
16
+ | (string & {})
17
+
18
+ export type EnvironmentName = "production" | "preview" | "development"
19
+
20
+ export type MeterKey =
21
+ | "edgeCpuMs"
22
+ | "containerSeconds"
23
+ | "warmSlotHours"
24
+ | "runCount"
25
+ | "activeScheduleHours"
26
+ | "payloadStorageGbHour"
27
+ | "retentionRunMonths"
28
+
29
+ export type BindingDeclaration =
30
+ | { type: "d1"; name: string }
31
+ | { type: "r2"; name: string }
32
+ | { type: "kv"; name: string }
33
+ | { type: "queue"; name: string }
34
+
35
+ export interface EnvironmentConfig {
36
+ customDomain?: string
37
+ }
38
+
39
+ export interface RetryPolicy {
40
+ max?: number
41
+ backoff?: "exponential" | "linear" | "fixed"
42
+ initial?: Duration
43
+ maxDelay?: Duration
44
+ }
45
+
46
+ export interface BuildExtension {
47
+ name: string
48
+ /** Open-ended hook surface; each extension defines its own contract. */
49
+ [key: string]: unknown
50
+ }
51
+
52
+ export interface Instrumentation {
53
+ name: string
54
+ [key: string]: unknown
55
+ }
56
+
57
+ export interface WorkflowsConfig {
58
+ dirs?: string[]
59
+ defaults?: {
60
+ retries?: RetryPolicy
61
+ timeout?: Duration
62
+ timezone?: string
63
+ machine?: MachineType
64
+ concurrency?: { strategy?: "queue" | "cancel-in-progress" | "cancel-newest" | "round-robin" }
65
+ }
66
+ containerPool?: {
67
+ defaultMachine?: MachineType
68
+ maxConcurrency?: number
69
+ warmPoolSize?: Partial<Record<EnvironmentName, number>>
70
+ evictionTtl?: Duration
71
+ perPodConcurrency?: number
72
+ scaleOutPolicy?: "none" | "onDemand"
73
+ preWarmStrategy?: "onFirstRequest" | "onDeploy"
74
+ }
75
+ build?: {
76
+ extensions?: BuildExtension[]
77
+ defineEnv?: Record<string, string>
78
+ }
79
+ instrumentations?: Instrumentation[]
80
+ dev?: {
81
+ port?: number
82
+ preservation?: "smart" | "always" | "never"
83
+ simulateEnvironment?: EnvironmentName
84
+ }
85
+ billing?: {
86
+ caps?: {
87
+ monthly?: Partial<Record<MeterKey, number>>
88
+ action?: "hard-stop" | "soft-notify"
89
+ perEnvironment?: Partial<
90
+ Record<EnvironmentName, { monthly?: Partial<Record<MeterKey, number>> }>
91
+ >
92
+ }
93
+ }
94
+ versioning?: {
95
+ retireAfter?: Duration
96
+ onSunset?: "cancel" | "migrate" | "extend"
97
+ sunsetNoticePeriod?: Duration
98
+ }
99
+ }
100
+
101
+ export interface VoyantConfig {
102
+ projectId: string
103
+ entry: {
104
+ worker: string
105
+ container?: string
106
+ }
107
+ environments: Record<EnvironmentName, EnvironmentConfig>
108
+ bindings: Record<string, BindingDeclaration>
109
+ workflows?: WorkflowsConfig
110
+ }
111
+
112
+ export function defineConfig(config: VoyantConfig): VoyantConfig {
113
+ return config
114
+ }