@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,267 @@
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
+ import { executeWorkflowStep, } from "../runtime/executor.js";
20
+ export { executeWorkflowStep };
21
+ import { PROTOCOL_VERSION, } from "../protocol/index.js";
22
+ import { getWorkflow } from "../workflow.js";
23
+ export { buildResumeStepRequest, } from "./resume.js";
24
+ /** Build an HTTP fetch-style handler. */
25
+ export function createStepHandler(deps = {}) {
26
+ return async (req) => {
27
+ if (req.method !== "POST") {
28
+ return jsonResponse(405, errorBody("method_not_allowed", "POST required"));
29
+ }
30
+ try {
31
+ if (deps.verifyRequest)
32
+ await deps.verifyRequest(req);
33
+ }
34
+ catch (err) {
35
+ deps.logger?.("warn", "step handler: auth rejected", {
36
+ error: err instanceof Error ? err.message : String(err),
37
+ });
38
+ return jsonResponse(401, errorBody("unauthorized", errMessage(err)));
39
+ }
40
+ let raw;
41
+ try {
42
+ raw = await req.json();
43
+ }
44
+ catch (err) {
45
+ return jsonResponse(400, errorBody("invalid_json", errMessage(err)));
46
+ }
47
+ // The incoming Request carries its own AbortSignal; threading it
48
+ // through lets `ctx.signal` observe client-side aborts (orchestrator
49
+ // cancellations, closed fetches, etc.) during step execution.
50
+ const out = await runStepInner(raw, deps, { signal: req.signal });
51
+ return jsonResponse(out.status, out.body);
52
+ };
53
+ }
54
+ /**
55
+ * Transport-free entry point. Callers that already parsed the body
56
+ * (e.g. local orchestrator in-memory, tests) invoke this directly.
57
+ * Returns either the step response or an error envelope with the HTTP
58
+ * status the caller should use.
59
+ */
60
+ export async function handleStepRequest(raw, deps = {}, opts = {}) {
61
+ return runStepInner(raw, deps, opts);
62
+ }
63
+ async function runStepInner(raw, deps, opts = {}) {
64
+ const parsed = parseRequest(raw);
65
+ if (!parsed.ok)
66
+ return { status: 400, body: errorBody("invalid_request", parsed.message) };
67
+ const reqBody = parsed.value;
68
+ if (reqBody.protocolVersion !== PROTOCOL_VERSION) {
69
+ return {
70
+ status: 426,
71
+ body: errorBody("protocol_version_mismatch", `tenant supports protocol ${PROTOCOL_VERSION}, got ${String(reqBody.protocolVersion)}`),
72
+ };
73
+ }
74
+ const def = getWorkflow(reqBody.workflowId);
75
+ if (!def) {
76
+ return {
77
+ status: 404,
78
+ body: errorBody("workflow_not_found", `workflow "${reqBody.workflowId}" is not registered in this bundle`),
79
+ };
80
+ }
81
+ const now = deps.now ?? (() => Date.now());
82
+ const stepRunner = createInProcessStepRunner(now);
83
+ const runtimeEnv = {
84
+ run: {
85
+ id: reqBody.runId,
86
+ number: reqBody.runMeta.number,
87
+ attempt: reqBody.runMeta.attempt,
88
+ triggeredBy: reqBody.runMeta.triggeredBy,
89
+ tags: reqBody.runMeta.tags,
90
+ startedAt: reqBody.runMeta.startedAt,
91
+ },
92
+ workflow: { id: reqBody.workflowId, version: reqBody.workflowVersion },
93
+ environment: { name: reqBody.environment },
94
+ project: {
95
+ id: reqBody.tenantMeta.projectId,
96
+ slug: reqBody.tenantMeta.projectSlug ?? reqBody.tenantMeta.projectId,
97
+ },
98
+ organization: {
99
+ id: reqBody.tenantMeta.organizationId,
100
+ slug: reqBody.tenantMeta.organizationSlug ?? reqBody.tenantMeta.organizationId,
101
+ },
102
+ };
103
+ try {
104
+ const response = await executeWorkflowStep(def, {
105
+ runId: reqBody.runId,
106
+ workflowId: reqBody.workflowId,
107
+ workflowVersion: reqBody.workflowVersion,
108
+ input: reqBody.input,
109
+ journal: reqBody.journal,
110
+ invocationCount: reqBody.invocationCount,
111
+ environment: runtimeEnv,
112
+ triggeredBy: reqBody.runMeta.triggeredBy,
113
+ runStartedAt: reqBody.runMeta.startedAt,
114
+ tags: reqBody.runMeta.tags,
115
+ stepRunner,
116
+ nodeStepRunner: deps.nodeStepRunner,
117
+ rateLimiter: deps.rateLimiter,
118
+ services: deps.services,
119
+ now,
120
+ abortSignal: opts.signal,
121
+ onStreamChunk: opts.onStreamChunk,
122
+ });
123
+ return { status: 200, body: response };
124
+ }
125
+ catch (err) {
126
+ deps.logger?.("error", "step handler: executor threw", {
127
+ error: err instanceof Error ? err.message : String(err),
128
+ });
129
+ return {
130
+ status: 500,
131
+ body: errorBody("executor_error", errMessage(err)),
132
+ };
133
+ }
134
+ }
135
+ /**
136
+ * Build a step runner that executes the step body in the same
137
+ * process. Suitable for `runtime: "edge"`. Container-runtime steps
138
+ * will swap this for a dispatching runner that POSTs to a pod.
139
+ */
140
+ function createInProcessStepRunner(now) {
141
+ return async ({ stepId: _stepId, attempt, fn, stepCtx }) => {
142
+ const startedAt = now();
143
+ try {
144
+ const output = await fn(stepCtx);
145
+ return {
146
+ attempt,
147
+ status: "ok",
148
+ output,
149
+ startedAt,
150
+ finishedAt: now(),
151
+ };
152
+ }
153
+ catch (err) {
154
+ const e = err;
155
+ const code = typeof err.code === "string"
156
+ ? err.code
157
+ : "UNKNOWN";
158
+ const retryAfter = err.retryAfter;
159
+ return {
160
+ attempt,
161
+ status: "err",
162
+ error: {
163
+ category: "USER_ERROR",
164
+ code,
165
+ message: e?.message ?? String(err),
166
+ name: e?.name,
167
+ stack: e?.stack,
168
+ data: retryAfter !== undefined ? { retryAfter } : undefined,
169
+ },
170
+ startedAt,
171
+ finishedAt: now(),
172
+ };
173
+ }
174
+ };
175
+ }
176
+ // ---- Parsing ----
177
+ function parseRequest(raw) {
178
+ if (raw === null || typeof raw !== "object") {
179
+ return { ok: false, message: "body must be a JSON object" };
180
+ }
181
+ const r = raw;
182
+ const required = [
183
+ "protocolVersion",
184
+ "runId",
185
+ "workflowId",
186
+ "workflowVersion",
187
+ "invocationCount",
188
+ "journal",
189
+ "environment",
190
+ "deadline",
191
+ "tenantMeta",
192
+ "runMeta",
193
+ ];
194
+ for (const k of required) {
195
+ if (!(k in r))
196
+ return { ok: false, message: `missing required field "${k}"` };
197
+ }
198
+ if (typeof r.protocolVersion !== "number") {
199
+ return { ok: false, message: "`protocolVersion` must be a number" };
200
+ }
201
+ if (typeof r.runId !== "string" || r.runId.length === 0) {
202
+ return { ok: false, message: "`runId` must be a non-empty string" };
203
+ }
204
+ if (typeof r.workflowId !== "string" || r.workflowId.length === 0) {
205
+ return { ok: false, message: "`workflowId` must be a non-empty string" };
206
+ }
207
+ if (typeof r.workflowVersion !== "string" || r.workflowVersion.length === 0) {
208
+ return { ok: false, message: "`workflowVersion` must be a non-empty string" };
209
+ }
210
+ if (typeof r.invocationCount !== "number" || r.invocationCount < 1) {
211
+ return { ok: false, message: "`invocationCount` must be >= 1" };
212
+ }
213
+ if (typeof r.deadline !== "number") {
214
+ return { ok: false, message: "`deadline` must be a number" };
215
+ }
216
+ if (!r.journal || typeof r.journal !== "object") {
217
+ return { ok: false, message: "`journal` must be an object" };
218
+ }
219
+ const env = r.environment;
220
+ if (env !== "production" && env !== "preview" && env !== "development") {
221
+ return { ok: false, message: "`environment` must be production | preview | development" };
222
+ }
223
+ if (!r.tenantMeta || typeof r.tenantMeta !== "object") {
224
+ return { ok: false, message: "`tenantMeta` must be an object" };
225
+ }
226
+ if (!r.runMeta || typeof r.runMeta !== "object") {
227
+ return { ok: false, message: "`runMeta` must be an object" };
228
+ }
229
+ return {
230
+ ok: true,
231
+ value: {
232
+ protocolVersion: r.protocolVersion,
233
+ runId: r.runId,
234
+ workflowId: r.workflowId,
235
+ workflowVersion: r.workflowVersion,
236
+ invocationCount: r.invocationCount,
237
+ input: r.input,
238
+ journal: r.journal,
239
+ environment: env,
240
+ deadline: r.deadline,
241
+ tenantMeta: r.tenantMeta,
242
+ runMeta: r.runMeta,
243
+ activation: isObjectRecord(r.activation)
244
+ ? r.activation
245
+ : undefined,
246
+ },
247
+ };
248
+ }
249
+ // ---- Helpers ----
250
+ function jsonResponse(status, body) {
251
+ return new Response(JSON.stringify(body), {
252
+ status,
253
+ headers: { "content-type": "application/json; charset=utf-8" },
254
+ });
255
+ }
256
+ function errorBody(error, message, details) {
257
+ const out = { error, message };
258
+ if (details !== undefined)
259
+ out.details = details;
260
+ return out;
261
+ }
262
+ function errMessage(err) {
263
+ return err instanceof Error ? err.message : String(err);
264
+ }
265
+ function isObjectRecord(value) {
266
+ return typeof value === "object" && value !== null && !Array.isArray(value);
267
+ }
@@ -0,0 +1,41 @@
1
+ import { type ProtocolVersion, type WorkflowActivationFreshness, type WorkflowBundleReference, type WorkflowJournalReference, type WorkflowPayloadReference, type WorkflowWaitpointResumeTarget, type WorkflowWaitpointSnapshot } from "../protocol/index.js";
2
+ import type { JournalSlice } from "../runtime/journal.js";
3
+ import type { WorkflowStepRequest } from "./index.js";
4
+ export interface BuildResumeStepRequestInput {
5
+ runId: string;
6
+ workflowId: string;
7
+ workflowVersion: string;
8
+ input: unknown;
9
+ journal: JournalSlice;
10
+ pendingWaitpoints: readonly WorkflowWaitpointResumeTarget[];
11
+ waitpointId?: string;
12
+ waitpointKey?: string;
13
+ parkedAt?: number;
14
+ resumePayload?: unknown;
15
+ resumePayloadRef?: WorkflowPayloadReference;
16
+ resolvedAt?: number;
17
+ matchedEventId?: string;
18
+ source?: "live" | "inbox" | "replay";
19
+ protocolVersion?: ProtocolVersion;
20
+ invocationCount: number;
21
+ environment: "production" | "preview" | "development";
22
+ deadline: number;
23
+ tenantMeta: WorkflowStepRequest["tenantMeta"];
24
+ runMeta: WorkflowStepRequest["runMeta"];
25
+ workflowReleaseId?: string;
26
+ releaseId?: string;
27
+ bundle?: WorkflowBundleReference;
28
+ journalRef?: WorkflowJournalReference;
29
+ freshness?: WorkflowActivationFreshness;
30
+ }
31
+ export type BuildResumeStepRequestResult = {
32
+ ok: true;
33
+ request: WorkflowStepRequest;
34
+ waitpoint: WorkflowWaitpointSnapshot;
35
+ } | {
36
+ ok: false;
37
+ code: "missing_waitpoint_selector" | "waitpoint_not_found";
38
+ message: string;
39
+ };
40
+ export declare function buildResumeStepRequest(input: BuildResumeStepRequestInput): BuildResumeStepRequestResult;
41
+ //# sourceMappingURL=resume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/handler/resume.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,YAAY,CAAA;IACrB,iBAAiB,EAAE,SAAS,6BAA6B,EAAE,CAAA;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gBAAgB,CAAC,EAAE,wBAAwB,CAAA;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;IACpC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,YAAY,GAAG,SAAS,GAAG,aAAa,CAAA;IACrD,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAA;IAC7C,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAA;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,uBAAuB,CAAA;IAChC,UAAU,CAAC,EAAE,wBAAwB,CAAA;IACrC,SAAS,CAAC,EAAE,2BAA2B,CAAA;CACxC;AAED,MAAM,MAAM,4BAA4B,GACpC;IACE,EAAE,EAAE,IAAI,CAAA;IACR,OAAO,EAAE,mBAAmB,CAAA;IAC5B,SAAS,EAAE,yBAAyB,CAAA;CACrC,GACD;IACE,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,4BAA4B,GAAG,qBAAqB,CAAA;IAC1D,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAEL,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,2BAA2B,GACjC,4BAA4B,CA2C9B"}
@@ -0,0 +1,44 @@
1
+ import { applyWorkflowResumeToJournal, PROTOCOL_VERSION, } from "../protocol/index.js";
2
+ export function buildResumeStepRequest(input) {
3
+ const applied = applyWorkflowResumeToJournal({
4
+ journal: input.journal,
5
+ waitpoints: input.pendingWaitpoints,
6
+ waitpointId: input.waitpointId,
7
+ waitpointKey: input.waitpointKey,
8
+ parkedAt: input.parkedAt,
9
+ payload: input.resumePayload,
10
+ payloadRef: input.resumePayloadRef,
11
+ resolvedAt: input.resolvedAt,
12
+ matchedEventId: input.matchedEventId,
13
+ source: input.source,
14
+ });
15
+ if (!applied.ok)
16
+ return applied;
17
+ return {
18
+ ok: true,
19
+ waitpoint: applied.waitpoint,
20
+ request: {
21
+ protocolVersion: input.protocolVersion ?? PROTOCOL_VERSION,
22
+ runId: input.runId,
23
+ workflowId: input.workflowId,
24
+ workflowVersion: input.workflowVersion,
25
+ invocationCount: input.invocationCount,
26
+ input: input.input,
27
+ journal: applied.journal,
28
+ environment: input.environment,
29
+ deadline: input.deadline,
30
+ tenantMeta: input.tenantMeta,
31
+ runMeta: input.runMeta,
32
+ activation: {
33
+ kind: "resume",
34
+ workflowReleaseId: input.workflowReleaseId,
35
+ releaseId: input.releaseId,
36
+ bundle: input.bundle,
37
+ journalRef: input.journalRef,
38
+ waitpoint: applied.waitpoint,
39
+ resumePayloadRef: input.resumePayloadRef,
40
+ freshness: input.freshness,
41
+ },
42
+ },
43
+ };
44
+ }
@@ -0,0 +1,54 @@
1
+ import type { WorkflowDriver } from "./driver.js";
2
+ /**
3
+ * Minimum interface a Hono-shaped app exposes that we use. `app.post(...)`
4
+ * and `app.get(...)` register handlers; the handler signature mirrors
5
+ * Hono's `Context`-style callback for portability — we only read the
6
+ * request body and request params via the framework's response helpers.
7
+ */
8
+ export interface HttpAppLike {
9
+ post(path: string, handler: HttpHandler): unknown;
10
+ get(path: string, handler: HttpHandler): unknown;
11
+ }
12
+ /**
13
+ * Minimum context shape we read off Hono. Restricted to body parsing,
14
+ * route params, and JSON response helpers.
15
+ */
16
+ export interface HttpContextLike {
17
+ req: {
18
+ json(): Promise<unknown>;
19
+ param(name: string): string | undefined;
20
+ header(name: string): string | undefined;
21
+ raw: Request;
22
+ };
23
+ json(body: unknown, status?: number): Response;
24
+ text(body: string, status?: number): Response;
25
+ status(code: number): unknown;
26
+ }
27
+ export type HttpHandler = (ctx: HttpContextLike) => Promise<Response> | Response;
28
+ export interface MountHttpIngestAdapterOptions {
29
+ /**
30
+ * Driver the adapter forwards into. Typically the same instance
31
+ * `createApp({ workflows: { driver } })` constructed.
32
+ */
33
+ driver: WorkflowDriver;
34
+ /** Mount path. Defaults to `"/api/workflows"`. */
35
+ basePath?: string;
36
+ /**
37
+ * Optional auth check. Receives the original `Request` and returns
38
+ * `void` on success / throws on failure. Reuse
39
+ * `createBearerVerifier(...)` from `@voyant-travel/workflows/auth` for the
40
+ * canonical bearer-token shape.
41
+ */
42
+ verifyRequest?: (req: Request) => void | Promise<void>;
43
+ }
44
+ /**
45
+ * Mount the adapter onto a Hono-shaped app. Registers:
46
+ *
47
+ * POST {basePath}/events → driver.ingestEvent
48
+ * POST {basePath}/manifests → driver.registerManifest
49
+ * GET {basePath}/manifests/:env → driver.getManifest
50
+ *
51
+ * Returns the mounted base path so callers can log it.
52
+ */
53
+ export declare function mountHttpIngestAdapter(app: HttpAppLike, opts: MountHttpIngestAdapterOptions): string;
54
+ //# sourceMappingURL=http-ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-ingest.d.ts","sourceRoot":"","sources":["../src/http-ingest.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,aAAa,CAAA;AAOlE;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAA;IACjD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAA;CACjD;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE;QACH,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;QACxB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;QACvC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;QACxC,GAAG,EAAE,OAAO,CAAA;KACb,CAAA;IACD,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;IAC9C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAC9B;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAA;AAEhF,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,MAAM,EAAE,cAAc,CAAA;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvD;AAID;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,6BAA6B,GAClC,MAAM,CAyFR"}
@@ -0,0 +1,214 @@
1
+ // Optional HTTP ingest adapter — mounts `/api/manifests` and `/api/events`
2
+ // on a Hono-shaped app, forwarding into a `WorkflowDriver`.
3
+ //
4
+ // Self-host Mode 2 deployments mount this when external emitters need to
5
+ // fire events into the runtime (storefront BFF, third-party webhooks,
6
+ // sibling-process pairs across machines). voyant-cloud always mounts it
7
+ // at its HTTP boundary.
8
+ //
9
+ // Transport-agnostic: takes a minimal `HttpAppLike` interface so the SDK
10
+ // stays a leaf package (no `hono` dep). `@voyant-travel/voyant-hono`'s `Hono`
11
+ // instance satisfies the shape via TypeScript structural compat.
12
+ //
13
+ // Architecture: docs/architecture/workflows-runtime-architecture.md §15.4.
14
+ const ALLOWED_ENVS = new Set(["production", "preview", "development"]);
15
+ // ---- Mount ----
16
+ /**
17
+ * Mount the adapter onto a Hono-shaped app. Registers:
18
+ *
19
+ * POST {basePath}/events → driver.ingestEvent
20
+ * POST {basePath}/manifests → driver.registerManifest
21
+ * GET {basePath}/manifests/:env → driver.getManifest
22
+ *
23
+ * Returns the mounted base path so callers can log it.
24
+ */
25
+ export function mountHttpIngestAdapter(app, opts) {
26
+ const base = (opts.basePath ?? "/api/workflows").replace(/\/$/, "");
27
+ app.post(`${base}/events`, async (ctx) => {
28
+ if (opts.verifyRequest) {
29
+ try {
30
+ await opts.verifyRequest(ctx.req.raw);
31
+ }
32
+ catch (err) {
33
+ return ctx.json({ error: "unauthorized", message: errMessage(err) }, 401);
34
+ }
35
+ }
36
+ let raw;
37
+ try {
38
+ raw = await ctx.req.json();
39
+ }
40
+ catch (err) {
41
+ return ctx.json({ error: "invalid_json", message: errMessage(err) }, 400);
42
+ }
43
+ const validation = validateIngestBody(raw);
44
+ if (!validation.ok)
45
+ return ctx.json(validation.error, 400);
46
+ const args = {
47
+ environment: validation.body.environment,
48
+ envelope: validation.body.envelope,
49
+ idempotencyKey: validation.body.idempotencyKey,
50
+ };
51
+ const result = await opts.driver.ingestEvent(args);
52
+ if (!result.ok && result.reason === "manifest_not_registered") {
53
+ return ctx.json(result, 200);
54
+ }
55
+ if (!result.ok) {
56
+ return ctx.json(result, 502);
57
+ }
58
+ return ctx.json(result, 200);
59
+ });
60
+ app.post(`${base}/manifests`, async (ctx) => {
61
+ if (opts.verifyRequest) {
62
+ try {
63
+ await opts.verifyRequest(ctx.req.raw);
64
+ }
65
+ catch (err) {
66
+ return ctx.json({ error: "unauthorized", message: errMessage(err) }, 401);
67
+ }
68
+ }
69
+ let raw;
70
+ try {
71
+ raw = await ctx.req.json();
72
+ }
73
+ catch (err) {
74
+ return ctx.json({ error: "invalid_json", message: errMessage(err) }, 400);
75
+ }
76
+ const validation = validateRegisterBody(raw);
77
+ if (!validation.ok)
78
+ return ctx.json(validation.error, 400);
79
+ try {
80
+ const result = await opts.driver.registerManifest({
81
+ environment: validation.body.environment,
82
+ manifest: validation.body.manifest, // structurally compatible
83
+ });
84
+ return ctx.json({ ok: true, versionId: result.versionId }, 200);
85
+ }
86
+ catch (err) {
87
+ return ctx.json({ error: "register_failed", message: errMessage(err) }, 500);
88
+ }
89
+ });
90
+ app.get(`${base}/manifests/:env`, async (ctx) => {
91
+ if (opts.verifyRequest) {
92
+ try {
93
+ await opts.verifyRequest(ctx.req.raw);
94
+ }
95
+ catch (err) {
96
+ return ctx.json({ error: "unauthorized", message: errMessage(err) }, 401);
97
+ }
98
+ }
99
+ const env = ctx.req.param("env");
100
+ if (!env || !ALLOWED_ENVS.has(env)) {
101
+ return ctx.json({
102
+ error: "invalid_environment",
103
+ message: `environment must be one of ${[...ALLOWED_ENVS].join(", ")}`,
104
+ }, 400);
105
+ }
106
+ const manifest = await opts.driver.getManifest({ environment: env });
107
+ if (!manifest) {
108
+ return ctx.json({ error: "not_found", environment: env }, 404);
109
+ }
110
+ return ctx.json({ environment: env, versionId: manifest.versionId, manifest }, 200);
111
+ });
112
+ return base;
113
+ }
114
+ function validateIngestBody(raw) {
115
+ if (typeof raw !== "object" || raw === null) {
116
+ return { ok: false, error: { error: "invalid_body", message: "expected JSON object" } };
117
+ }
118
+ const r = raw;
119
+ if (typeof r.environment !== "string" || !ALLOWED_ENVS.has(r.environment)) {
120
+ return {
121
+ ok: false,
122
+ error: {
123
+ error: "invalid_body",
124
+ message: `"environment" must be one of ${[...ALLOWED_ENVS].join(", ")}`,
125
+ },
126
+ };
127
+ }
128
+ if (typeof r.envelope !== "object" || r.envelope === null) {
129
+ return { ok: false, error: { error: "invalid_body", message: '"envelope" must be an object' } };
130
+ }
131
+ const envelope = r.envelope;
132
+ if (typeof envelope.name !== "string" || envelope.name.length === 0) {
133
+ return {
134
+ ok: false,
135
+ error: { error: "invalid_body", message: '"envelope.name" must be a non-empty string' },
136
+ };
137
+ }
138
+ if (typeof envelope.emittedAt !== "string" || envelope.emittedAt.length === 0) {
139
+ return {
140
+ ok: false,
141
+ error: {
142
+ error: "invalid_body",
143
+ message: '"envelope.emittedAt" must be an ISO timestamp string',
144
+ },
145
+ };
146
+ }
147
+ if (envelope.metadata !== undefined &&
148
+ (typeof envelope.metadata !== "object" || envelope.metadata === null)) {
149
+ return {
150
+ ok: false,
151
+ error: {
152
+ error: "invalid_body",
153
+ message: '"envelope.metadata" must be an object when supplied',
154
+ },
155
+ };
156
+ }
157
+ if (r.idempotencyKey !== undefined && typeof r.idempotencyKey !== "string") {
158
+ return {
159
+ ok: false,
160
+ error: { error: "invalid_body", message: '"idempotencyKey" must be a string when supplied' },
161
+ };
162
+ }
163
+ return {
164
+ ok: true,
165
+ body: {
166
+ environment: r.environment,
167
+ envelope: {
168
+ name: envelope.name,
169
+ data: envelope.data,
170
+ metadata: envelope.metadata,
171
+ emittedAt: envelope.emittedAt,
172
+ },
173
+ idempotencyKey: r.idempotencyKey,
174
+ },
175
+ };
176
+ }
177
+ function validateRegisterBody(raw) {
178
+ if (typeof raw !== "object" || raw === null) {
179
+ return { ok: false, error: { error: "invalid_body", message: "expected JSON object" } };
180
+ }
181
+ const r = raw;
182
+ if (typeof r.environment !== "string" || !ALLOWED_ENVS.has(r.environment)) {
183
+ return {
184
+ ok: false,
185
+ error: {
186
+ error: "invalid_body",
187
+ message: `"environment" must be one of ${[...ALLOWED_ENVS].join(", ")}`,
188
+ },
189
+ };
190
+ }
191
+ if (typeof r.manifest !== "object" || r.manifest === null) {
192
+ return { ok: false, error: { error: "invalid_body", message: '"manifest" must be an object' } };
193
+ }
194
+ const manifest = r.manifest;
195
+ if (typeof manifest.versionId !== "string" || manifest.versionId.length === 0) {
196
+ return {
197
+ ok: false,
198
+ error: {
199
+ error: "invalid_body",
200
+ message: '"manifest.versionId" must be a non-empty string',
201
+ },
202
+ };
203
+ }
204
+ return {
205
+ ok: true,
206
+ body: {
207
+ environment: r.environment,
208
+ manifest: manifest,
209
+ },
210
+ };
211
+ }
212
+ function errMessage(err) {
213
+ return err instanceof Error ? err.message : String(err);
214
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./conditions.js";
2
+ export { FatalError, HookConflictError, QuotaExceededError, RetryableError, TimeoutError, ValidationError, } from "./errors.js";
3
+ export * from "./trigger.js";
4
+ export * from "./types.js";
5
+ export * from "./workflow.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,eAAe,GAChB,MAAM,aAAa,CAAA;AACpB,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // @voyant-travel/workflows
2
+ //
3
+ // Authoring SDK for Voyant Workflows. Full contract in:
4
+ // docs/sdk-surface.md §2–§8
5
+ // docs/design.md §3–§4
6
+ export * from "./conditions.js";
7
+ export { FatalError, HookConflictError, QuotaExceededError, RetryableError, TimeoutError, ValidationError, } from "./errors.js";
8
+ export * from "./trigger.js";
9
+ export * from "./types.js";
10
+ export * from "./workflow.js";