@shware/workflow 2.0.1 → 3.0.0

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 (228) hide show
  1. package/README.md +96 -2
  2. package/dist/apns-relay.cjs +4 -17
  3. package/dist/apns-relay.cjs.map +1 -1
  4. package/dist/apns-relay.mjs +4 -17
  5. package/dist/apns-relay.mjs.map +1 -1
  6. package/dist/aws/engine-step.cjs +104 -0
  7. package/dist/aws/engine-step.cjs.map +1 -0
  8. package/dist/aws/engine-step.d.cts +28 -0
  9. package/dist/aws/engine-step.d.cts.map +1 -0
  10. package/dist/aws/engine-step.d.mts +28 -0
  11. package/dist/aws/engine-step.d.mts.map +1 -0
  12. package/dist/aws/engine-step.mjs +102 -0
  13. package/dist/aws/engine-step.mjs.map +1 -0
  14. package/dist/aws/function-url.cjs +62 -0
  15. package/dist/aws/function-url.cjs.map +1 -0
  16. package/dist/aws/function-url.d.cts +38 -0
  17. package/dist/aws/function-url.d.cts.map +1 -0
  18. package/dist/aws/function-url.d.mts +38 -0
  19. package/dist/aws/function-url.d.mts.map +1 -0
  20. package/dist/aws/function-url.mjs +59 -0
  21. package/dist/aws/function-url.mjs.map +1 -0
  22. package/dist/aws/index.cjs +160 -0
  23. package/dist/aws/index.cjs.map +1 -0
  24. package/dist/aws/index.d.cts +62 -0
  25. package/dist/aws/index.d.cts.map +1 -0
  26. package/dist/aws/index.d.mts +62 -0
  27. package/dist/aws/index.d.mts.map +1 -0
  28. package/dist/aws/index.mjs +148 -0
  29. package/dist/aws/index.mjs.map +1 -0
  30. package/dist/aws/instances.cjs +176 -0
  31. package/dist/aws/instances.cjs.map +1 -0
  32. package/dist/aws/instances.d.cts +75 -0
  33. package/dist/aws/instances.d.cts.map +1 -0
  34. package/dist/aws/instances.d.mts +75 -0
  35. package/dist/aws/instances.d.mts.map +1 -0
  36. package/dist/aws/instances.mjs +172 -0
  37. package/dist/aws/instances.mjs.map +1 -0
  38. package/dist/aws/lambda-api.cjs +89 -0
  39. package/dist/aws/lambda-api.cjs.map +1 -0
  40. package/dist/aws/lambda-api.d.cts +43 -0
  41. package/dist/aws/lambda-api.d.cts.map +1 -0
  42. package/dist/aws/lambda-api.d.mts +43 -0
  43. package/dist/aws/lambda-api.d.mts.map +1 -0
  44. package/dist/aws/lambda-api.mjs +88 -0
  45. package/dist/aws/lambda-api.mjs.map +1 -0
  46. package/dist/client.d.cts +1 -1
  47. package/dist/client.d.mts +1 -1
  48. package/dist/cloudflare/bindings.cjs +19 -0
  49. package/dist/cloudflare/bindings.cjs.map +1 -1
  50. package/dist/cloudflare/bindings.d.cts +37 -0
  51. package/dist/cloudflare/bindings.d.cts.map +1 -1
  52. package/dist/cloudflare/bindings.d.mts +37 -0
  53. package/dist/cloudflare/bindings.d.mts.map +1 -1
  54. package/dist/cloudflare/bindings.mjs +18 -1
  55. package/dist/cloudflare/bindings.mjs.map +1 -1
  56. package/dist/cloudflare/index.cjs +32 -28
  57. package/dist/cloudflare/index.d.cts +8 -6
  58. package/dist/cloudflare/index.d.mts +8 -6
  59. package/dist/cloudflare/index.mjs +12 -10
  60. package/dist/cloudflare/runner.cjs +27 -76
  61. package/dist/cloudflare/runner.cjs.map +1 -1
  62. package/dist/cloudflare/runner.d.cts +7 -2
  63. package/dist/cloudflare/runner.d.cts.map +1 -1
  64. package/dist/cloudflare/runner.d.mts +7 -2
  65. package/dist/cloudflare/runner.d.mts.map +1 -1
  66. package/dist/cloudflare/runner.mjs +26 -75
  67. package/dist/cloudflare/runner.mjs.map +1 -1
  68. package/dist/cloudflare/worker.cjs +22 -203
  69. package/dist/cloudflare/worker.cjs.map +1 -1
  70. package/dist/cloudflare/worker.d.cts +10 -56
  71. package/dist/cloudflare/worker.d.cts.map +1 -1
  72. package/dist/cloudflare/worker.d.mts +10 -56
  73. package/dist/cloudflare/worker.d.mts.map +1 -1
  74. package/dist/cloudflare/worker.mjs +20 -199
  75. package/dist/cloudflare/worker.mjs.map +1 -1
  76. package/dist/email-sender.cjs +62 -0
  77. package/dist/email-sender.cjs.map +1 -0
  78. package/dist/email-sender.d.cts +70 -0
  79. package/dist/email-sender.d.cts.map +1 -0
  80. package/dist/email-sender.d.mts +70 -0
  81. package/dist/email-sender.d.mts.map +1 -0
  82. package/dist/email-sender.mjs +60 -0
  83. package/dist/email-sender.mjs.map +1 -0
  84. package/dist/email.cjs +21 -0
  85. package/dist/email.cjs.map +1 -1
  86. package/dist/email.d.cts +6 -0
  87. package/dist/email.d.cts.map +1 -1
  88. package/dist/email.d.mts +6 -0
  89. package/dist/email.d.mts.map +1 -1
  90. package/dist/email.mjs +21 -1
  91. package/dist/email.mjs.map +1 -1
  92. package/dist/engine/index.cjs +1 -1
  93. package/dist/engine/index.mjs +1 -1
  94. package/dist/engine/interpreter.cjs +1 -1
  95. package/dist/engine/interpreter.cjs.map +1 -1
  96. package/dist/engine/interpreter.mjs +1 -1
  97. package/dist/engine/interpreter.mjs.map +1 -1
  98. package/dist/engine/ports.d.cts +6 -1
  99. package/dist/engine/ports.d.cts.map +1 -1
  100. package/dist/engine/ports.d.mts +6 -1
  101. package/dist/engine/ports.d.mts.map +1 -1
  102. package/dist/{cloudflare → host}/channels.cjs +13 -11
  103. package/dist/host/channels.cjs.map +1 -0
  104. package/dist/{cloudflare → host}/channels.d.cts +8 -3
  105. package/dist/host/channels.d.cts.map +1 -0
  106. package/dist/{cloudflare → host}/channels.d.mts +8 -3
  107. package/dist/host/channels.d.mts.map +1 -0
  108. package/dist/{cloudflare → host}/channels.mjs +9 -7
  109. package/dist/host/channels.mjs.map +1 -0
  110. package/dist/host/execute.cjs +82 -0
  111. package/dist/host/execute.cjs.map +1 -0
  112. package/dist/host/execute.d.cts +41 -0
  113. package/dist/host/execute.d.cts.map +1 -0
  114. package/dist/host/execute.d.mts +41 -0
  115. package/dist/host/execute.d.mts.map +1 -0
  116. package/dist/host/execute.mjs +81 -0
  117. package/dist/host/execute.mjs.map +1 -0
  118. package/dist/host/journey-host.cjs +214 -0
  119. package/dist/host/journey-host.cjs.map +1 -0
  120. package/dist/host/journey-host.d.cts +105 -0
  121. package/dist/host/journey-host.d.cts.map +1 -0
  122. package/dist/host/journey-host.d.mts +105 -0
  123. package/dist/host/journey-host.d.mts.map +1 -0
  124. package/dist/host/journey-host.mjs +210 -0
  125. package/dist/host/journey-host.mjs.map +1 -0
  126. package/dist/{cloudflare → host}/router.cjs +28 -9
  127. package/dist/host/router.cjs.map +1 -0
  128. package/dist/{cloudflare → host}/router.d.cts +2 -2
  129. package/dist/host/router.d.cts.map +1 -0
  130. package/dist/{cloudflare → host}/router.d.mts +2 -2
  131. package/dist/host/router.d.mts.map +1 -0
  132. package/dist/{cloudflare → host}/router.mjs +28 -9
  133. package/dist/host/router.mjs.map +1 -0
  134. package/dist/host/senders.cjs +63 -0
  135. package/dist/host/senders.cjs.map +1 -0
  136. package/dist/host/senders.d.cts +31 -0
  137. package/dist/host/senders.d.cts.map +1 -0
  138. package/dist/host/senders.d.mts +31 -0
  139. package/dist/host/senders.d.mts.map +1 -0
  140. package/dist/host/senders.mjs +59 -0
  141. package/dist/host/senders.mjs.map +1 -0
  142. package/dist/http2-client.cjs +88 -0
  143. package/dist/http2-client.cjs.map +1 -0
  144. package/dist/http2-client.d.cts +44 -0
  145. package/dist/http2-client.d.cts.map +1 -0
  146. package/dist/http2-client.d.mts +44 -0
  147. package/dist/http2-client.d.mts.map +1 -0
  148. package/dist/http2-client.mjs +86 -0
  149. package/dist/http2-client.mjs.map +1 -0
  150. package/dist/index.cjs +1 -0
  151. package/dist/index.d.cts +2 -2
  152. package/dist/index.d.mts +2 -2
  153. package/dist/index.mjs +2 -2
  154. package/dist/react-email.cjs.map +1 -1
  155. package/dist/react-email.d.cts +1 -2
  156. package/dist/react-email.d.cts.map +1 -1
  157. package/dist/react-email.d.mts +1 -2
  158. package/dist/react-email.d.mts.map +1 -1
  159. package/dist/react-email.mjs.map +1 -1
  160. package/dist/store/d1.cjs +1 -0
  161. package/dist/store/d1.cjs.map +1 -1
  162. package/dist/store/d1.d.cts.map +1 -1
  163. package/dist/store/d1.d.mts.map +1 -1
  164. package/dist/store/d1.mjs +1 -0
  165. package/dist/store/d1.mjs.map +1 -1
  166. package/dist/store/index.cjs.map +1 -1
  167. package/dist/store/index.d.cts +63 -0
  168. package/dist/store/index.d.cts.map +1 -1
  169. package/dist/store/index.d.mts +63 -0
  170. package/dist/store/index.d.mts.map +1 -1
  171. package/dist/store/index.mjs.map +1 -1
  172. package/dist/store/postgres.cjs +143 -2
  173. package/dist/store/postgres.cjs.map +1 -1
  174. package/dist/store/postgres.d.cts +4 -1
  175. package/dist/store/postgres.d.cts.map +1 -1
  176. package/dist/store/postgres.d.mts +4 -1
  177. package/dist/store/postgres.d.mts.map +1 -1
  178. package/dist/store/postgres.mjs +143 -2
  179. package/dist/store/postgres.mjs.map +1 -1
  180. package/dist/store/tables.cjs +26 -0
  181. package/dist/store/tables.cjs.map +1 -1
  182. package/dist/store/tables.d.cts +1 -0
  183. package/dist/store/tables.d.cts.map +1 -1
  184. package/dist/store/tables.d.mts +1 -0
  185. package/dist/store/tables.d.mts.map +1 -1
  186. package/dist/store/tables.mjs +26 -0
  187. package/dist/store/tables.mjs.map +1 -1
  188. package/dist/transports/cloudflare-email.cjs +59 -0
  189. package/dist/transports/cloudflare-email.cjs.map +1 -0
  190. package/dist/transports/cloudflare-email.d.cts +38 -0
  191. package/dist/transports/cloudflare-email.d.cts.map +1 -0
  192. package/dist/transports/cloudflare-email.d.mts +38 -0
  193. package/dist/transports/cloudflare-email.d.mts.map +1 -0
  194. package/dist/transports/cloudflare-email.mjs +57 -0
  195. package/dist/transports/cloudflare-email.mjs.map +1 -0
  196. package/dist/transports/ses.cjs +91 -0
  197. package/dist/transports/ses.cjs.map +1 -0
  198. package/dist/transports/ses.d.cts +39 -0
  199. package/dist/transports/ses.d.cts.map +1 -0
  200. package/dist/transports/ses.d.mts +39 -0
  201. package/dist/transports/ses.d.mts.map +1 -0
  202. package/dist/transports/ses.mjs +89 -0
  203. package/dist/transports/ses.mjs.map +1 -0
  204. package/dist/transports/sigv4.cjs +83 -0
  205. package/dist/transports/sigv4.cjs.map +1 -0
  206. package/dist/transports/sigv4.d.cts +27 -0
  207. package/dist/transports/sigv4.d.cts.map +1 -0
  208. package/dist/transports/sigv4.d.mts +27 -0
  209. package/dist/transports/sigv4.d.mts.map +1 -0
  210. package/dist/transports/sigv4.mjs +82 -0
  211. package/dist/transports/sigv4.mjs.map +1 -0
  212. package/package.json +53 -1
  213. package/dist/cloudflare/channels.cjs.map +0 -1
  214. package/dist/cloudflare/channels.d.cts.map +0 -1
  215. package/dist/cloudflare/channels.d.mts.map +0 -1
  216. package/dist/cloudflare/channels.mjs.map +0 -1
  217. package/dist/cloudflare/router.cjs.map +0 -1
  218. package/dist/cloudflare/router.d.cts.map +0 -1
  219. package/dist/cloudflare/router.d.mts.map +0 -1
  220. package/dist/cloudflare/router.mjs.map +0 -1
  221. package/dist/cloudflare/senders.cjs +0 -121
  222. package/dist/cloudflare/senders.cjs.map +0 -1
  223. package/dist/cloudflare/senders.d.cts +0 -72
  224. package/dist/cloudflare/senders.d.cts.map +0 -1
  225. package/dist/cloudflare/senders.d.mts +0 -72
  226. package/dist/cloudflare/senders.d.mts.map +0 -1
  227. package/dist/cloudflare/senders.mjs +0 -115
  228. package/dist/cloudflare/senders.mjs.map +0 -1
@@ -0,0 +1,148 @@
1
+ import { http2Fetch } from "../http2-client.mjs";
2
+ import { AwsEngineStep, stepRetry } from "./engine-step.mjs";
3
+ import { fromResponse, isFunctionUrlEvent, toRequest } from "./function-url.mjs";
4
+ import { sweepSegments } from "../host/router.mjs";
5
+ import { executeJourney } from "../host/execute.mjs";
6
+ import { createJourneyHost } from "../host/journey-host.mjs";
7
+ import { AwsInstances, RUN_EVENT_TYPE, executionName, isRunInput } from "./instances.mjs";
8
+ import { sdkLambdaApi } from "./lambda-api.mjs";
9
+ import { withDurableExecution } from "@aws/durable-execution-sdk-js";
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ //#region src/aws/index.ts
12
+ /**
13
+ * `@shware/workflow/aws` — the journey host as one Lambda durable function behind a Function
14
+ * URL, on Postgres.
15
+ *
16
+ * The one handler tells three inputs apart by the event's top-level shape: a Function URL
17
+ * request (the HTTP API — ingest, identify, devices, tracking, bundle, preview), an internal run
18
+ * (one journey instance: one durable execution, started by an asynchronous invoke of this
19
+ * function's own numbered version), and a scheduled maintenance tick. An HTTP body can never
20
+ * pass for the other two: the Function URL always wraps it.
21
+ *
22
+ * const journeys = journeyLambda({
23
+ * config,
24
+ * env, // parsed from process.env (zod)
25
+ * store: (env, database) => postgresJsStore(env.DATABASE_URL, database),
26
+ * email: (env) => sesEmailTransport({ region: env.AWS_REGION }),
27
+ * });
28
+ * export const handler = journeys.handler;
29
+ */
30
+ /** The scheduled tick (EventBridge Scheduler's payload): the segment sweep and maintenance. */
31
+ const SWEEP_EVENT_TYPE = "$workflow.sweep";
32
+ /** This function's ARN without its qualifier, from the ARN it was invoked under. */
33
+ function unqualified(invokedFunctionArn) {
34
+ return invokedFunctionArn.split(":").slice(0, 7).join(":");
35
+ }
36
+ const json = (body, status = 200) => new Response(JSON.stringify(body), {
37
+ status,
38
+ headers: { "content-type": "application/json" }
39
+ });
40
+ function journeyLambda(options) {
41
+ const { env: baseEnv, lambda: lambdaOption, startRetryMs, ...hostOptions } = options;
42
+ const host = createJourneyHost({
43
+ ...hostOptions,
44
+ store: (env, database) => options.store(env, database)
45
+ }, { apnsFetch: http2Fetch() });
46
+ const lambda = lambdaOption ?? sdkLambdaApi();
47
+ const ownHashes = new Set(host.bundle().workflows.map((workflow) => workflow.contentHash));
48
+ const scope = new AsyncLocalStorage();
49
+ /** The version this code was published as: `$LATEST` while developing. */
50
+ const ownVersion = () => process.env.AWS_LAMBDA_FUNCTION_VERSION ?? "$LATEST";
51
+ /** Numbered versions are ordered: the bundle compiled into a later one goes live by itself. */
52
+ const bundleVersion = () => {
53
+ const version = ownVersion();
54
+ return /^\d+$/.test(version) ? {
55
+ id: version,
56
+ createdAt: Number(version)
57
+ } : void 0;
58
+ };
59
+ /** The store and the env (with the instance port) of this invocation, opened on first use. */
60
+ const open = (context) => {
61
+ const invocation = scope.getStore();
62
+ if (invocation === void 0) throw new Error("journeyLambda: outside an invocation");
63
+ const placeholder = { ...baseEnv };
64
+ invocation.lease ??= host.lease(placeholder);
65
+ const { store } = invocation.lease;
66
+ const executions = store.executions;
67
+ if (executions === void 0) throw new Error("journeyLambda needs a store with an execution registry (PostgresJourneyStore)");
68
+ const functionArn = unqualified(context.lambdaContext.invokedFunctionArn);
69
+ const target = async (contentHash) => {
70
+ if (ownHashes.has(contentHash)) return `${functionArn}:${ownVersion()}`;
71
+ const live = await store.liveBundleVersion?.();
72
+ if (live !== void 0 && /^\d+$/.test(live.id)) return `${functionArn}:${live.id}`;
73
+ throw new Error(`no published version carries workflow IR ${contentHash}`);
74
+ };
75
+ const instances = new AwsInstances({
76
+ store,
77
+ executions,
78
+ lambda,
79
+ target,
80
+ ...startRetryMs === void 0 ? {} : { startRetryMs }
81
+ });
82
+ return {
83
+ store,
84
+ env: {
85
+ ...baseEnv,
86
+ JOURNEY: instances
87
+ },
88
+ executions
89
+ };
90
+ };
91
+ const run = async (input, context) => {
92
+ const { store, env, executions } = open(context);
93
+ const { instanceId, params } = input;
94
+ await context.step("$workflow:started", async () => {
95
+ await executions.markSubmitted(instanceId, context.executionContext.durableExecutionArn, Date.now());
96
+ });
97
+ return executeJourney({
98
+ instanceId,
99
+ params,
100
+ enteredAtMs: input.enteredAtMs,
101
+ step: new AwsEngineStep(context, store, executions, lambda, params.userId, instanceId)
102
+ }, host.runParts(env, store, params));
103
+ };
104
+ const http = (event, context) => {
105
+ const serve = async () => {
106
+ const method = event.requestContext.http.method;
107
+ try {
108
+ const { store, env } = open(context);
109
+ const request = toRequest(event);
110
+ if (new URL(request.url).pathname === "/deploy") return await fromResponse(json({ error: "deploy by publishing a version (POST /bundle/deploy applies its own bundle)" }, 409), method);
111
+ await host.applyOwnBundle(env, bundleVersion());
112
+ return await fromResponse(await host.handle(request, env, store), method);
113
+ } catch (error) {
114
+ console.error("[workflow] request failed", error);
115
+ return fromResponse(json({ error: "internal error" }, 500), method);
116
+ }
117
+ };
118
+ const method = event.requestContext.http.method.toUpperCase();
119
+ if (method === "GET" || method === "HEAD") return serve();
120
+ return context.step("http", serve, { retryStrategy: () => ({ shouldRetry: false }) });
121
+ };
122
+ const sweep = (context) => context.step("sweep", async () => {
123
+ const { store, env } = open(context);
124
+ await host.applyOwnBundle(env, bundleVersion());
125
+ return sweepSegments(env, store);
126
+ }, { retryStrategy: () => ({ shouldRetry: false }) });
127
+ const durable = withDurableExecution(async (input, context) => {
128
+ if (isFunctionUrlEvent(input)) return http(input, context);
129
+ if (isRunInput(input)) return run(input, context);
130
+ if (typeof input === "object" && input !== null && input.type === "$workflow.sweep") return sweep(context);
131
+ throw new Error("journeyLambda: unrecognized event (not a Function URL request, run or sweep)");
132
+ });
133
+ const handler = async (event, context) => {
134
+ const invocation = {};
135
+ try {
136
+ return await scope.run(invocation, () => durable(event, context));
137
+ } finally {
138
+ await invocation.lease?.close().catch((error) => {
139
+ console.error("[workflow] closing the store failed", error);
140
+ });
141
+ }
142
+ };
143
+ return { handler };
144
+ }
145
+ //#endregion
146
+ export { AwsEngineStep, AwsInstances, RUN_EVENT_TYPE, SWEEP_EVENT_TYPE, executionName, fromResponse, isFunctionUrlEvent, isRunInput, journeyLambda, sdkLambdaApi, stepRetry, toRequest };
147
+
148
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/aws/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n type DurableContext,\n type DurableExecutionInvocationInput,\n type DurableExecutionInvocationOutput,\n withDurableExecution,\n} from '@aws/durable-execution-sdk-js';\nimport type { JourneyEnv } from '../cloudflare/bindings';\nimport type { JourneyOutcome } from '../engine/ports';\nimport { executeJourney } from '../host/execute';\nimport { type JourneyHostOptions, createJourneyHost } from '../host/journey-host';\nimport { type SweepResult, sweepSegments } from '../host/router';\nimport { http2Fetch } from '../http2-client';\nimport type { BundleVersion, ExecutionRegistry, JourneyStore, StoreLease } from '../store/index';\nimport { AwsEngineStep } from './engine-step';\nimport {\n type FunctionUrlEvent,\n type FunctionUrlResult,\n fromResponse,\n isFunctionUrlEvent,\n toRequest,\n} from './function-url';\nimport { AwsInstances, type RunInput, isRunInput } from './instances';\nimport { type LambdaApi, sdkLambdaApi } from './lambda-api';\n\n/**\n * `@shware/workflow/aws` — the journey host as one Lambda durable function behind a Function\n * URL, on Postgres.\n *\n * The one handler tells three inputs apart by the event's top-level shape: a Function URL\n * request (the HTTP API — ingest, identify, devices, tracking, bundle, preview), an internal run\n * (one journey instance: one durable execution, started by an asynchronous invoke of this\n * function's own numbered version), and a scheduled maintenance tick. An HTTP body can never\n * pass for the other two: the Function URL always wraps it.\n *\n * const journeys = journeyLambda({\n * config,\n * env, // parsed from process.env (zod)\n * store: (env, database) => postgresJsStore(env.DATABASE_URL, database),\n * email: (env) => sesEmailTransport({ region: env.AWS_REGION }),\n * });\n * export const handler = journeys.handler;\n */\n\n/** The scheduled tick (EventBridge Scheduler's payload): the segment sweep and maintenance. */\nexport const SWEEP_EVENT_TYPE = '$workflow.sweep';\n\n/** What journeyLambda reads from its env: `JourneyEnv` without the binding it supplies itself. */\nexport type JourneyLambdaEnv = Omit<JourneyEnv, 'JOURNEY' | 'DB' | 'WORKFLOW_KV' | 'EMAIL'>;\n\nexport interface JourneyLambdaOptions<Env extends JourneyLambdaEnv> extends Omit<\n JourneyHostOptions<Env & JourneyEnv>,\n 'env' | 'store'\n> {\n /** The validated environment (parsed from `process.env` with zod by the app). */\n env: Env;\n /** The Postgres store: AWS needs its execution registry (`postgresJsStore`). */\n store: (env: Env, database: { schema: string }) => JourneyStore | StoreLease;\n /** The Lambda control plane; `@aws-sdk/client-lambda` with the execution role by default. */\n lambda?: LambdaApi;\n /** How long an unconfirmed start is retried (default 12 hours; keep it under the retention). */\n startRetryMs?: number;\n}\n\n/**\n * The Lambda invocation context, as far as the handler's signature needs it — declared here so\n * the package's types do not depend on `@types/aws-lambda`. Lambda passes the full object.\n */\nexport interface LambdaContext {\n functionName: string;\n functionVersion: string;\n invokedFunctionArn: string;\n awsRequestId: string;\n getRemainingTimeInMillis(): number;\n}\n\nexport interface JourneyLambda {\n /** Export as the function's handler. */\n handler: (\n event: DurableExecutionInvocationInput,\n context: LambdaContext\n ) => Promise<DurableExecutionInvocationOutput>;\n}\n\ninterface Invocation {\n lease?: Required<StoreLease>;\n}\n\n/** This function's ARN without its qualifier, from the ARN it was invoked under. */\nfunction unqualified(invokedFunctionArn: string): string {\n return invokedFunctionArn.split(':').slice(0, 7).join(':');\n}\n\nconst json = (body: unknown, status = 200) =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });\n\nexport function journeyLambda<Env extends JourneyLambdaEnv>(\n options: JourneyLambdaOptions<Env>\n): JourneyLambda {\n type HostEnv = Env & JourneyEnv;\n const { env: baseEnv, lambda: lambdaOption, startRetryMs, ...hostOptions } = options;\n const host = createJourneyHost<HostEnv>(\n { ...hostOptions, store: (env, database) => options.store(env, database) },\n // Node 24's fetch cannot reach APNs (HTTP/2 only): see http2-client.ts for when this can go\n { apnsFetch: http2Fetch() }\n );\n const lambda = lambdaOption ?? sdkLambdaApi();\n const ownHashes = new Set(host.bundle().workflows.map((workflow) => workflow.contentHash));\n const scope = new AsyncLocalStorage<Invocation>();\n\n /** The version this code was published as: `$LATEST` while developing. */\n const ownVersion = () => process.env.AWS_LAMBDA_FUNCTION_VERSION ?? '$LATEST';\n /** Numbered versions are ordered: the bundle compiled into a later one goes live by itself. */\n const bundleVersion = (): BundleVersion | undefined => {\n const version = ownVersion();\n return /^\\d+$/.test(version) ? { id: version, createdAt: Number(version) } : undefined;\n };\n\n /** The store and the env (with the instance port) of this invocation, opened on first use. */\n const open = (\n context: DurableContext\n ): { store: JourneyStore; env: HostEnv; executions: ExecutionRegistry } => {\n const invocation = scope.getStore();\n if (invocation === undefined) throw new Error('journeyLambda: outside an invocation');\n const placeholder = { ...baseEnv } as unknown as HostEnv;\n invocation.lease ??= host.lease(placeholder);\n const { store } = invocation.lease;\n const executions = store.executions;\n if (executions === undefined) {\n throw new Error(\n 'journeyLambda needs a store with an execution registry (PostgresJourneyStore)'\n );\n }\n const functionArn = unqualified(context.lambdaContext.invokedFunctionArn);\n const target = async (contentHash: string): Promise<string> => {\n // An execution runs the code its IR was compiled with: this version's own workflows here,\n // anything else on the version whose bundle put it in the routes\n if (ownHashes.has(contentHash)) return `${functionArn}:${ownVersion()}`;\n const live = await store.liveBundleVersion?.();\n if (live !== undefined && /^\\d+$/.test(live.id)) return `${functionArn}:${live.id}`;\n throw new Error(`no published version carries workflow IR ${contentHash}`);\n };\n const instances = new AwsInstances({\n store,\n executions,\n lambda,\n target,\n ...(startRetryMs === undefined ? {} : { startRetryMs }),\n });\n return { store, env: { ...baseEnv, JOURNEY: instances }, executions };\n };\n\n const run = async (input: RunInput, context: DurableContext): Promise<JourneyOutcome> => {\n const { store, env, executions } = open(context);\n const { instanceId, params } = input;\n // Running is the proof the start got through, whatever became of the invoke's response\n await context.step('$workflow:started', async () => {\n await executions.markSubmitted(\n instanceId,\n context.executionContext.durableExecutionArn,\n Date.now()\n );\n });\n return executeJourney(\n {\n instanceId,\n params,\n enteredAtMs: input.enteredAtMs,\n step: new AwsEngineStep(context, store, executions, lambda, params.userId, instanceId),\n },\n host.runParts(env, store, params)\n );\n };\n\n const http = (event: FunctionUrlEvent, context: DurableContext): Promise<FunctionUrlResult> => {\n const serve = async (): Promise<FunctionUrlResult> => {\n const method = event.requestContext.http.method;\n try {\n const { store, env } = open(context);\n const request = toRequest(event);\n // Only the bundle compiled into a version may route: one is never swapped in by hand\n if (new URL(request.url).pathname === '/deploy') {\n return await fromResponse(\n json(\n {\n error:\n 'deploy by publishing a version (POST /bundle/deploy applies its own bundle)',\n },\n 409\n ),\n method\n );\n }\n await host.applyOwnBundle(env, bundleVersion());\n return await fromResponse(await host.handle(request, env, store), method);\n } catch (error) {\n console.error('[workflow] request failed', error);\n return fromResponse(json({ error: 'internal error' }, 500), method);\n }\n };\n // A read (the bundle, a preview, a tracked link or pixel) answers straight from the handler:\n // its body can be large, and a step would checkpoint all of it. A write runs in one step,\n // never retried — the caller is waiting, and retries the request itself\n const method = event.requestContext.http.method.toUpperCase();\n if (method === 'GET' || method === 'HEAD') return serve();\n return context.step('http', serve, { retryStrategy: () => ({ shouldRetry: false }) });\n };\n\n const sweep = (context: DurableContext): Promise<SweepResult> =>\n context.step(\n 'sweep',\n async () => {\n const { store, env } = open(context);\n await host.applyOwnBundle(env, bundleVersion());\n return sweepSegments(env, store);\n },\n { retryStrategy: () => ({ shouldRetry: false }) }\n );\n\n const durable = withDurableExecution(async (input: unknown, context: DurableContext) => {\n if (isFunctionUrlEvent(input)) return http(input, context);\n if (isRunInput(input)) return run(input, context);\n if (\n typeof input === 'object' &&\n input !== null &&\n (input as { type?: unknown }).type === SWEEP_EVENT_TYPE\n ) {\n return sweep(context);\n }\n throw new Error('journeyLambda: unrecognized event (not a Function URL request, run or sweep)');\n });\n\n const handler = async (\n event: DurableExecutionInvocationInput,\n context: LambdaContext\n ): Promise<DurableExecutionInvocationOutput> => {\n const invocation: Invocation = {};\n try {\n // The durable wrapper reads the whole context Lambda passed, not just the fields above\n return await scope.run(invocation, () =>\n durable(event, context as Parameters<typeof durable>[1])\n );\n } finally {\n // Outside the durable wrapper: a suspended run returns PENDING without unwinding the\n // handler, so a `finally` in there would never release the connection\n await invocation.lease?.close().catch((error: unknown) => {\n console.error('[workflow] closing the store failed', error);\n });\n }\n };\n\n return { handler };\n}\n\nexport { AwsEngineStep, stepRetry } from './engine-step';\nexport { AwsInstances, RUN_EVENT_TYPE, executionName, isRunInput } from './instances';\nexport type { AwsInstancesOptions, RunInput } from './instances';\nexport { sdkLambdaApi } from './lambda-api';\nexport type { ExecutionState, LambdaApi } from './lambda-api';\nexport { fromResponse, isFunctionUrlEvent, toRequest } from './function-url';\nexport type { FunctionUrlEvent, FunctionUrlResult } from './function-url';\n"],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_engine_bucket = require("../engine/bucket.cjs");
3
+ const require_cloudflare_bindings = require("../cloudflare/bindings.cjs");
4
+ //#region src/aws/instances.ts
5
+ /**
6
+ * The router's instance port over Lambda durable executions: an instance is one execution of
7
+ * this same function, started by an asynchronous self-invoke under a stable execution name;
8
+ * a wake completes the callback its current wait parks on; stopping stops the execution.
9
+ */
10
+ /** The top-level shape of an internal run: what the self-invoke sends, never an HTTP body. */
11
+ const RUN_EVENT_TYPE = "$workflow.run";
12
+ function isRunInput(event) {
13
+ if (typeof event !== "object" || event === null) return false;
14
+ const candidate = event;
15
+ return candidate.type === "$workflow.run" && candidate.requestContext === void 0 && typeof candidate.instanceId === "string" && typeof candidate.enteredAtMs === "number" && typeof candidate.params === "object" && candidate.params !== null;
16
+ }
17
+ const utf8 = new TextEncoder();
18
+ const NAME = /^[a-zA-Z0-9_-]{1,64}$/;
19
+ /**
20
+ * The durable execution name for an instance: the instance id itself when it fits, otherwise a
21
+ * prefix of it with a fingerprint of the whole — stable, so every retry of the start reuses it.
22
+ */
23
+ function executionName(instanceId) {
24
+ if (NAME.test(instanceId)) return instanceId;
25
+ const safe = instanceId.replace(/[^a-zA-Z0-9_-]/g, "-");
26
+ const fingerprint = `${require_engine_bucket.murmur3(utf8.encode(instanceId)).toString(36)}${require_engine_bucket.murmur3(utf8.encode(instanceId), 2538058380).toString(36)}`;
27
+ return `${safe.slice(0, 63 - fingerprint.length)}-${fingerprint}`;
28
+ }
29
+ const MINUTE = 6e4;
30
+ var AwsInstances = class {
31
+ options;
32
+ now;
33
+ startRetryMs;
34
+ reconcileEveryMs;
35
+ constructor(options) {
36
+ this.options = options;
37
+ this.now = options.now ?? Date.now;
38
+ this.startRetryMs = options.startRetryMs ?? 720 * MINUTE;
39
+ this.reconcileEveryMs = options.reconcileEveryMs ?? 15 * MINUTE;
40
+ }
41
+ async plan(options) {
42
+ const input = {
43
+ type: RUN_EVENT_TYPE,
44
+ instanceId: options.id,
45
+ params: options.params,
46
+ enteredAtMs: this.now()
47
+ };
48
+ return {
49
+ name: executionName(options.id),
50
+ target: await this.options.target(options.params.contentHash),
51
+ input: JSON.stringify(input)
52
+ };
53
+ }
54
+ async create(options) {
55
+ const record = await this.options.executions.get(options.id);
56
+ if (record === void 0) throw new Error(`no start is recorded for ${options.id}: the entry must be planned first`);
57
+ await this.submit(record);
58
+ return this.instance(options.id);
59
+ }
60
+ async get(id) {
61
+ if (await this.options.executions.get(id) === void 0) throw new require_cloudflare_bindings.InstanceNotFoundError(id);
62
+ return this.instance(id);
63
+ }
64
+ async resume(entry) {
65
+ for (const record of await this.options.executions.pendingStarts({
66
+ ...entry,
67
+ limit: 1
68
+ })) if (!await this.giveUpIfStale(record)) await this.submit(record);
69
+ }
70
+ async maintain() {
71
+ const now = this.now();
72
+ const failures = [];
73
+ for (const record of await this.options.executions.pendingStarts({
74
+ createdBefore: now - MINUTE,
75
+ limit: 25
76
+ })) try {
77
+ if (!await this.giveUpIfStale(record)) await this.submit(record);
78
+ } catch (error) {
79
+ failures.push(error);
80
+ }
81
+ for (const record of await this.options.executions.toReconcile(25, now - this.reconcileEveryMs)) try {
82
+ await this.reconcile(record, now);
83
+ } catch (error) {
84
+ failures.push(error);
85
+ }
86
+ if (failures.length > 0) throw failures[0];
87
+ }
88
+ async submit(record) {
89
+ const { arn } = await this.options.lambda.startExecution({
90
+ target: record.target,
91
+ name: record.name,
92
+ payload: record.input
93
+ });
94
+ await this.options.executions.markSubmitted(record.instanceId, arn, this.now());
95
+ }
96
+ /** Past the retry budget a start is unresolved for good: no retry could tell a duplicate apart. */
97
+ async giveUpIfStale(record) {
98
+ if (this.now() - record.createdAt <= this.startRetryMs) return false;
99
+ await this.options.executions.markUnresolved(record.instanceId, this.now());
100
+ console.error(`[workflow] the start of ${record.instanceId} (${record.name}) was never confirmed: left unresolved`);
101
+ return true;
102
+ }
103
+ async arnOf(record) {
104
+ if (record.arn !== void 0) return record.arn;
105
+ const arn = await this.options.lambda.findExecution(record.target, record.name);
106
+ if (arn !== void 0) await this.options.executions.setArn(record.instanceId, arn);
107
+ return arn;
108
+ }
109
+ /**
110
+ * Past the start retry budget, an execution still nowhere to be seen never started: any queued
111
+ * invoke has expired by then. Before that, not finding it says nothing.
112
+ */
113
+ vanished(record) {
114
+ return this.now() - record.createdAt > this.startRetryMs;
115
+ }
116
+ async reconcile(record, now) {
117
+ const arn = await this.arnOf(record);
118
+ const state = arn === void 0 ? void 0 : await this.options.lambda.executionState(arn);
119
+ if (state === "failed" || state === "stopped" || state === "timed_out" || state === void 0 && this.vanished(record)) {
120
+ const { store } = this.options;
121
+ await store.setEntryStatus(record.instanceId, "failed");
122
+ await store.reporting?.instanceFinished({
123
+ workflow: record.workflow,
124
+ instanceId: record.instanceId,
125
+ userId: record.userId,
126
+ status: "failed",
127
+ at: now
128
+ });
129
+ console.warn(`[workflow] ${record.instanceId} ended on the platform (${state ?? "unknown"}) without finishing: recorded as failed`);
130
+ }
131
+ await this.options.executions.markChecked(record.instanceId, now);
132
+ }
133
+ instance(id) {
134
+ const { executions, lambda } = this.options;
135
+ const current = async () => {
136
+ const record = await executions.get(id);
137
+ if (record === void 0) throw new require_cloudflare_bindings.InstanceNotFoundError(id);
138
+ return record;
139
+ };
140
+ return {
141
+ id,
142
+ sendEvent: async (event) => {
143
+ const { callbackId } = await current();
144
+ if (callbackId === void 0) return;
145
+ await lambda.completeCallback(callbackId, JSON.stringify(event.payload ?? {}));
146
+ },
147
+ terminate: async () => {
148
+ const record = await current();
149
+ if (record.submittedAt === void 0 && record.unresolvedAt === void 0) await executions.markUnresolved(id, this.now());
150
+ const arn = await this.arnOf(record);
151
+ if (arn === void 0) {
152
+ if (this.vanished(record)) return;
153
+ throw new Error(`cannot stop ${id} yet: its execution is not visible`);
154
+ }
155
+ if (!await lambda.stopExecution(arn)) throw new Error(`stopping ${id} failed`);
156
+ },
157
+ status: async () => {
158
+ const record = await current();
159
+ const arn = await this.arnOf(record);
160
+ if (arn === void 0) return { status: this.vanished(record) ? "terminated" : "running" };
161
+ const state = await lambda.executionState(arn);
162
+ if (state === "succeeded") return { status: "complete" };
163
+ if (state === "failed") return { status: "errored" };
164
+ if (state === "running") return { status: "running" };
165
+ return { status: "terminated" };
166
+ }
167
+ };
168
+ }
169
+ };
170
+ //#endregion
171
+ exports.AwsInstances = AwsInstances;
172
+ exports.RUN_EVENT_TYPE = RUN_EVENT_TYPE;
173
+ exports.executionName = executionName;
174
+ exports.isRunInput = isRunInput;
175
+
176
+ //# sourceMappingURL=instances.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instances.cjs","names":["murmur3","InstanceNotFoundError"],"sources":["../../src/aws/instances.ts"],"sourcesContent":["import {\n InstanceNotFoundError,\n type JourneyParams,\n type WorkflowBindingLike,\n type WorkflowInstanceLike,\n} from '../cloudflare/bindings';\nimport { murmur3 } from '../engine/bucket';\nimport type {\n ExecutionRecord,\n ExecutionRegistry,\n ExecutionStart,\n JourneyStore,\n} from '../store/index';\nimport type { LambdaApi } from './lambda-api';\n\n/**\n * The router's instance port over Lambda durable executions: an instance is one execution of\n * this same function, started by an asynchronous self-invoke under a stable execution name;\n * a wake completes the callback its current wait parks on; stopping stops the execution.\n */\n\n/** The top-level shape of an internal run: what the self-invoke sends, never an HTTP body. */\nexport const RUN_EVENT_TYPE = '$workflow.run';\n\nexport interface RunInput {\n type: typeof RUN_EVENT_TYPE;\n instanceId: string;\n params: JourneyParams;\n /** When the instance was created (as Cloudflare's event timestamp): its entry anchor, fixed with the start. */\n enteredAtMs: number;\n}\n\nexport function isRunInput(event: unknown): event is RunInput {\n if (typeof event !== 'object' || event === null) return false;\n const candidate = event as {\n type?: unknown;\n instanceId?: unknown;\n enteredAtMs?: unknown;\n params?: unknown;\n requestContext?: unknown;\n };\n return (\n candidate.type === RUN_EVENT_TYPE &&\n candidate.requestContext === undefined &&\n typeof candidate.instanceId === 'string' &&\n typeof candidate.enteredAtMs === 'number' &&\n typeof candidate.params === 'object' &&\n candidate.params !== null\n );\n}\n\nconst utf8 = new TextEncoder();\nconst NAME = /^[a-zA-Z0-9_-]{1,64}$/;\n\n/**\n * The durable execution name for an instance: the instance id itself when it fits, otherwise a\n * prefix of it with a fingerprint of the whole — stable, so every retry of the start reuses it.\n */\nexport function executionName(instanceId: string): string {\n if (NAME.test(instanceId)) return instanceId;\n const safe = instanceId.replace(/[^a-zA-Z0-9_-]/g, '-');\n const fingerprint = `${murmur3(utf8.encode(instanceId)).toString(36)}${murmur3(utf8.encode(instanceId), 0x9747b28c).toString(36)}`;\n return `${safe.slice(0, 63 - fingerprint.length)}-${fingerprint}`;\n}\n\nexport interface AwsInstancesOptions {\n store: JourneyStore;\n executions: ExecutionRegistry;\n lambda: LambdaApi;\n /** The qualified function ARN an execution pinned to `contentHash` must run on. */\n target(contentHash: string): Promise<string>;\n /**\n * How long an unconfirmed start keeps being retried. It must stay shorter than the durable\n * execution retention: past that the platform forgets the name, and a retry would start a\n * second execution. Twelve hours by default.\n */\n startRetryMs?: number;\n /** How stale a running instance's last check must be before maintenance looks again. */\n reconcileEveryMs?: number;\n now?: () => number;\n}\n\nconst MINUTE = 60_000;\n\nexport class AwsInstances implements WorkflowBindingLike {\n private readonly now: () => number;\n private readonly startRetryMs: number;\n private readonly reconcileEveryMs: number;\n\n constructor(private readonly options: AwsInstancesOptions) {\n this.now = options.now ?? Date.now;\n this.startRetryMs = options.startRetryMs ?? 12 * 60 * MINUTE;\n this.reconcileEveryMs = options.reconcileEveryMs ?? 15 * MINUTE;\n }\n\n async plan(options: { id: string; params: JourneyParams; ts: number }): Promise<ExecutionStart> {\n const input: RunInput = {\n type: RUN_EVENT_TYPE,\n instanceId: options.id,\n params: options.params,\n // The moment the instance is created, like Cloudflare's: an event's own ts may be long past\n enteredAtMs: this.now(),\n };\n return {\n name: executionName(options.id),\n target: await this.options.target(options.params.contentHash),\n input: JSON.stringify(input),\n };\n }\n\n async create(options: { id: string; params: unknown }): Promise<WorkflowInstanceLike> {\n const record = await this.options.executions.get(options.id);\n if (record === undefined) {\n throw new Error(`no start is recorded for ${options.id}: the entry must be planned first`);\n }\n await this.submit(record);\n return this.instance(options.id);\n }\n\n async get(id: string): Promise<WorkflowInstanceLike> {\n // A read of the registry alone: a Lambda failure must never read as \"no such instance\"\n if ((await this.options.executions.get(id)) === undefined) {\n throw new InstanceNotFoundError(id);\n }\n return this.instance(id);\n }\n\n async resume(entry: { workflow: string; userId: string }): Promise<void> {\n for (const record of await this.options.executions.pendingStarts({ ...entry, limit: 1 })) {\n if (!(await this.giveUpIfStale(record))) await this.submit(record);\n }\n }\n\n async maintain(): Promise<void> {\n const now = this.now();\n const failures: unknown[] = [];\n // Starts whose submit never confirmed: retried with the recorded name and payload\n for (const record of await this.options.executions.pendingStarts({\n createdBefore: now - MINUTE,\n limit: 25,\n })) {\n try {\n if (!(await this.giveUpIfStale(record))) await this.submit(record);\n } catch (error) {\n failures.push(error);\n }\n }\n // Executions the platform ended without the run finalizing (a timeout, a stop, a crash)\n for (const record of await this.options.executions.toReconcile(\n 25,\n now - this.reconcileEveryMs\n )) {\n try {\n await this.reconcile(record, now);\n } catch (error) {\n failures.push(error);\n }\n }\n if (failures.length > 0) throw failures[0];\n }\n\n private async submit(record: ExecutionRecord): Promise<void> {\n const { arn } = await this.options.lambda.startExecution({\n target: record.target,\n name: record.name,\n payload: record.input,\n });\n await this.options.executions.markSubmitted(record.instanceId, arn, this.now());\n }\n\n /** Past the retry budget a start is unresolved for good: no retry could tell a duplicate apart. */\n private async giveUpIfStale(record: ExecutionRecord): Promise<boolean> {\n if (this.now() - record.createdAt <= this.startRetryMs) return false;\n await this.options.executions.markUnresolved(record.instanceId, this.now());\n console.error(\n `[workflow] the start of ${record.instanceId} (${record.name}) was never confirmed: left unresolved`\n );\n return true;\n }\n\n private async arnOf(record: ExecutionRecord): Promise<string | undefined> {\n if (record.arn !== undefined) return record.arn;\n const arn = await this.options.lambda.findExecution(record.target, record.name);\n if (arn !== undefined) await this.options.executions.setArn(record.instanceId, arn);\n return arn;\n }\n\n /**\n * Past the start retry budget, an execution still nowhere to be seen never started: any queued\n * invoke has expired by then. Before that, not finding it says nothing.\n */\n private vanished(record: ExecutionRecord): boolean {\n return this.now() - record.createdAt > this.startRetryMs;\n }\n\n private async reconcile(record: ExecutionRecord, now: number): Promise<void> {\n const arn = await this.arnOf(record);\n const state = arn === undefined ? undefined : await this.options.lambda.executionState(arn);\n // Only a state that will never run another step — or one missing for good — ends the entry;\n // a run still queued, or not visible yet, is left alone\n const ended =\n state === 'failed' ||\n state === 'stopped' ||\n state === 'timed_out' ||\n (state === undefined && this.vanished(record));\n if (ended) {\n // Ended without its finalize step: the ledger must not show it running forever\n const { store } = this.options;\n await store.setEntryStatus(record.instanceId, 'failed');\n await store.reporting?.instanceFinished({\n workflow: record.workflow,\n instanceId: record.instanceId,\n userId: record.userId,\n status: 'failed',\n at: now,\n });\n console.warn(\n `[workflow] ${record.instanceId} ended on the platform (${state ?? 'unknown'}) without finishing: recorded as failed`\n );\n }\n await this.options.executions.markChecked(record.instanceId, now);\n }\n\n private instance(id: string): WorkflowInstanceLike {\n const { executions, lambda } = this.options;\n const current = async () => {\n const record = await executions.get(id);\n if (record === undefined) throw new InstanceNotFoundError(id);\n return record;\n };\n return {\n id,\n sendEvent: async (event) => {\n // The wait parked now, read at the moment of the wake: a newer wait's callback replaces\n // an older one, and a wake for none (the instance is between waits) is nothing to do —\n // its next wait re-reads the facts before parking\n const { callbackId } = await current();\n if (callbackId === undefined) return;\n await lambda.completeCallback(callbackId, JSON.stringify(event.payload ?? {}));\n },\n terminate: async () => {\n const record = await current();\n if (record.submittedAt === undefined && record.unresolvedAt === undefined) {\n // No retry may start it after this\n await executions.markUnresolved(id, this.now());\n }\n const arn = await this.arnOf(record);\n if (arn === undefined) {\n // Not visible yet is not stopped: the erasure stays pending, and the sweep retries\n if (this.vanished(record)) return;\n throw new Error(`cannot stop ${id} yet: its execution is not visible`);\n }\n if (!(await lambda.stopExecution(arn))) throw new Error(`stopping ${id} failed`);\n },\n status: async () => {\n const record = await current();\n const arn = await this.arnOf(record);\n // Not visible yet may still run: only one missing past the budget is gone\n if (arn === undefined) return { status: this.vanished(record) ? 'terminated' : 'running' };\n const state = await lambda.executionState(arn);\n // In the router's vocabulary (Cloudflare's): what will never run another step\n if (state === 'succeeded') return { status: 'complete' };\n if (state === 'failed') return { status: 'errored' };\n if (state === 'running') return { status: 'running' };\n return { status: 'terminated' };\n },\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ import { JourneyParams, WorkflowBindingLike, WorkflowInstanceLike } from "../cloudflare/bindings.cjs";
2
+ import { ExecutionRegistry, ExecutionStart, JourneyStore } from "../store/index.cjs";
3
+ import { LambdaApi } from "./lambda-api.cjs";
4
+ //#region src/aws/instances.d.ts
5
+ /**
6
+ * The router's instance port over Lambda durable executions: an instance is one execution of
7
+ * this same function, started by an asynchronous self-invoke under a stable execution name;
8
+ * a wake completes the callback its current wait parks on; stopping stops the execution.
9
+ */
10
+ /** The top-level shape of an internal run: what the self-invoke sends, never an HTTP body. */
11
+ export declare const RUN_EVENT_TYPE = "$workflow.run";
12
+ export interface RunInput {
13
+ type: typeof RUN_EVENT_TYPE;
14
+ instanceId: string;
15
+ params: JourneyParams;
16
+ /** When the instance was created (as Cloudflare's event timestamp): its entry anchor, fixed with the start. */
17
+ enteredAtMs: number;
18
+ }
19
+ export declare function isRunInput(event: unknown): event is RunInput;
20
+ /**
21
+ * The durable execution name for an instance: the instance id itself when it fits, otherwise a
22
+ * prefix of it with a fingerprint of the whole — stable, so every retry of the start reuses it.
23
+ */
24
+ export declare function executionName(instanceId: string): string;
25
+ export interface AwsInstancesOptions {
26
+ store: JourneyStore;
27
+ executions: ExecutionRegistry;
28
+ lambda: LambdaApi;
29
+ /** The qualified function ARN an execution pinned to `contentHash` must run on. */
30
+ target(contentHash: string): Promise<string>;
31
+ /**
32
+ * How long an unconfirmed start keeps being retried. It must stay shorter than the durable
33
+ * execution retention: past that the platform forgets the name, and a retry would start a
34
+ * second execution. Twelve hours by default.
35
+ */
36
+ startRetryMs?: number;
37
+ /** How stale a running instance's last check must be before maintenance looks again. */
38
+ reconcileEveryMs?: number;
39
+ now?: () => number;
40
+ }
41
+ export declare class AwsInstances implements WorkflowBindingLike {
42
+ private readonly options;
43
+ private readonly now;
44
+ private readonly startRetryMs;
45
+ private readonly reconcileEveryMs;
46
+ constructor(options: AwsInstancesOptions);
47
+ plan(options: {
48
+ id: string;
49
+ params: JourneyParams;
50
+ ts: number;
51
+ }): Promise<ExecutionStart>;
52
+ create(options: {
53
+ id: string;
54
+ params: unknown;
55
+ }): Promise<WorkflowInstanceLike>;
56
+ get(id: string): Promise<WorkflowInstanceLike>;
57
+ resume(entry: {
58
+ workflow: string;
59
+ userId: string;
60
+ }): Promise<void>;
61
+ maintain(): Promise<void>;
62
+ private submit;
63
+ /** Past the retry budget a start is unresolved for good: no retry could tell a duplicate apart. */
64
+ private giveUpIfStale;
65
+ private arnOf;
66
+ /**
67
+ * Past the start retry budget, an execution still nowhere to be seen never started: any queued
68
+ * invoke has expired by then. Before that, not finding it says nothing.
69
+ */
70
+ private vanished;
71
+ private reconcile;
72
+ private instance;
73
+ }
74
+ //#endregion
75
+ //# sourceMappingURL=instances.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instances.d.cts","names":[],"sources":["../../src/aws/instances.ts"],"mappings":";;;;;;;;;;qBAsBa;iBAEI;EACf,aAAa;EACb;EACA,QAAQ;;EAER;;wBAGc,WAAW,iBAAiB,SAAS;;;;;wBA0BrC,cAAc;iBAOb;EACf,OAAO;EACP,YAAY;EACZ,QAAQ;;EAER,OAAO,sBAAsB;;;;;;EAM7B;;EAEA;EACA;;qBAKW,wBAAwB;mBAKN;mBAJZ;mBACA;mBACA;EAEjB,YAA6B,SAAS;EAMhC,KAAK;IAAW;IAAY,QAAQ;IAAe;MAAe,QAAQ;EAe1E,OAAO;IAAW;IAAY;MAAoB,QAAQ;EAS1D,IAAI,aAAa,QAAQ;EAQzB,OAAO;IAAS;IAAkB;MAAmB;EAMrD,YAAY;UA4BJ;;UAUA;UASA;;;;;UAWN;UAIM;UA4BN"}
@@ -0,0 +1,75 @@
1
+ import { JourneyParams, WorkflowBindingLike, WorkflowInstanceLike } from "../cloudflare/bindings.mjs";
2
+ import { ExecutionRegistry, ExecutionStart, JourneyStore } from "../store/index.mjs";
3
+ import { LambdaApi } from "./lambda-api.mjs";
4
+ //#region src/aws/instances.d.ts
5
+ /**
6
+ * The router's instance port over Lambda durable executions: an instance is one execution of
7
+ * this same function, started by an asynchronous self-invoke under a stable execution name;
8
+ * a wake completes the callback its current wait parks on; stopping stops the execution.
9
+ */
10
+ /** The top-level shape of an internal run: what the self-invoke sends, never an HTTP body. */
11
+ export declare const RUN_EVENT_TYPE = "$workflow.run";
12
+ export interface RunInput {
13
+ type: typeof RUN_EVENT_TYPE;
14
+ instanceId: string;
15
+ params: JourneyParams;
16
+ /** When the instance was created (as Cloudflare's event timestamp): its entry anchor, fixed with the start. */
17
+ enteredAtMs: number;
18
+ }
19
+ export declare function isRunInput(event: unknown): event is RunInput;
20
+ /**
21
+ * The durable execution name for an instance: the instance id itself when it fits, otherwise a
22
+ * prefix of it with a fingerprint of the whole — stable, so every retry of the start reuses it.
23
+ */
24
+ export declare function executionName(instanceId: string): string;
25
+ export interface AwsInstancesOptions {
26
+ store: JourneyStore;
27
+ executions: ExecutionRegistry;
28
+ lambda: LambdaApi;
29
+ /** The qualified function ARN an execution pinned to `contentHash` must run on. */
30
+ target(contentHash: string): Promise<string>;
31
+ /**
32
+ * How long an unconfirmed start keeps being retried. It must stay shorter than the durable
33
+ * execution retention: past that the platform forgets the name, and a retry would start a
34
+ * second execution. Twelve hours by default.
35
+ */
36
+ startRetryMs?: number;
37
+ /** How stale a running instance's last check must be before maintenance looks again. */
38
+ reconcileEveryMs?: number;
39
+ now?: () => number;
40
+ }
41
+ export declare class AwsInstances implements WorkflowBindingLike {
42
+ private readonly options;
43
+ private readonly now;
44
+ private readonly startRetryMs;
45
+ private readonly reconcileEveryMs;
46
+ constructor(options: AwsInstancesOptions);
47
+ plan(options: {
48
+ id: string;
49
+ params: JourneyParams;
50
+ ts: number;
51
+ }): Promise<ExecutionStart>;
52
+ create(options: {
53
+ id: string;
54
+ params: unknown;
55
+ }): Promise<WorkflowInstanceLike>;
56
+ get(id: string): Promise<WorkflowInstanceLike>;
57
+ resume(entry: {
58
+ workflow: string;
59
+ userId: string;
60
+ }): Promise<void>;
61
+ maintain(): Promise<void>;
62
+ private submit;
63
+ /** Past the retry budget a start is unresolved for good: no retry could tell a duplicate apart. */
64
+ private giveUpIfStale;
65
+ private arnOf;
66
+ /**
67
+ * Past the start retry budget, an execution still nowhere to be seen never started: any queued
68
+ * invoke has expired by then. Before that, not finding it says nothing.
69
+ */
70
+ private vanished;
71
+ private reconcile;
72
+ private instance;
73
+ }
74
+ //#endregion
75
+ //# sourceMappingURL=instances.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instances.d.mts","names":[],"sources":["../../src/aws/instances.ts"],"mappings":";;;;;;;;;;qBAsBa;iBAEI;EACf,aAAa;EACb;EACA,QAAQ;;EAER;;wBAGc,WAAW,iBAAiB,SAAS;;;;;wBA0BrC,cAAc;iBAOb;EACf,OAAO;EACP,YAAY;EACZ,QAAQ;;EAER,OAAO,sBAAsB;;;;;;EAM7B;;EAEA;EACA;;qBAKW,wBAAwB;mBAKN;mBAJZ;mBACA;mBACA;EAEjB,YAA6B,SAAS;EAMhC,KAAK;IAAW;IAAY,QAAQ;IAAe;MAAe,QAAQ;EAe1E,OAAO;IAAW;IAAY;MAAoB,QAAQ;EAS1D,IAAI,aAAa,QAAQ;EAQzB,OAAO;IAAS;IAAkB;MAAmB;EAMrD,YAAY;UA4BJ;;UAUA;UASA;;;;;UAWN;UAIM;UA4BN"}