@voyant-travel/workflows-orchestrator 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 (61) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +52 -0
  3. package/README.md +76 -0
  4. package/dist/abort-registry.d.ts +6 -0
  5. package/dist/abort-registry.d.ts.map +1 -0
  6. package/dist/abort-registry.js +37 -0
  7. package/dist/concurrency.d.ts +31 -0
  8. package/dist/concurrency.d.ts.map +1 -0
  9. package/dist/concurrency.js +145 -0
  10. package/dist/drive.d.ts +67 -0
  11. package/dist/drive.d.ts.map +1 -0
  12. package/dist/drive.js +373 -0
  13. package/dist/driver-inmemory.d.ts +30 -0
  14. package/dist/driver-inmemory.d.ts.map +1 -0
  15. package/dist/driver-inmemory.js +394 -0
  16. package/dist/event-router.d.ts +51 -0
  17. package/dist/event-router.d.ts.map +1 -0
  18. package/dist/event-router.js +68 -0
  19. package/dist/http-step-handler.d.ts +25 -0
  20. package/dist/http-step-handler.d.ts.map +1 -0
  21. package/dist/http-step-handler.js +78 -0
  22. package/dist/in-memory-store.d.ts +5 -0
  23. package/dist/in-memory-store.d.ts.map +1 -0
  24. package/dist/in-memory-store.js +41 -0
  25. package/dist/index.d.ts +13 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +22 -0
  28. package/dist/journal-helpers.d.ts +3 -0
  29. package/dist/journal-helpers.d.ts.map +1 -0
  30. package/dist/journal-helpers.js +9 -0
  31. package/dist/orchestrator.d.ts +116 -0
  32. package/dist/orchestrator.d.ts.map +1 -0
  33. package/dist/orchestrator.js +411 -0
  34. package/dist/resume-run.d.ts +40 -0
  35. package/dist/resume-run.d.ts.map +1 -0
  36. package/dist/resume-run.js +119 -0
  37. package/dist/schedule.d.ts +51 -0
  38. package/dist/schedule.d.ts.map +1 -0
  39. package/dist/schedule.js +243 -0
  40. package/dist/testing/driver-compliance.d.ts +58 -0
  41. package/dist/testing/driver-compliance.d.ts.map +1 -0
  42. package/dist/testing/driver-compliance.js +667 -0
  43. package/dist/types.d.ts +182 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +4 -0
  46. package/package.json +51 -0
  47. package/src/__tests__/orchestrator-test-support.ts +18 -0
  48. package/src/abort-registry.ts +41 -0
  49. package/src/concurrency.ts +217 -0
  50. package/src/drive.ts +477 -0
  51. package/src/driver-inmemory.ts +511 -0
  52. package/src/event-router.ts +120 -0
  53. package/src/http-step-handler.ts +112 -0
  54. package/src/in-memory-store.ts +44 -0
  55. package/src/index.ts +73 -0
  56. package/src/journal-helpers.ts +11 -0
  57. package/src/orchestrator.ts +527 -0
  58. package/src/resume-run.ts +162 -0
  59. package/src/schedule.ts +310 -0
  60. package/src/testing/driver-compliance.ts +800 -0
  61. package/src/types.ts +201 -0
@@ -0,0 +1,112 @@
1
+ import { PROTOCOL_VERSION } from "@voyant-travel/workflows/protocol"
2
+ import type { StepHandler, WorkflowStepRequest, WorkflowStepResponse } from "./types.js"
3
+
4
+ interface StepHandlerError {
5
+ error: string
6
+ message: string
7
+ details?: unknown
8
+ }
9
+
10
+ export interface HttpStepTarget {
11
+ url: string
12
+ fetch(request: Request): Promise<Response>
13
+ label?: string
14
+ }
15
+
16
+ export interface HttpStepHandlerDeps {
17
+ /**
18
+ * Resolve the transport target for this workflow-step invocation.
19
+ * Adapters can map `tenantMeta.tenantScript` to a dispatch-namespace
20
+ * binding, a service URL, or a local proxy.
21
+ */
22
+ resolveTarget: (req: WorkflowStepRequest) => HttpStepTarget | Promise<HttpStepTarget>
23
+ /** Optional HMAC signer for the X-Voyant-Dispatch-Auth header. */
24
+ sign?: (body: string, req: WorkflowStepRequest) => Promise<string> | string
25
+ /** Optional logger for step-level observability. */
26
+ logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void
27
+ }
28
+
29
+ /**
30
+ * Build a StepHandler that serializes WorkflowStepRequest over HTTP.
31
+ * The concrete transport target is adapter-specific; the request/response
32
+ * mapping is shared across Cloudflare, Node, and future adapters.
33
+ */
34
+ export function createHttpStepHandler(deps: HttpStepHandlerDeps): StepHandler {
35
+ return async (req: WorkflowStepRequest, stepOpts) => {
36
+ const body = JSON.stringify(req)
37
+ const headers: Record<string, string> = {
38
+ "content-type": "application/json; charset=utf-8",
39
+ "x-voyant-protocol": String(PROTOCOL_VERSION),
40
+ }
41
+ if (deps.sign) {
42
+ headers["x-voyant-dispatch-auth"] = await deps.sign(body, req)
43
+ }
44
+
45
+ const target = await deps.resolveTarget(req)
46
+ deps.logger?.("info", "http-step: invoking tenant step", {
47
+ target: target.label ?? target.url,
48
+ runId: req.runId,
49
+ workflowId: req.workflowId,
50
+ invocation: req.invocationCount,
51
+ })
52
+
53
+ let response: Response
54
+ try {
55
+ response = await target.fetch(
56
+ new Request(target.url, {
57
+ method: "POST",
58
+ headers,
59
+ body,
60
+ signal: stepOpts?.signal,
61
+ }),
62
+ )
63
+ } catch (err) {
64
+ deps.logger?.("error", "http-step: tenant fetch threw", {
65
+ target: target.label ?? target.url,
66
+ runId: req.runId,
67
+ error: err instanceof Error ? err.message : String(err),
68
+ })
69
+ return {
70
+ status: 502,
71
+ body: {
72
+ error: "tenant_unreachable",
73
+ message: err instanceof Error ? err.message : String(err),
74
+ },
75
+ }
76
+ }
77
+
78
+ const text = await response.text()
79
+ let parsed: unknown
80
+ try {
81
+ parsed = JSON.parse(text)
82
+ } catch {
83
+ return {
84
+ status: 502,
85
+ body: {
86
+ error: "tenant_invalid_response",
87
+ message: `tenant returned non-JSON body (HTTP ${response.status})`,
88
+ },
89
+ }
90
+ }
91
+
92
+ if (response.status !== 200) {
93
+ return { status: response.status, body: toErrorBody(parsed, response.status) }
94
+ }
95
+ return { status: 200, body: parsed as WorkflowStepResponse }
96
+ }
97
+ }
98
+
99
+ function toErrorBody(parsed: unknown, fallbackStatus: number): StepHandlerError {
100
+ if (
101
+ parsed !== null &&
102
+ typeof parsed === "object" &&
103
+ typeof (parsed as { error?: unknown }).error === "string" &&
104
+ typeof (parsed as { message?: unknown }).message === "string"
105
+ ) {
106
+ return parsed as StepHandlerError
107
+ }
108
+ return {
109
+ error: "tenant_error",
110
+ message: `tenant returned HTTP ${fallbackStatus}`,
111
+ }
112
+ }
@@ -0,0 +1,44 @@
1
+ // A pure in-memory RunRecordStore. Useful for tests and local-only
2
+ // orchestrator harnesses. The production store is Postgres-backed
3
+ // and lives in voyant-cloud.
4
+
5
+ import type { OrchestratorRunStatus, RunRecord, RunRecordStore } from "./types.js"
6
+
7
+ export function createInMemoryRunStore(): RunRecordStore {
8
+ const records = new Map<string, RunRecord>()
9
+ return {
10
+ async get(id) {
11
+ const r = records.get(id)
12
+ return r ? clone(r) : undefined
13
+ },
14
+ async save(record) {
15
+ records.set(record.id, clone(record))
16
+ return clone(record)
17
+ },
18
+ async tryInsert(record) {
19
+ // Atomic-by-construction: Map.has + Map.set inside a single
20
+ // microtask. Concurrent `tryInsert(idA)` calls all schedule on the
21
+ // same JS event loop and only the first one observes the slot
22
+ // empty — subsequent callers see the inserted record.
23
+ const existing = records.get(record.id)
24
+ if (existing) return { record: clone(existing), created: false }
25
+ records.set(record.id, clone(record))
26
+ return { record: clone(record), created: true }
27
+ },
28
+ async list(filter = {}) {
29
+ let out = [...records.values()].map(clone)
30
+ if (filter.workflowId) out = out.filter((r) => r.workflowId === filter.workflowId)
31
+ if (filter.status) out = out.filter((r) => r.status === filter.status)
32
+ out.sort((a, b) => b.startedAt - a.startedAt)
33
+ if (filter.limit !== undefined) out = out.slice(0, filter.limit)
34
+ return out
35
+ },
36
+ }
37
+ }
38
+
39
+ function clone<T>(value: T): T {
40
+ return structuredClone(value)
41
+ }
42
+
43
+ /** Keep the unused import happy — guards against accidental type-only drift. */
44
+ export type { OrchestratorRunStatus }
package/src/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ // @voyant-travel/workflows-orchestrator
2
+ //
3
+ // Reference orchestrator for Voyant Workflows. Drives runs through
4
+ // the tenant step handler over the v1 wire protocol. Transport- and
5
+ // storage-agnostic: compose with a RunRecordStore of your choice
6
+ // (in-memory for tests, Postgres-backed for production).
7
+ //
8
+ // See docs/runtime-protocol.md §2 + §5 for the contract this
9
+ // implements and docs/design.md §6 for the broader orchestrator
10
+ // state-machine design.
11
+
12
+ export {
13
+ registerRunAbort,
14
+ signalRunAbort,
15
+ unregisterRunAbort,
16
+ } from "./abort-registry.js"
17
+ export {
18
+ type ConcurrencyRunHooks,
19
+ createInProcessConcurrencyCoordinator,
20
+ type InProcessConcurrencyCoordinator,
21
+ type RuntimeConcurrencyPolicy,
22
+ resolveConcurrencyKey,
23
+ WorkflowConcurrencyRejectedError,
24
+ } from "./concurrency.js"
25
+ export { applyWaitpointInjection, type DriveOptions, driveUntilPaused } from "./drive.js"
26
+ export {
27
+ createInMemoryDriver,
28
+ type InMemoryDriverOptions,
29
+ } from "./driver-inmemory.js"
30
+ export {
31
+ type RouteEventArgs,
32
+ type RouterMatch,
33
+ routeEvent,
34
+ } from "./event-router.js"
35
+ export {
36
+ createHttpStepHandler,
37
+ type HttpStepHandlerDeps,
38
+ type HttpStepTarget,
39
+ } from "./http-step-handler.js"
40
+ export { createInMemoryRunStore } from "./in-memory-store.js"
41
+ export { emptyJournal } from "./journal-helpers.js"
42
+ export {
43
+ type CancelArgs,
44
+ cancel,
45
+ type OrchestratorDeps,
46
+ type ResumeArgs,
47
+ type ResumeDueAlarmsArgs,
48
+ resume,
49
+ resumeDueAlarms,
50
+ type TriggerArgs,
51
+ trigger,
52
+ } from "./orchestrator.js"
53
+ export {
54
+ type BuildResumeJournalInput,
55
+ type BuildResumeJournalResult,
56
+ type BuildSeededResumeJournalInput,
57
+ buildResumeJournal,
58
+ buildSeededResumeJournal,
59
+ } from "./resume-run.js"
60
+ export {
61
+ type CronSpec,
62
+ computeNextFire,
63
+ createScheduler,
64
+ manifestScheduleSources,
65
+ nextCronFire,
66
+ parseCron,
67
+ type SchedulableDeclaration,
68
+ type SchedulerDeps,
69
+ type SchedulerHandle,
70
+ type ScheduleSource,
71
+ toMs,
72
+ } from "./schedule.js"
73
+ export * from "./types.js"
@@ -0,0 +1,11 @@
1
+ import type { JournalSlice } from "@voyant-travel/workflows/protocol"
2
+
3
+ export function emptyJournal(): JournalSlice {
4
+ return {
5
+ stepResults: {},
6
+ waitpointsResolved: {},
7
+ compensationsRun: {},
8
+ metadataState: {},
9
+ streamsCompleted: {},
10
+ }
11
+ }