@dicabrio/durable-sdk 0.1.0 → 0.2.1

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.
package/README.md CHANGED
@@ -54,8 +54,10 @@ import { onboarding } from "./onboarding.js";
54
54
  const signingKey = process.env.DURABLE_APP_KEY!;
55
55
 
56
56
  const app = express();
57
- // The service POSTs invocations here; serve() verifies the HMAC over the raw body.
58
- app.post("/api/durable", serve([onboarding], { signingKey }));
57
+ // serve() answers POST (invocations) and GET (the function manifest, for pull
58
+ // registration); mount with app.all so both reach it. It verifies the HMAC
59
+ // over the raw body itself — don't add a JSON body parser on this route.
60
+ app.all("/api/durable", serve([onboarding], { signingKey }));
59
61
  app.listen(4000);
60
62
 
61
63
  const client = new DurableClient({
@@ -70,6 +72,23 @@ await client.sync([onboarding]);
70
72
  await client.send({ name: "user.created", data: { id: "alice" } });
71
73
  ```
72
74
 
75
+ ## Registration: push or pull
76
+
77
+ `serve()` also answers a signed `GET` with your function manifest, so the
78
+ service can **pull** your registration instead of you pushing it. You give
79
+ Durable the app URL once — `provision()` creates the workspace and sends the
80
+ client's `appUrl` in one call:
81
+
82
+ ```ts
83
+ // Idempotent by (name, environment); returns this app's id + signing key.
84
+ const { id, key } = await client.provision({ name: "billing", environment: "prod" });
85
+ ```
86
+
87
+ The service then fetches the manifest from your app on boot, when the URL is
88
+ set or changed, nightly, and from the dashboard's Refresh button. `sync()`
89
+ (push) stays fully supported — both paths produce identical registration, so
90
+ you can use whichever fits (push from CI, pull for hands-off dev).
91
+
73
92
  ## Step primitives
74
93
 
75
94
  | Primitive | What it does |
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DurableClient: () => DurableClient,
24
+ SDK_VERSION: () => SDK_VERSION,
24
25
  StepInterrupt: () => StepInterrupt,
25
26
  createFunction: () => createFunction,
26
27
  createStep: () => createStep,
@@ -123,10 +124,26 @@ async function runInvocation(fn, req) {
123
124
  return { op: "error", id: null, message: message2, retryable: true };
124
125
  }
125
126
  }
127
+ function toWindow(w) {
128
+ return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : void 0;
129
+ }
130
+ function buildManifest(functions) {
131
+ return functions.map((f) => ({
132
+ id: f.id,
133
+ trigger: f.trigger,
134
+ concurrency: f.concurrency,
135
+ priority: f.priority,
136
+ throttle: toWindow(f.throttle),
137
+ rateLimit: toWindow(f.rateLimit),
138
+ debounce: f.debounce ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key } : void 0,
139
+ batch: f.batch ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key } : void 0
140
+ }));
141
+ }
126
142
 
127
143
  // ../shared/src/protocol.ts
128
144
  var SIGNATURE_HEADER = "x-durable-signature";
129
145
  var APP_HEADER = "x-durable-app";
146
+ var TIMESTAMP_HEADER = "x-durable-timestamp";
130
147
 
131
148
  // ../shared/src/hmac.ts
132
149
  var import_node_crypto = require("crypto");
@@ -141,6 +158,9 @@ function verify(body, signature, key) {
141
158
  return (0, import_node_crypto.timingSafeEqual)(a, b);
142
159
  }
143
160
 
161
+ // src/version.ts
162
+ var SDK_VERSION = true ? "0.2.1" : "dev";
163
+
144
164
  // src/serve.ts
145
165
  function readBody(req) {
146
166
  return new Promise((resolve, reject) => {
@@ -153,6 +173,24 @@ function readBody(req) {
153
173
  function serve(functions, opts) {
154
174
  return async (req, res) => {
155
175
  try {
176
+ if (req.method === "GET") {
177
+ const ts = req.headers[TIMESTAMP_HEADER];
178
+ const sig2 = req.headers[SIGNATURE_HEADER];
179
+ const fresh = typeof ts === "string" && Math.abs(Date.now() - Number(ts)) <= 5 * 60 * 1e3;
180
+ if (!fresh || typeof sig2 !== "string" || !verify(ts, sig2, opts.signingKey)) {
181
+ res.writeHead(401).end("invalid signature");
182
+ return;
183
+ }
184
+ const out2 = JSON.stringify({
185
+ functions: buildManifest(functions),
186
+ sdkVersion: SDK_VERSION
187
+ });
188
+ res.writeHead(200, {
189
+ "content-type": "application/json",
190
+ [SIGNATURE_HEADER]: sign(out2, opts.signingKey)
191
+ }).end(out2);
192
+ return;
193
+ }
156
194
  const raw = await readBody(req);
157
195
  const sig = req.headers[SIGNATURE_HEADER];
158
196
  if (typeof sig !== "string" || !verify(raw, sig, opts.signingKey)) {
@@ -179,9 +217,6 @@ function serve(functions, opts) {
179
217
  }
180
218
 
181
219
  // src/client.ts
182
- function toWindow(w) {
183
- return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : void 0;
184
- }
185
220
  var DurableClient = class {
186
221
  constructor(opts) {
187
222
  this.opts = opts;
@@ -208,16 +243,8 @@ var DurableClient = class {
208
243
  async sync(functions) {
209
244
  const payload = {
210
245
  url: this.opts.appUrl,
211
- functions: functions.map((f) => ({
212
- id: f.id,
213
- trigger: f.trigger,
214
- concurrency: f.concurrency,
215
- priority: f.priority,
216
- throttle: toWindow(f.throttle),
217
- rateLimit: toWindow(f.rateLimit),
218
- debounce: f.debounce ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key } : void 0,
219
- batch: f.batch ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key } : void 0
220
- }))
246
+ sdkVersion: SDK_VERSION,
247
+ functions: buildManifest(functions)
221
248
  };
222
249
  await this.post("/fn/sync", payload);
223
250
  }
@@ -225,10 +252,25 @@ var DurableClient = class {
225
252
  async send(event) {
226
253
  await this.post("/e", { name: event.name, data: event.data ?? {} });
227
254
  }
255
+ /** Create (or reuse, idempotent by name+env) this app's workspace, sending its appUrl. */
256
+ async provision(opts) {
257
+ const res = await fetch(this.opts.baseUrl + "/api/apps", {
258
+ method: "POST",
259
+ headers: { "content-type": "application/json" },
260
+ body: JSON.stringify({
261
+ name: opts.name,
262
+ environment: opts.environment,
263
+ appUrl: this.opts.appUrl
264
+ })
265
+ });
266
+ if (!res.ok) throw new Error(`provision failed: ${res.status} ${await res.text()}`);
267
+ return await res.json();
268
+ }
228
269
  };
229
270
  // Annotate the CommonJS export names for ESM import in node:
230
271
  0 && (module.exports = {
231
272
  DurableClient,
273
+ SDK_VERSION,
232
274
  StepInterrupt,
233
275
  createFunction,
234
276
  createStep,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/step.ts","../src/function.ts","../../shared/src/protocol.ts","../../shared/src/hmac.ts","../src/serve.ts","../src/client.ts"],"sourcesContent":["export { createFunction, runInvocation } from \"./function.js\";\nexport type { DurableFunction, FunctionContext } from \"./function.js\";\nexport { createStep, parseDuration, StepInterrupt } from \"./step.js\";\nexport type { StepTools, WaitForEventOptions } from \"./step.js\";\nexport { serve } from \"./serve.js\";\nexport { DurableClient } from \"./client.js\";\nexport type { DurableClientOptions } from \"./client.js\";\n","import type { DurableEvent, InvokeResponse, MemoizedStep } from \"@durable/shared\";\n\n/**\n * Thrown to unwind the handler the moment it reaches a new operation. The\n * carried response is exactly what the SDK reports to the service. Using an\n * exception means at most one *new* step executes per invocation — everything\n * after it is skipped until the service re-invokes with that step memoized.\n */\nexport class StepInterrupt extends Error {\n constructor(public readonly response: InvokeResponse) {\n super(\"durable:step-interrupt\");\n }\n}\n\nexport interface WaitForEventOptions {\n /** The event name to wait for. */\n event: string;\n /** Key/value pairs the incoming event.data must contain to match. */\n match?: Record<string, unknown>;\n /** How long to wait before giving up (e.g. \"7d\", \"1h\", or ms). */\n timeout: string | number;\n}\n\nexport interface StepTools {\n /** Run a side-effecting step once; its result is memoized and replayed. */\n run<T>(id: string, cb: () => Promise<T> | T): Promise<T>;\n /** Pause the run until the duration elapses (e.g. \"10s\", \"2d\", or ms). */\n sleep(id: string, duration: string | number): Promise<void>;\n /**\n * Pause the run until a matching event arrives, or the timeout elapses.\n * Resolves with the matched event, or `null` if it timed out.\n */\n waitForEvent(id: string, opts: WaitForEventOptions): Promise<DurableEvent | null>;\n}\n\nconst UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nexport function parseDuration(d: string | number): number {\n if (typeof d === \"number\") return d;\n const m = /^(\\d+)\\s*(ms|s|m|h|d)$/.exec(d.trim());\n if (!m) throw new Error(`invalid duration: ${d}`);\n return Number(m[1]) * UNITS[m[2]];\n}\n\nfunction message(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Build the step tools bound to the steps already memoized for this run. */\nexport function createStep(memoized: Record<string, MemoizedStep>): StepTools {\n return {\n async run<T>(id: string, cb: () => Promise<T> | T): Promise<T> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"run\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return memo.data as T; // replay: free, no side effect\n }\n let data: T;\n try {\n data = await cb();\n } catch (err) {\n // A genuine failure inside the step: let the service decide on retry.\n throw new StepInterrupt({\n op: \"error\",\n id,\n message: message(err),\n retryable: true,\n });\n }\n throw new StepInterrupt({ op: \"step\", id, data });\n },\n\n async sleep(id: string, duration: string | number): Promise<void> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"sleep\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return; // sleep already satisfied\n }\n const until = new Date(Date.now() + parseDuration(duration)).toISOString();\n throw new StepInterrupt({ op: \"sleep\", id, until });\n },\n\n async waitForEvent(id, opts): Promise<DurableEvent | null> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"wait\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return (memo.data as DurableEvent | null) ?? null; // event, or null on timeout\n }\n const until = new Date(Date.now() + parseDuration(opts.timeout)).toISOString();\n throw new StepInterrupt({\n op: \"wait\",\n id,\n event: opts.event,\n match: opts.match ?? null,\n until,\n });\n },\n };\n}\n","import type {\n Concurrency,\n DurableEvent,\n InvokeRequest,\n InvokeResponse,\n Trigger,\n} from \"@durable/shared\";\nimport { createStep, StepInterrupt, type StepTools } from \"./step.js\";\n\nexport interface FunctionContext {\n event: DurableEvent;\n step: StepTools;\n}\n\nexport interface DurableFunction {\n id: string;\n /** Triggered by an event name (`{ event }`) or a cron expression (`{ cron }`). */\n trigger: Trigger;\n /** Optional cap on how many runs execute at once (per key, if given). */\n concurrency?: Concurrency;\n /** Higher runs sooner when jobs compete in the queue (default 0). */\n priority?: number;\n /** Spread run starts over time: max `limit` per `period` (excess delayed). */\n throttle?: { limit: number; period: string | number; key?: string };\n /** Cap new runs: max `limit` per `period` (excess dropped). */\n rateLimit?: { limit: number; period: string | number; key?: string };\n /** Collapse rapid events: one run with the LAST event, once quiet for `period`. */\n debounce?: { period: string | number; key?: string };\n /**\n * Group events: ONE run receives event.data = the whole list, flushed at\n * `maxSize` events or `timeout` after the first (event.name = \"$batch\").\n */\n batch?: { maxSize: number; timeout: string | number; key?: string };\n handler: (ctx: FunctionContext) => Promise<unknown>;\n}\n\nexport function createFunction(def: DurableFunction): DurableFunction {\n return def;\n}\n\n/**\n * Replay a single invocation: run the handler from the top, returning the next\n * operation it reaches (a new step, a sleep, completion, or an error).\n */\nexport async function runInvocation(\n fn: DurableFunction,\n req: InvokeRequest,\n): Promise<InvokeResponse> {\n const step = createStep(req.steps);\n try {\n const data = await fn.handler({ event: req.event, step });\n return { op: \"done\", data };\n } catch (err) {\n if (err instanceof StepInterrupt) return err.response;\n // Error thrown outside any step.run (in the handler body itself).\n const message = err instanceof Error ? err.message : String(err);\n return { op: \"error\", id: null, message, retryable: true };\n }\n}\n","/**\n * Wire protocol between the Durable service and the app SDK.\n *\n * One HTTP round-trip advances a run by at most one *new* step. The service\n * sends the run state (event + already-memoized steps); the SDK replays the\n * cached steps for free, executes the next new operation, and reports it back.\n */\n\n/** An event that triggered (or can trigger) a run. */\nexport interface DurableEvent {\n id: string;\n name: string;\n data: unknown;\n}\n\n/** A previously-completed step, keyed by its user-supplied step id. */\nexport interface MemoizedStep {\n /**\n * \"run\" = a step.run result,\n * \"sleep\" = a satisfied step.sleep marker,\n * \"wait\" = a settled step.waitForEvent (data = the event, or null on timeout).\n */\n type: \"run\" | \"sleep\" | \"wait\";\n /** Output of step.run, or the matched event for \"wait\". Null otherwise. */\n data: unknown;\n}\n\n/** Service -> SDK: \"advance this run\". */\nexport interface InvokeRequest {\n runId: string;\n functionId: string;\n event: DurableEvent;\n /** Completed steps so far, keyed by step id. */\n steps: Record<string, MemoizedStep>;\n}\n\n/**\n * SDK -> service: the next operation the function reached.\n *\n * - step : a new step.run executed; persist `data` under `id`, then re-invoke.\n * - sleep : function wants to pause until `until`; arm a timer.\n * - done : function returned; the run is complete with `data`.\n * - error : the current step threw; retry (if retryable) or fail the run.\n */\nexport type InvokeResponse =\n | { op: \"step\"; id: string; data: unknown }\n | { op: \"sleep\"; id: string; until: string /* ISO timestamp */ }\n | {\n op: \"wait\";\n id: string;\n event: string;\n /** Key/value pairs the incoming event.data must contain; null = any. */\n match: Record<string, unknown> | null;\n until: string /* ISO timestamp timeout */;\n }\n | { op: \"done\"; data: unknown }\n | { op: \"error\"; id: string | null; message: string; retryable: boolean };\n\n/** A function is triggered by either an event name or a cron expression. */\nexport type Trigger = { event: string } | { cron: string };\n\n/** Optional concurrency cap for a function. */\nexport interface Concurrency {\n /** Max runs executing at once (per key, if given). */\n limit: number;\n /** event.data field name to scope the limit by (e.g. \"tenantId\"). */\n key?: string;\n}\n\n/** Wire form of a throttle/rate-limit rule (period already in ms). */\nexport interface WindowLimit {\n limit: number;\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a debounce rule (period already in ms). */\nexport interface DebounceRule {\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a batch rule (timeout already in ms). */\nexport interface BatchRule {\n maxSize: number;\n timeoutMs: number;\n key?: string;\n}\n\n/** Registry sync: app -> service, \"here are my functions and where to reach me\". */\nexport interface SyncRequest {\n /** Base URL the service should POST invocations to (callback endpoint). */\n url: string;\n functions: Array<{\n id: string;\n trigger: Trigger;\n concurrency?: Concurrency;\n priority?: number;\n throttle?: WindowLimit;\n rateLimit?: WindowLimit;\n debounce?: DebounceRule;\n batch?: BatchRule;\n }>;\n}\n\n/** Header carrying the HMAC signature on every service<->app request body. */\nexport const SIGNATURE_HEADER = \"x-durable-signature\";\n\n/** Header carrying the app id on app -> service requests. */\nexport const APP_HEADER = \"x-durable-app\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/** Compute the hex HMAC-SHA256 of a raw request body. */\nexport function sign(body: string, key: string): string {\n return createHmac(\"sha256\", key).update(body).digest(\"hex\");\n}\n\n/** Constant-time verify of a signature against a raw body. */\nexport function verify(body: string, signature: string, key: string): boolean {\n const expected = sign(body, key);\n const a = Buffer.from(expected);\n const b = Buffer.from(signature);\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport {\n sign,\n verify,\n SIGNATURE_HEADER,\n type InvokeRequest,\n} from \"@durable/shared\";\nimport { runInvocation, type DurableFunction } from \"./function.js\";\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (c) => chunks.push(c as Buffer));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n}\n\n/**\n * Returns a Node HTTP handler (also usable directly as an Express route) that\n * the Durable service POSTs invocations to. Verifies the HMAC, replays the run,\n * and returns the next operation — itself signed so the service can trust it.\n *\n * Mount this route WITHOUT a JSON body parser; the handler reads the raw stream\n * so it can verify the signature over the exact bytes.\n */\nexport function serve(\n functions: DurableFunction[],\n opts: { signingKey: string },\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const raw = await readBody(req);\n const sig = req.headers[SIGNATURE_HEADER];\n if (typeof sig !== \"string\" || !verify(raw, sig, opts.signingKey)) {\n res.writeHead(401).end(\"invalid signature\");\n return;\n }\n\n const body = JSON.parse(raw) as InvokeRequest;\n const fn = functions.find((f) => f.id === body.functionId);\n const response = fn\n ? await runInvocation(fn, body)\n : ({\n op: \"error\" as const,\n id: null,\n message: `unknown function ${body.functionId}`,\n retryable: false,\n });\n\n const out = JSON.stringify(response);\n res\n .writeHead(200, {\n \"content-type\": \"application/json\",\n [SIGNATURE_HEADER]: sign(out, opts.signingKey),\n })\n .end(out);\n } catch (err) {\n res.writeHead(500).end(err instanceof Error ? err.message : String(err));\n }\n };\n}\n","import {\n sign,\n SIGNATURE_HEADER,\n APP_HEADER,\n type SyncRequest,\n type WindowLimit,\n} from \"@durable/shared\";\nimport type { DurableFunction } from \"./function.js\";\nimport { parseDuration } from \"./step.js\";\n\nfunction toWindow(w?: { limit: number; period: string | number; key?: string }): WindowLimit | undefined {\n return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : undefined;\n}\n\nexport interface DurableClientOptions {\n /** Base URL of the Durable service. */\n baseUrl: string;\n /** This app's id (from the dashboard). */\n appId: string;\n /** This app's signing key (from the dashboard). */\n signingKey: string;\n /** This app's callback base URL, where the service POSTs invocations. */\n appUrl: string;\n}\n\nexport class DurableClient {\n constructor(private readonly opts: DurableClientOptions) {}\n\n private async post(path: string, payload: unknown): Promise<void> {\n const body = JSON.stringify(payload);\n const res = await fetch(this.opts.baseUrl + path, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n [APP_HEADER]: this.opts.appId,\n [SIGNATURE_HEADER]: sign(body, this.opts.signingKey),\n },\n body,\n });\n if (!res.ok) {\n throw new Error(\n `durable ${path} failed: ${res.status} ${await res.text()}`,\n );\n }\n }\n\n /** Register this app's functions and callback URL with the service. */\n async sync(functions: DurableFunction[]): Promise<void> {\n const payload: SyncRequest = {\n url: this.opts.appUrl,\n functions: functions.map((f) => ({\n id: f.id,\n trigger: f.trigger,\n concurrency: f.concurrency,\n priority: f.priority,\n throttle: toWindow(f.throttle),\n rateLimit: toWindow(f.rateLimit),\n debounce: f.debounce\n ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key }\n : undefined,\n batch: f.batch\n ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key }\n : undefined,\n })),\n };\n await this.post(\"/fn/sync\", payload);\n }\n\n /** Send an event to the service. */\n async send(event: { name: string; data?: unknown }): Promise<void> {\n await this.post(\"/e\", { name: event.name, data: event.data ?? {} });\n }\n}\n"],"mappings":"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","names":["message"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/step.ts","../src/function.ts","../../shared/src/protocol.ts","../../shared/src/hmac.ts","../src/version.ts","../src/serve.ts","../src/client.ts"],"sourcesContent":["export { createFunction, runInvocation } from \"./function.js\";\nexport type { DurableFunction, FunctionContext } from \"./function.js\";\nexport { createStep, parseDuration, StepInterrupt } from \"./step.js\";\nexport type { StepTools, WaitForEventOptions } from \"./step.js\";\nexport { serve } from \"./serve.js\";\nexport { DurableClient } from \"./client.js\";\nexport type { DurableClientOptions } from \"./client.js\";\nexport { SDK_VERSION } from \"./version.js\";\n","import type { DurableEvent, InvokeResponse, MemoizedStep } from \"@durable/shared\";\n\n/**\n * Thrown to unwind the handler the moment it reaches a new operation. The\n * carried response is exactly what the SDK reports to the service. Using an\n * exception means at most one *new* step executes per invocation — everything\n * after it is skipped until the service re-invokes with that step memoized.\n */\nexport class StepInterrupt extends Error {\n constructor(public readonly response: InvokeResponse) {\n super(\"durable:step-interrupt\");\n }\n}\n\nexport interface WaitForEventOptions {\n /** The event name to wait for. */\n event: string;\n /** Key/value pairs the incoming event.data must contain to match. */\n match?: Record<string, unknown>;\n /** How long to wait before giving up (e.g. \"7d\", \"1h\", or ms). */\n timeout: string | number;\n}\n\nexport interface StepTools {\n /** Run a side-effecting step once; its result is memoized and replayed. */\n run<T>(id: string, cb: () => Promise<T> | T): Promise<T>;\n /** Pause the run until the duration elapses (e.g. \"10s\", \"2d\", or ms). */\n sleep(id: string, duration: string | number): Promise<void>;\n /**\n * Pause the run until a matching event arrives, or the timeout elapses.\n * Resolves with the matched event, or `null` if it timed out.\n */\n waitForEvent(id: string, opts: WaitForEventOptions): Promise<DurableEvent | null>;\n}\n\nconst UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nexport function parseDuration(d: string | number): number {\n if (typeof d === \"number\") return d;\n const m = /^(\\d+)\\s*(ms|s|m|h|d)$/.exec(d.trim());\n if (!m) throw new Error(`invalid duration: ${d}`);\n return Number(m[1]) * UNITS[m[2]];\n}\n\nfunction message(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Build the step tools bound to the steps already memoized for this run. */\nexport function createStep(memoized: Record<string, MemoizedStep>): StepTools {\n return {\n async run<T>(id: string, cb: () => Promise<T> | T): Promise<T> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"run\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return memo.data as T; // replay: free, no side effect\n }\n let data: T;\n try {\n data = await cb();\n } catch (err) {\n // A genuine failure inside the step: let the service decide on retry.\n throw new StepInterrupt({\n op: \"error\",\n id,\n message: message(err),\n retryable: true,\n });\n }\n throw new StepInterrupt({ op: \"step\", id, data });\n },\n\n async sleep(id: string, duration: string | number): Promise<void> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"sleep\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return; // sleep already satisfied\n }\n const until = new Date(Date.now() + parseDuration(duration)).toISOString();\n throw new StepInterrupt({ op: \"sleep\", id, until });\n },\n\n async waitForEvent(id, opts): Promise<DurableEvent | null> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"wait\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return (memo.data as DurableEvent | null) ?? null; // event, or null on timeout\n }\n const until = new Date(Date.now() + parseDuration(opts.timeout)).toISOString();\n throw new StepInterrupt({\n op: \"wait\",\n id,\n event: opts.event,\n match: opts.match ?? null,\n until,\n });\n },\n };\n}\n","import type {\n Concurrency,\n DurableEvent,\n InvokeRequest,\n InvokeResponse,\n Trigger,\n SyncRequest,\n WindowLimit,\n} from \"@durable/shared\";\nimport { createStep, StepInterrupt, type StepTools } from \"./step.js\";\nimport { parseDuration } from \"./step.js\";\n\nexport interface FunctionContext {\n event: DurableEvent;\n step: StepTools;\n}\n\nexport interface DurableFunction {\n id: string;\n /** Triggered by an event name (`{ event }`) or a cron expression (`{ cron }`). */\n trigger: Trigger;\n /** Optional cap on how many runs execute at once (per key, if given). */\n concurrency?: Concurrency;\n /** Higher runs sooner when jobs compete in the queue (default 0). */\n priority?: number;\n /** Spread run starts over time: max `limit` per `period` (excess delayed). */\n throttle?: { limit: number; period: string | number; key?: string };\n /** Cap new runs: max `limit` per `period` (excess dropped). */\n rateLimit?: { limit: number; period: string | number; key?: string };\n /** Collapse rapid events: one run with the LAST event, once quiet for `period`. */\n debounce?: { period: string | number; key?: string };\n /**\n * Group events: ONE run receives event.data = the whole list, flushed at\n * `maxSize` events or `timeout` after the first (event.name = \"$batch\").\n */\n batch?: { maxSize: number; timeout: string | number; key?: string };\n handler: (ctx: FunctionContext) => Promise<unknown>;\n}\n\nexport function createFunction(def: DurableFunction): DurableFunction {\n return def;\n}\n\n/**\n * Replay a single invocation: run the handler from the top, returning the next\n * operation it reaches (a new step, a sleep, completion, or an error).\n */\nexport async function runInvocation(\n fn: DurableFunction,\n req: InvokeRequest,\n): Promise<InvokeResponse> {\n const step = createStep(req.steps);\n try {\n const data = await fn.handler({ event: req.event, step });\n return { op: \"done\", data };\n } catch (err) {\n if (err instanceof StepInterrupt) return err.response;\n // Error thrown outside any step.run (in the handler body itself).\n const message = err instanceof Error ? err.message : String(err);\n return { op: \"error\", id: null, message, retryable: true };\n }\n}\n\nfunction toWindow(w?: { limit: number; period: string | number; key?: string }): WindowLimit | undefined {\n return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : undefined;\n}\n\n/** Build the wire manifest for a set of functions (shared by push sync and pull GET). */\nexport function buildManifest(functions: DurableFunction[]): SyncRequest[\"functions\"] {\n return functions.map((f) => ({\n id: f.id,\n trigger: f.trigger,\n concurrency: f.concurrency,\n priority: f.priority,\n throttle: toWindow(f.throttle),\n rateLimit: toWindow(f.rateLimit),\n debounce: f.debounce\n ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key }\n : undefined,\n batch: f.batch\n ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key }\n : undefined,\n }));\n}\n","/**\n * Wire protocol between the Durable service and the app SDK.\n *\n * One HTTP round-trip advances a run by at most one *new* step. The service\n * sends the run state (event + already-memoized steps); the SDK replays the\n * cached steps for free, executes the next new operation, and reports it back.\n */\n\n/** An event that triggered (or can trigger) a run. */\nexport interface DurableEvent {\n id: string;\n name: string;\n data: unknown;\n}\n\n/** A previously-completed step, keyed by its user-supplied step id. */\nexport interface MemoizedStep {\n /**\n * \"run\" = a step.run result,\n * \"sleep\" = a satisfied step.sleep marker,\n * \"wait\" = a settled step.waitForEvent (data = the event, or null on timeout).\n */\n type: \"run\" | \"sleep\" | \"wait\";\n /** Output of step.run, or the matched event for \"wait\". Null otherwise. */\n data: unknown;\n}\n\n/** Service -> SDK: \"advance this run\". */\nexport interface InvokeRequest {\n runId: string;\n functionId: string;\n event: DurableEvent;\n /** Completed steps so far, keyed by step id. */\n steps: Record<string, MemoizedStep>;\n}\n\n/**\n * SDK -> service: the next operation the function reached.\n *\n * - step : a new step.run executed; persist `data` under `id`, then re-invoke.\n * - sleep : function wants to pause until `until`; arm a timer.\n * - done : function returned; the run is complete with `data`.\n * - error : the current step threw; retry (if retryable) or fail the run.\n */\nexport type InvokeResponse =\n | { op: \"step\"; id: string; data: unknown }\n | { op: \"sleep\"; id: string; until: string /* ISO timestamp */ }\n | {\n op: \"wait\";\n id: string;\n event: string;\n /** Key/value pairs the incoming event.data must contain; null = any. */\n match: Record<string, unknown> | null;\n until: string /* ISO timestamp timeout */;\n }\n | { op: \"done\"; data: unknown }\n | { op: \"error\"; id: string | null; message: string; retryable: boolean };\n\n/** A function is triggered by either an event name or a cron expression. */\nexport type Trigger = { event: string } | { cron: string };\n\n/** Optional concurrency cap for a function. */\nexport interface Concurrency {\n /** Max runs executing at once (per key, if given). */\n limit: number;\n /** event.data field name to scope the limit by (e.g. \"tenantId\"). */\n key?: string;\n}\n\n/** Wire form of a throttle/rate-limit rule (period already in ms). */\nexport interface WindowLimit {\n limit: number;\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a debounce rule (period already in ms). */\nexport interface DebounceRule {\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a batch rule (timeout already in ms). */\nexport interface BatchRule {\n maxSize: number;\n timeoutMs: number;\n key?: string;\n}\n\n/** Registry sync: app -> service, \"here are my functions and where to reach me\". */\nexport interface SyncRequest {\n /** Base URL the service should POST invocations to (callback endpoint). */\n url: string;\n /** SDK version reporting in (\"dev\" for the monorepo's tsx path); absent for older/non-JS SDKs. */\n sdkVersion?: string;\n functions: Array<{\n id: string;\n trigger: Trigger;\n concurrency?: Concurrency;\n priority?: number;\n throttle?: WindowLimit;\n rateLimit?: WindowLimit;\n debounce?: DebounceRule;\n batch?: BatchRule;\n }>;\n}\n\n/** Service <- app (pull): the app's function manifest, same shape as SyncRequest.functions. */\nexport interface ManifestResponse {\n functions: SyncRequest[\"functions\"];\n /** SDK version reporting in (\"dev\" for the monorepo's tsx path); absent for older/non-JS SDKs. */\n sdkVersion?: string;\n}\n\n/** Header carrying the HMAC signature on every service<->app request body. */\nexport const SIGNATURE_HEADER = \"x-durable-signature\";\n\n/** Header carrying the app id on app -> service requests. */\nexport const APP_HEADER = \"x-durable-app\";\n\n/** Header carrying the millisecond timestamp a signed GET (manifest pull) was made at. */\nexport const TIMESTAMP_HEADER = \"x-durable-timestamp\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/** Compute the hex HMAC-SHA256 of a raw request body. */\nexport function sign(body: string, key: string): string {\n return createHmac(\"sha256\", key).update(body).digest(\"hex\");\n}\n\n/** Constant-time verify of a signature against a raw body. */\nexport function verify(body: string, signature: string, key: string): boolean {\n const expected = sign(body, key);\n const a = Buffer.from(expected);\n const b = Buffer.from(signature);\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n","// Baked in by tsup's esbuild `define` (see tsup.config.ts) from package.json's\n// version at build time. Undefined under the monorepo's zero-build tsx path.\ndeclare const __SDK_VERSION__: string | undefined;\n\nexport const SDK_VERSION =\n typeof __SDK_VERSION__ !== \"undefined\" ? __SDK_VERSION__ : \"dev\";\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport {\n sign,\n verify,\n SIGNATURE_HEADER,\n TIMESTAMP_HEADER,\n type InvokeRequest,\n} from \"@durable/shared\";\nimport { runInvocation, buildManifest, type DurableFunction } from \"./function.js\";\nimport { SDK_VERSION } from \"./version.js\";\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (c) => chunks.push(c as Buffer));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n}\n\n/**\n * Returns a Node HTTP handler (also usable directly as an Express route) that\n * the Durable service POSTs invocations to. Verifies the HMAC, replays the run,\n * and returns the next operation — itself signed so the service can trust it.\n *\n * Mount this route WITHOUT a JSON body parser; the handler reads the raw stream\n * so it can verify the signature over the exact bytes.\n */\nexport function serve(\n functions: DurableFunction[],\n opts: { signingKey: string },\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (req.method === \"GET\") {\n const ts = req.headers[TIMESTAMP_HEADER];\n const sig = req.headers[SIGNATURE_HEADER];\n const fresh = typeof ts === \"string\" && Math.abs(Date.now() - Number(ts)) <= 5 * 60 * 1000;\n if (!fresh || typeof sig !== \"string\" || !verify(ts as string, sig, opts.signingKey)) {\n res.writeHead(401).end(\"invalid signature\");\n return;\n }\n const out = JSON.stringify({\n functions: buildManifest(functions),\n sdkVersion: SDK_VERSION,\n });\n res\n .writeHead(200, {\n \"content-type\": \"application/json\",\n [SIGNATURE_HEADER]: sign(out, opts.signingKey),\n })\n .end(out);\n return;\n }\n\n const raw = await readBody(req);\n const sig = req.headers[SIGNATURE_HEADER];\n if (typeof sig !== \"string\" || !verify(raw, sig, opts.signingKey)) {\n res.writeHead(401).end(\"invalid signature\");\n return;\n }\n\n const body = JSON.parse(raw) as InvokeRequest;\n const fn = functions.find((f) => f.id === body.functionId);\n const response = fn\n ? await runInvocation(fn, body)\n : ({\n op: \"error\" as const,\n id: null,\n message: `unknown function ${body.functionId}`,\n retryable: false,\n });\n\n const out = JSON.stringify(response);\n res\n .writeHead(200, {\n \"content-type\": \"application/json\",\n [SIGNATURE_HEADER]: sign(out, opts.signingKey),\n })\n .end(out);\n } catch (err) {\n res.writeHead(500).end(err instanceof Error ? err.message : String(err));\n }\n };\n}\n","import {\n sign,\n SIGNATURE_HEADER,\n APP_HEADER,\n type SyncRequest,\n} from \"@durable/shared\";\nimport type { DurableFunction } from \"./function.js\";\nimport { buildManifest } from \"./function.js\";\nimport { SDK_VERSION } from \"./version.js\";\n\nexport interface DurableClientOptions {\n /** Base URL of the Durable service. */\n baseUrl: string;\n /** This app's id (from the dashboard). */\n appId: string;\n /** This app's signing key (from the dashboard). */\n signingKey: string;\n /** This app's callback base URL, where the service POSTs invocations. */\n appUrl: string;\n}\n\nexport class DurableClient {\n constructor(private readonly opts: DurableClientOptions) {}\n\n private async post(path: string, payload: unknown): Promise<void> {\n const body = JSON.stringify(payload);\n const res = await fetch(this.opts.baseUrl + path, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n [APP_HEADER]: this.opts.appId,\n [SIGNATURE_HEADER]: sign(body, this.opts.signingKey),\n },\n body,\n });\n if (!res.ok) {\n throw new Error(\n `durable ${path} failed: ${res.status} ${await res.text()}`,\n );\n }\n }\n\n /** Register this app's functions and callback URL with the service. */\n async sync(functions: DurableFunction[]): Promise<void> {\n const payload: SyncRequest = {\n url: this.opts.appUrl,\n sdkVersion: SDK_VERSION,\n functions: buildManifest(functions),\n };\n await this.post(\"/fn/sync\", payload);\n }\n\n /** Send an event to the service. */\n async send(event: { name: string; data?: unknown }): Promise<void> {\n await this.post(\"/e\", { name: event.name, data: event.data ?? {} });\n }\n\n /** Create (or reuse, idempotent by name+env) this app's workspace, sending its appUrl. */\n async provision(opts: { name: string; environment?: string }): Promise<{ id: string; key: string }> {\n const res = await fetch(this.opts.baseUrl + \"/api/apps\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n name: opts.name,\n environment: opts.environment,\n appUrl: this.opts.appUrl,\n }),\n });\n if (!res.ok) throw new Error(`provision failed: ${res.status} ${await res.text()}`);\n return (await res.json()) as { id: string; key: string };\n }\n}\n"],"mappings":"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","names":["message","sig","out"]}
package/dist/index.d.cts CHANGED
@@ -114,6 +114,16 @@ declare class DurableClient {
114
114
  name: string;
115
115
  data?: unknown;
116
116
  }): Promise<void>;
117
+ /** Create (or reuse, idempotent by name+env) this app's workspace, sending its appUrl. */
118
+ provision(opts: {
119
+ name: string;
120
+ environment?: string;
121
+ }): Promise<{
122
+ id: string;
123
+ key: string;
124
+ }>;
117
125
  }
118
126
 
119
- export { DurableClient, type DurableClientOptions, type DurableFunction, type FunctionContext, StepInterrupt, type StepTools, type WaitForEventOptions, createFunction, createStep, parseDuration, runInvocation, serve };
127
+ declare const SDK_VERSION: string;
128
+
129
+ export { DurableClient, type DurableClientOptions, type DurableFunction, type FunctionContext, SDK_VERSION, StepInterrupt, type StepTools, type WaitForEventOptions, createFunction, createStep, parseDuration, runInvocation, serve };
package/dist/index.d.ts CHANGED
@@ -114,6 +114,16 @@ declare class DurableClient {
114
114
  name: string;
115
115
  data?: unknown;
116
116
  }): Promise<void>;
117
+ /** Create (or reuse, idempotent by name+env) this app's workspace, sending its appUrl. */
118
+ provision(opts: {
119
+ name: string;
120
+ environment?: string;
121
+ }): Promise<{
122
+ id: string;
123
+ key: string;
124
+ }>;
117
125
  }
118
126
 
119
- export { DurableClient, type DurableClientOptions, type DurableFunction, type FunctionContext, StepInterrupt, type StepTools, type WaitForEventOptions, createFunction, createStep, parseDuration, runInvocation, serve };
127
+ declare const SDK_VERSION: string;
128
+
129
+ export { DurableClient, type DurableClientOptions, type DurableFunction, type FunctionContext, SDK_VERSION, StepInterrupt, type StepTools, type WaitForEventOptions, createFunction, createStep, parseDuration, runInvocation, serve };
package/dist/index.js CHANGED
@@ -91,10 +91,26 @@ async function runInvocation(fn, req) {
91
91
  return { op: "error", id: null, message: message2, retryable: true };
92
92
  }
93
93
  }
94
+ function toWindow(w) {
95
+ return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : void 0;
96
+ }
97
+ function buildManifest(functions) {
98
+ return functions.map((f) => ({
99
+ id: f.id,
100
+ trigger: f.trigger,
101
+ concurrency: f.concurrency,
102
+ priority: f.priority,
103
+ throttle: toWindow(f.throttle),
104
+ rateLimit: toWindow(f.rateLimit),
105
+ debounce: f.debounce ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key } : void 0,
106
+ batch: f.batch ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key } : void 0
107
+ }));
108
+ }
94
109
 
95
110
  // ../shared/src/protocol.ts
96
111
  var SIGNATURE_HEADER = "x-durable-signature";
97
112
  var APP_HEADER = "x-durable-app";
113
+ var TIMESTAMP_HEADER = "x-durable-timestamp";
98
114
 
99
115
  // ../shared/src/hmac.ts
100
116
  import { createHmac, timingSafeEqual } from "crypto";
@@ -109,6 +125,9 @@ function verify(body, signature, key) {
109
125
  return timingSafeEqual(a, b);
110
126
  }
111
127
 
128
+ // src/version.ts
129
+ var SDK_VERSION = true ? "0.2.1" : "dev";
130
+
112
131
  // src/serve.ts
113
132
  function readBody(req) {
114
133
  return new Promise((resolve, reject) => {
@@ -121,6 +140,24 @@ function readBody(req) {
121
140
  function serve(functions, opts) {
122
141
  return async (req, res) => {
123
142
  try {
143
+ if (req.method === "GET") {
144
+ const ts = req.headers[TIMESTAMP_HEADER];
145
+ const sig2 = req.headers[SIGNATURE_HEADER];
146
+ const fresh = typeof ts === "string" && Math.abs(Date.now() - Number(ts)) <= 5 * 60 * 1e3;
147
+ if (!fresh || typeof sig2 !== "string" || !verify(ts, sig2, opts.signingKey)) {
148
+ res.writeHead(401).end("invalid signature");
149
+ return;
150
+ }
151
+ const out2 = JSON.stringify({
152
+ functions: buildManifest(functions),
153
+ sdkVersion: SDK_VERSION
154
+ });
155
+ res.writeHead(200, {
156
+ "content-type": "application/json",
157
+ [SIGNATURE_HEADER]: sign(out2, opts.signingKey)
158
+ }).end(out2);
159
+ return;
160
+ }
124
161
  const raw = await readBody(req);
125
162
  const sig = req.headers[SIGNATURE_HEADER];
126
163
  if (typeof sig !== "string" || !verify(raw, sig, opts.signingKey)) {
@@ -147,9 +184,6 @@ function serve(functions, opts) {
147
184
  }
148
185
 
149
186
  // src/client.ts
150
- function toWindow(w) {
151
- return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : void 0;
152
- }
153
187
  var DurableClient = class {
154
188
  constructor(opts) {
155
189
  this.opts = opts;
@@ -176,16 +210,8 @@ var DurableClient = class {
176
210
  async sync(functions) {
177
211
  const payload = {
178
212
  url: this.opts.appUrl,
179
- functions: functions.map((f) => ({
180
- id: f.id,
181
- trigger: f.trigger,
182
- concurrency: f.concurrency,
183
- priority: f.priority,
184
- throttle: toWindow(f.throttle),
185
- rateLimit: toWindow(f.rateLimit),
186
- debounce: f.debounce ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key } : void 0,
187
- batch: f.batch ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key } : void 0
188
- }))
213
+ sdkVersion: SDK_VERSION,
214
+ functions: buildManifest(functions)
189
215
  };
190
216
  await this.post("/fn/sync", payload);
191
217
  }
@@ -193,9 +219,24 @@ var DurableClient = class {
193
219
  async send(event) {
194
220
  await this.post("/e", { name: event.name, data: event.data ?? {} });
195
221
  }
222
+ /** Create (or reuse, idempotent by name+env) this app's workspace, sending its appUrl. */
223
+ async provision(opts) {
224
+ const res = await fetch(this.opts.baseUrl + "/api/apps", {
225
+ method: "POST",
226
+ headers: { "content-type": "application/json" },
227
+ body: JSON.stringify({
228
+ name: opts.name,
229
+ environment: opts.environment,
230
+ appUrl: this.opts.appUrl
231
+ })
232
+ });
233
+ if (!res.ok) throw new Error(`provision failed: ${res.status} ${await res.text()}`);
234
+ return await res.json();
235
+ }
196
236
  };
197
237
  export {
198
238
  DurableClient,
239
+ SDK_VERSION,
199
240
  StepInterrupt,
200
241
  createFunction,
201
242
  createStep,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/step.ts","../src/function.ts","../../shared/src/protocol.ts","../../shared/src/hmac.ts","../src/serve.ts","../src/client.ts"],"sourcesContent":["import type { DurableEvent, InvokeResponse, MemoizedStep } from \"@durable/shared\";\n\n/**\n * Thrown to unwind the handler the moment it reaches a new operation. The\n * carried response is exactly what the SDK reports to the service. Using an\n * exception means at most one *new* step executes per invocation — everything\n * after it is skipped until the service re-invokes with that step memoized.\n */\nexport class StepInterrupt extends Error {\n constructor(public readonly response: InvokeResponse) {\n super(\"durable:step-interrupt\");\n }\n}\n\nexport interface WaitForEventOptions {\n /** The event name to wait for. */\n event: string;\n /** Key/value pairs the incoming event.data must contain to match. */\n match?: Record<string, unknown>;\n /** How long to wait before giving up (e.g. \"7d\", \"1h\", or ms). */\n timeout: string | number;\n}\n\nexport interface StepTools {\n /** Run a side-effecting step once; its result is memoized and replayed. */\n run<T>(id: string, cb: () => Promise<T> | T): Promise<T>;\n /** Pause the run until the duration elapses (e.g. \"10s\", \"2d\", or ms). */\n sleep(id: string, duration: string | number): Promise<void>;\n /**\n * Pause the run until a matching event arrives, or the timeout elapses.\n * Resolves with the matched event, or `null` if it timed out.\n */\n waitForEvent(id: string, opts: WaitForEventOptions): Promise<DurableEvent | null>;\n}\n\nconst UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nexport function parseDuration(d: string | number): number {\n if (typeof d === \"number\") return d;\n const m = /^(\\d+)\\s*(ms|s|m|h|d)$/.exec(d.trim());\n if (!m) throw new Error(`invalid duration: ${d}`);\n return Number(m[1]) * UNITS[m[2]];\n}\n\nfunction message(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Build the step tools bound to the steps already memoized for this run. */\nexport function createStep(memoized: Record<string, MemoizedStep>): StepTools {\n return {\n async run<T>(id: string, cb: () => Promise<T> | T): Promise<T> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"run\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return memo.data as T; // replay: free, no side effect\n }\n let data: T;\n try {\n data = await cb();\n } catch (err) {\n // A genuine failure inside the step: let the service decide on retry.\n throw new StepInterrupt({\n op: \"error\",\n id,\n message: message(err),\n retryable: true,\n });\n }\n throw new StepInterrupt({ op: \"step\", id, data });\n },\n\n async sleep(id: string, duration: string | number): Promise<void> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"sleep\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return; // sleep already satisfied\n }\n const until = new Date(Date.now() + parseDuration(duration)).toISOString();\n throw new StepInterrupt({ op: \"sleep\", id, until });\n },\n\n async waitForEvent(id, opts): Promise<DurableEvent | null> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"wait\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return (memo.data as DurableEvent | null) ?? null; // event, or null on timeout\n }\n const until = new Date(Date.now() + parseDuration(opts.timeout)).toISOString();\n throw new StepInterrupt({\n op: \"wait\",\n id,\n event: opts.event,\n match: opts.match ?? null,\n until,\n });\n },\n };\n}\n","import type {\n Concurrency,\n DurableEvent,\n InvokeRequest,\n InvokeResponse,\n Trigger,\n} from \"@durable/shared\";\nimport { createStep, StepInterrupt, type StepTools } from \"./step.js\";\n\nexport interface FunctionContext {\n event: DurableEvent;\n step: StepTools;\n}\n\nexport interface DurableFunction {\n id: string;\n /** Triggered by an event name (`{ event }`) or a cron expression (`{ cron }`). */\n trigger: Trigger;\n /** Optional cap on how many runs execute at once (per key, if given). */\n concurrency?: Concurrency;\n /** Higher runs sooner when jobs compete in the queue (default 0). */\n priority?: number;\n /** Spread run starts over time: max `limit` per `period` (excess delayed). */\n throttle?: { limit: number; period: string | number; key?: string };\n /** Cap new runs: max `limit` per `period` (excess dropped). */\n rateLimit?: { limit: number; period: string | number; key?: string };\n /** Collapse rapid events: one run with the LAST event, once quiet for `period`. */\n debounce?: { period: string | number; key?: string };\n /**\n * Group events: ONE run receives event.data = the whole list, flushed at\n * `maxSize` events or `timeout` after the first (event.name = \"$batch\").\n */\n batch?: { maxSize: number; timeout: string | number; key?: string };\n handler: (ctx: FunctionContext) => Promise<unknown>;\n}\n\nexport function createFunction(def: DurableFunction): DurableFunction {\n return def;\n}\n\n/**\n * Replay a single invocation: run the handler from the top, returning the next\n * operation it reaches (a new step, a sleep, completion, or an error).\n */\nexport async function runInvocation(\n fn: DurableFunction,\n req: InvokeRequest,\n): Promise<InvokeResponse> {\n const step = createStep(req.steps);\n try {\n const data = await fn.handler({ event: req.event, step });\n return { op: \"done\", data };\n } catch (err) {\n if (err instanceof StepInterrupt) return err.response;\n // Error thrown outside any step.run (in the handler body itself).\n const message = err instanceof Error ? err.message : String(err);\n return { op: \"error\", id: null, message, retryable: true };\n }\n}\n","/**\n * Wire protocol between the Durable service and the app SDK.\n *\n * One HTTP round-trip advances a run by at most one *new* step. The service\n * sends the run state (event + already-memoized steps); the SDK replays the\n * cached steps for free, executes the next new operation, and reports it back.\n */\n\n/** An event that triggered (or can trigger) a run. */\nexport interface DurableEvent {\n id: string;\n name: string;\n data: unknown;\n}\n\n/** A previously-completed step, keyed by its user-supplied step id. */\nexport interface MemoizedStep {\n /**\n * \"run\" = a step.run result,\n * \"sleep\" = a satisfied step.sleep marker,\n * \"wait\" = a settled step.waitForEvent (data = the event, or null on timeout).\n */\n type: \"run\" | \"sleep\" | \"wait\";\n /** Output of step.run, or the matched event for \"wait\". Null otherwise. */\n data: unknown;\n}\n\n/** Service -> SDK: \"advance this run\". */\nexport interface InvokeRequest {\n runId: string;\n functionId: string;\n event: DurableEvent;\n /** Completed steps so far, keyed by step id. */\n steps: Record<string, MemoizedStep>;\n}\n\n/**\n * SDK -> service: the next operation the function reached.\n *\n * - step : a new step.run executed; persist `data` under `id`, then re-invoke.\n * - sleep : function wants to pause until `until`; arm a timer.\n * - done : function returned; the run is complete with `data`.\n * - error : the current step threw; retry (if retryable) or fail the run.\n */\nexport type InvokeResponse =\n | { op: \"step\"; id: string; data: unknown }\n | { op: \"sleep\"; id: string; until: string /* ISO timestamp */ }\n | {\n op: \"wait\";\n id: string;\n event: string;\n /** Key/value pairs the incoming event.data must contain; null = any. */\n match: Record<string, unknown> | null;\n until: string /* ISO timestamp timeout */;\n }\n | { op: \"done\"; data: unknown }\n | { op: \"error\"; id: string | null; message: string; retryable: boolean };\n\n/** A function is triggered by either an event name or a cron expression. */\nexport type Trigger = { event: string } | { cron: string };\n\n/** Optional concurrency cap for a function. */\nexport interface Concurrency {\n /** Max runs executing at once (per key, if given). */\n limit: number;\n /** event.data field name to scope the limit by (e.g. \"tenantId\"). */\n key?: string;\n}\n\n/** Wire form of a throttle/rate-limit rule (period already in ms). */\nexport interface WindowLimit {\n limit: number;\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a debounce rule (period already in ms). */\nexport interface DebounceRule {\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a batch rule (timeout already in ms). */\nexport interface BatchRule {\n maxSize: number;\n timeoutMs: number;\n key?: string;\n}\n\n/** Registry sync: app -> service, \"here are my functions and where to reach me\". */\nexport interface SyncRequest {\n /** Base URL the service should POST invocations to (callback endpoint). */\n url: string;\n functions: Array<{\n id: string;\n trigger: Trigger;\n concurrency?: Concurrency;\n priority?: number;\n throttle?: WindowLimit;\n rateLimit?: WindowLimit;\n debounce?: DebounceRule;\n batch?: BatchRule;\n }>;\n}\n\n/** Header carrying the HMAC signature on every service<->app request body. */\nexport const SIGNATURE_HEADER = \"x-durable-signature\";\n\n/** Header carrying the app id on app -> service requests. */\nexport const APP_HEADER = \"x-durable-app\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/** Compute the hex HMAC-SHA256 of a raw request body. */\nexport function sign(body: string, key: string): string {\n return createHmac(\"sha256\", key).update(body).digest(\"hex\");\n}\n\n/** Constant-time verify of a signature against a raw body. */\nexport function verify(body: string, signature: string, key: string): boolean {\n const expected = sign(body, key);\n const a = Buffer.from(expected);\n const b = Buffer.from(signature);\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport {\n sign,\n verify,\n SIGNATURE_HEADER,\n type InvokeRequest,\n} from \"@durable/shared\";\nimport { runInvocation, type DurableFunction } from \"./function.js\";\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (c) => chunks.push(c as Buffer));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n}\n\n/**\n * Returns a Node HTTP handler (also usable directly as an Express route) that\n * the Durable service POSTs invocations to. Verifies the HMAC, replays the run,\n * and returns the next operation — itself signed so the service can trust it.\n *\n * Mount this route WITHOUT a JSON body parser; the handler reads the raw stream\n * so it can verify the signature over the exact bytes.\n */\nexport function serve(\n functions: DurableFunction[],\n opts: { signingKey: string },\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const raw = await readBody(req);\n const sig = req.headers[SIGNATURE_HEADER];\n if (typeof sig !== \"string\" || !verify(raw, sig, opts.signingKey)) {\n res.writeHead(401).end(\"invalid signature\");\n return;\n }\n\n const body = JSON.parse(raw) as InvokeRequest;\n const fn = functions.find((f) => f.id === body.functionId);\n const response = fn\n ? await runInvocation(fn, body)\n : ({\n op: \"error\" as const,\n id: null,\n message: `unknown function ${body.functionId}`,\n retryable: false,\n });\n\n const out = JSON.stringify(response);\n res\n .writeHead(200, {\n \"content-type\": \"application/json\",\n [SIGNATURE_HEADER]: sign(out, opts.signingKey),\n })\n .end(out);\n } catch (err) {\n res.writeHead(500).end(err instanceof Error ? err.message : String(err));\n }\n };\n}\n","import {\n sign,\n SIGNATURE_HEADER,\n APP_HEADER,\n type SyncRequest,\n type WindowLimit,\n} from \"@durable/shared\";\nimport type { DurableFunction } from \"./function.js\";\nimport { parseDuration } from \"./step.js\";\n\nfunction toWindow(w?: { limit: number; period: string | number; key?: string }): WindowLimit | undefined {\n return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : undefined;\n}\n\nexport interface DurableClientOptions {\n /** Base URL of the Durable service. */\n baseUrl: string;\n /** This app's id (from the dashboard). */\n appId: string;\n /** This app's signing key (from the dashboard). */\n signingKey: string;\n /** This app's callback base URL, where the service POSTs invocations. */\n appUrl: string;\n}\n\nexport class DurableClient {\n constructor(private readonly opts: DurableClientOptions) {}\n\n private async post(path: string, payload: unknown): Promise<void> {\n const body = JSON.stringify(payload);\n const res = await fetch(this.opts.baseUrl + path, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n [APP_HEADER]: this.opts.appId,\n [SIGNATURE_HEADER]: sign(body, this.opts.signingKey),\n },\n body,\n });\n if (!res.ok) {\n throw new Error(\n `durable ${path} failed: ${res.status} ${await res.text()}`,\n );\n }\n }\n\n /** Register this app's functions and callback URL with the service. */\n async sync(functions: DurableFunction[]): Promise<void> {\n const payload: SyncRequest = {\n url: this.opts.appUrl,\n functions: functions.map((f) => ({\n id: f.id,\n trigger: f.trigger,\n concurrency: f.concurrency,\n priority: f.priority,\n throttle: toWindow(f.throttle),\n rateLimit: toWindow(f.rateLimit),\n debounce: f.debounce\n ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key }\n : undefined,\n batch: f.batch\n ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key }\n : undefined,\n })),\n };\n await this.post(\"/fn/sync\", payload);\n }\n\n /** Send an event to the service. */\n async send(event: { name: string; data?: unknown }): Promise<void> {\n await this.post(\"/e\", { name: event.name, data: event.data ?? {} });\n }\n}\n"],"mappings":";AAQO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAA4B,UAA0B;AACpD,UAAM,wBAAwB;AADJ;AAAA,EAE5B;AAAA,EAF4B;AAG9B;AAuBA,IAAM,QAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEO,SAAS,cAAc,GAA4B;AACxD,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,QAAM,IAAI,yBAAyB,KAAK,EAAE,KAAK,CAAC;AAChD,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,qBAAqB,CAAC,EAAE;AAChD,SAAO,OAAO,EAAE,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;AAClC;AAEA,SAAS,QAAQ,KAAsB;AACrC,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAGO,SAAS,WAAW,UAAmD;AAC5E,SAAO;AAAA,IACL,MAAM,IAAO,IAAY,IAAsC;AAC7D,YAAM,OAAO,SAAS,EAAE;AACxB,UAAI,MAAM;AACR,YAAI,KAAK,SAAS,OAAO;AACvB,gBAAM,IAAI,MAAM,SAAS,EAAE,uBAAuB,KAAK,IAAI,EAAE;AAAA,QAC/D;AACA,eAAO,KAAK;AAAA,MACd;AACA,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,GAAG;AAAA,MAClB,SAAS,KAAK;AAEZ,cAAM,IAAI,cAAc;AAAA,UACtB,IAAI;AAAA,UACJ;AAAA,UACA,SAAS,QAAQ,GAAG;AAAA,UACpB,WAAW;AAAA,QACb,CAAC;AAAA,MACH;AACA,YAAM,IAAI,cAAc,EAAE,IAAI,QAAQ,IAAI,KAAK,CAAC;AAAA,IAClD;AAAA,IAEA,MAAM,MAAM,IAAY,UAA0C;AAChE,YAAM,OAAO,SAAS,EAAE;AACxB,UAAI,MAAM;AACR,YAAI,KAAK,SAAS,SAAS;AACzB,gBAAM,IAAI,MAAM,SAAS,EAAE,uBAAuB,KAAK,IAAI,EAAE;AAAA,QAC/D;AACA;AAAA,MACF;AACA,YAAM,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,cAAc,QAAQ,CAAC,EAAE,YAAY;AACzE,YAAM,IAAI,cAAc,EAAE,IAAI,SAAS,IAAI,MAAM,CAAC;AAAA,IACpD;AAAA,IAEA,MAAM,aAAa,IAAI,MAAoC;AACzD,YAAM,OAAO,SAAS,EAAE;AACxB,UAAI,MAAM;AACR,YAAI,KAAK,SAAS,QAAQ;AACxB,gBAAM,IAAI,MAAM,SAAS,EAAE,uBAAuB,KAAK,IAAI,EAAE;AAAA,QAC/D;AACA,eAAQ,KAAK,QAAgC;AAAA,MAC/C;AACA,YAAM,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,cAAc,KAAK,OAAO,CAAC,EAAE,YAAY;AAC7E,YAAM,IAAI,cAAc;AAAA,QACtB,IAAI;AAAA,QACJ;AAAA,QACA,OAAO,KAAK;AAAA,QACZ,OAAO,KAAK,SAAS;AAAA,QACrB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AC1EO,SAAS,eAAe,KAAuC;AACpE,SAAO;AACT;AAMA,eAAsB,cACpB,IACA,KACyB;AACzB,QAAM,OAAO,WAAW,IAAI,KAAK;AACjC,MAAI;AACF,UAAM,OAAO,MAAM,GAAG,QAAQ,EAAE,OAAO,IAAI,OAAO,KAAK,CAAC;AACxD,WAAO,EAAE,IAAI,QAAQ,KAAK;AAAA,EAC5B,SAAS,KAAK;AACZ,QAAI,eAAe,cAAe,QAAO,IAAI;AAE7C,UAAMA,WAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,EAAE,IAAI,SAAS,IAAI,MAAM,SAAAA,UAAS,WAAW,KAAK;AAAA,EAC3D;AACF;;;ACgDO,IAAM,mBAAmB;AAGzB,IAAM,aAAa;;;AC7G1B,SAAS,YAAY,uBAAuB;AAGrC,SAAS,KAAK,MAAc,KAAqB;AACtD,SAAO,WAAW,UAAU,GAAG,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAC5D;AAGO,SAAS,OAAO,MAAc,WAAmB,KAAsB;AAC5E,QAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,QAAM,IAAI,OAAO,KAAK,QAAQ;AAC9B,QAAM,IAAI,OAAO,KAAK,SAAS;AAC/B,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,SAAO,gBAAgB,GAAG,CAAC;AAC7B;;;ACLA,SAAS,SAAS,KAAuC;AACvD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,MAAM,OAAO,KAAK,CAAW,CAAC;AAC9C,QAAI,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC,CAAC;AACnE,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAUO,SAAS,MACd,WACA,MACA;AACA,SAAO,OAAO,KAAsB,QAAuC;AACzE,QAAI;AACF,YAAM,MAAM,MAAM,SAAS,GAAG;AAC9B,YAAM,MAAM,IAAI,QAAQ,gBAAgB;AACxC,UAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,KAAK,KAAK,KAAK,UAAU,GAAG;AACjE,YAAI,UAAU,GAAG,EAAE,IAAI,mBAAmB;AAC1C;AAAA,MACF;AAEA,YAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,YAAM,KAAK,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,UAAU;AACzD,YAAM,WAAW,KACb,MAAM,cAAc,IAAI,IAAI,IAC3B;AAAA,QACC,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,SAAS,oBAAoB,KAAK,UAAU;AAAA,QAC5C,WAAW;AAAA,MACb;AAEJ,YAAM,MAAM,KAAK,UAAU,QAAQ;AACnC,UACG,UAAU,KAAK;AAAA,QACd,gBAAgB;AAAA,QAChB,CAAC,gBAAgB,GAAG,KAAK,KAAK,KAAK,UAAU;AAAA,MAC/C,CAAC,EACA,IAAI,GAAG;AAAA,IACZ,SAAS,KAAK;AACZ,UAAI,UAAU,GAAG,EAAE,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACzE;AAAA,EACF;AACF;;;ACnDA,SAAS,SAAS,GAAuF;AACvG,SAAO,IAAI,EAAE,OAAO,EAAE,OAAO,UAAU,cAAc,EAAE,MAAM,GAAG,KAAK,EAAE,IAAI,IAAI;AACjF;AAaO,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAA6B,MAA4B;AAA5B;AAAA,EAA6B;AAAA,EAA7B;AAAA,EAE7B,MAAc,KAAK,MAAc,SAAiC;AAChE,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,UAAM,MAAM,MAAM,MAAM,KAAK,KAAK,UAAU,MAAM;AAAA,MAChD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,CAAC,UAAU,GAAG,KAAK,KAAK;AAAA,QACxB,CAAC,gBAAgB,GAAG,KAAK,MAAM,KAAK,KAAK,UAAU;AAAA,MACrD;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,WAAW,IAAI,YAAY,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,KAAK,WAA6C;AACtD,UAAM,UAAuB;AAAA,MAC3B,KAAK,KAAK,KAAK;AAAA,MACf,WAAW,UAAU,IAAI,CAAC,OAAO;AAAA,QAC/B,IAAI,EAAE;AAAA,QACN,SAAS,EAAE;AAAA,QACX,aAAa,EAAE;AAAA,QACf,UAAU,EAAE;AAAA,QACZ,UAAU,SAAS,EAAE,QAAQ;AAAA,QAC7B,WAAW,SAAS,EAAE,SAAS;AAAA,QAC/B,UAAU,EAAE,WACR,EAAE,UAAU,cAAc,EAAE,SAAS,MAAM,GAAG,KAAK,EAAE,SAAS,IAAI,IAClE;AAAA,QACJ,OAAO,EAAE,QACL,EAAE,SAAS,EAAE,MAAM,SAAS,WAAW,cAAc,EAAE,MAAM,OAAO,GAAG,KAAK,EAAE,MAAM,IAAI,IACxF;AAAA,MACN,EAAE;AAAA,IACJ;AACA,UAAM,KAAK,KAAK,YAAY,OAAO;AAAA,EACrC;AAAA;AAAA,EAGA,MAAM,KAAK,OAAwD;AACjE,UAAM,KAAK,KAAK,MAAM,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,CAAC,EAAE,CAAC;AAAA,EACpE;AACF;","names":["message"]}
1
+ {"version":3,"sources":["../src/step.ts","../src/function.ts","../../shared/src/protocol.ts","../../shared/src/hmac.ts","../src/version.ts","../src/serve.ts","../src/client.ts"],"sourcesContent":["import type { DurableEvent, InvokeResponse, MemoizedStep } from \"@durable/shared\";\n\n/**\n * Thrown to unwind the handler the moment it reaches a new operation. The\n * carried response is exactly what the SDK reports to the service. Using an\n * exception means at most one *new* step executes per invocation — everything\n * after it is skipped until the service re-invokes with that step memoized.\n */\nexport class StepInterrupt extends Error {\n constructor(public readonly response: InvokeResponse) {\n super(\"durable:step-interrupt\");\n }\n}\n\nexport interface WaitForEventOptions {\n /** The event name to wait for. */\n event: string;\n /** Key/value pairs the incoming event.data must contain to match. */\n match?: Record<string, unknown>;\n /** How long to wait before giving up (e.g. \"7d\", \"1h\", or ms). */\n timeout: string | number;\n}\n\nexport interface StepTools {\n /** Run a side-effecting step once; its result is memoized and replayed. */\n run<T>(id: string, cb: () => Promise<T> | T): Promise<T>;\n /** Pause the run until the duration elapses (e.g. \"10s\", \"2d\", or ms). */\n sleep(id: string, duration: string | number): Promise<void>;\n /**\n * Pause the run until a matching event arrives, or the timeout elapses.\n * Resolves with the matched event, or `null` if it timed out.\n */\n waitForEvent(id: string, opts: WaitForEventOptions): Promise<DurableEvent | null>;\n}\n\nconst UNITS: Record<string, number> = {\n ms: 1,\n s: 1000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nexport function parseDuration(d: string | number): number {\n if (typeof d === \"number\") return d;\n const m = /^(\\d+)\\s*(ms|s|m|h|d)$/.exec(d.trim());\n if (!m) throw new Error(`invalid duration: ${d}`);\n return Number(m[1]) * UNITS[m[2]];\n}\n\nfunction message(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Build the step tools bound to the steps already memoized for this run. */\nexport function createStep(memoized: Record<string, MemoizedStep>): StepTools {\n return {\n async run<T>(id: string, cb: () => Promise<T> | T): Promise<T> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"run\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return memo.data as T; // replay: free, no side effect\n }\n let data: T;\n try {\n data = await cb();\n } catch (err) {\n // A genuine failure inside the step: let the service decide on retry.\n throw new StepInterrupt({\n op: \"error\",\n id,\n message: message(err),\n retryable: true,\n });\n }\n throw new StepInterrupt({ op: \"step\", id, data });\n },\n\n async sleep(id: string, duration: string | number): Promise<void> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"sleep\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return; // sleep already satisfied\n }\n const until = new Date(Date.now() + parseDuration(duration)).toISOString();\n throw new StepInterrupt({ op: \"sleep\", id, until });\n },\n\n async waitForEvent(id, opts): Promise<DurableEvent | null> {\n const memo = memoized[id];\n if (memo) {\n if (memo.type !== \"wait\") {\n throw new Error(`step \"${id}\" already used as a ${memo.type}`);\n }\n return (memo.data as DurableEvent | null) ?? null; // event, or null on timeout\n }\n const until = new Date(Date.now() + parseDuration(opts.timeout)).toISOString();\n throw new StepInterrupt({\n op: \"wait\",\n id,\n event: opts.event,\n match: opts.match ?? null,\n until,\n });\n },\n };\n}\n","import type {\n Concurrency,\n DurableEvent,\n InvokeRequest,\n InvokeResponse,\n Trigger,\n SyncRequest,\n WindowLimit,\n} from \"@durable/shared\";\nimport { createStep, StepInterrupt, type StepTools } from \"./step.js\";\nimport { parseDuration } from \"./step.js\";\n\nexport interface FunctionContext {\n event: DurableEvent;\n step: StepTools;\n}\n\nexport interface DurableFunction {\n id: string;\n /** Triggered by an event name (`{ event }`) or a cron expression (`{ cron }`). */\n trigger: Trigger;\n /** Optional cap on how many runs execute at once (per key, if given). */\n concurrency?: Concurrency;\n /** Higher runs sooner when jobs compete in the queue (default 0). */\n priority?: number;\n /** Spread run starts over time: max `limit` per `period` (excess delayed). */\n throttle?: { limit: number; period: string | number; key?: string };\n /** Cap new runs: max `limit` per `period` (excess dropped). */\n rateLimit?: { limit: number; period: string | number; key?: string };\n /** Collapse rapid events: one run with the LAST event, once quiet for `period`. */\n debounce?: { period: string | number; key?: string };\n /**\n * Group events: ONE run receives event.data = the whole list, flushed at\n * `maxSize` events or `timeout` after the first (event.name = \"$batch\").\n */\n batch?: { maxSize: number; timeout: string | number; key?: string };\n handler: (ctx: FunctionContext) => Promise<unknown>;\n}\n\nexport function createFunction(def: DurableFunction): DurableFunction {\n return def;\n}\n\n/**\n * Replay a single invocation: run the handler from the top, returning the next\n * operation it reaches (a new step, a sleep, completion, or an error).\n */\nexport async function runInvocation(\n fn: DurableFunction,\n req: InvokeRequest,\n): Promise<InvokeResponse> {\n const step = createStep(req.steps);\n try {\n const data = await fn.handler({ event: req.event, step });\n return { op: \"done\", data };\n } catch (err) {\n if (err instanceof StepInterrupt) return err.response;\n // Error thrown outside any step.run (in the handler body itself).\n const message = err instanceof Error ? err.message : String(err);\n return { op: \"error\", id: null, message, retryable: true };\n }\n}\n\nfunction toWindow(w?: { limit: number; period: string | number; key?: string }): WindowLimit | undefined {\n return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : undefined;\n}\n\n/** Build the wire manifest for a set of functions (shared by push sync and pull GET). */\nexport function buildManifest(functions: DurableFunction[]): SyncRequest[\"functions\"] {\n return functions.map((f) => ({\n id: f.id,\n trigger: f.trigger,\n concurrency: f.concurrency,\n priority: f.priority,\n throttle: toWindow(f.throttle),\n rateLimit: toWindow(f.rateLimit),\n debounce: f.debounce\n ? { periodMs: parseDuration(f.debounce.period), key: f.debounce.key }\n : undefined,\n batch: f.batch\n ? { maxSize: f.batch.maxSize, timeoutMs: parseDuration(f.batch.timeout), key: f.batch.key }\n : undefined,\n }));\n}\n","/**\n * Wire protocol between the Durable service and the app SDK.\n *\n * One HTTP round-trip advances a run by at most one *new* step. The service\n * sends the run state (event + already-memoized steps); the SDK replays the\n * cached steps for free, executes the next new operation, and reports it back.\n */\n\n/** An event that triggered (or can trigger) a run. */\nexport interface DurableEvent {\n id: string;\n name: string;\n data: unknown;\n}\n\n/** A previously-completed step, keyed by its user-supplied step id. */\nexport interface MemoizedStep {\n /**\n * \"run\" = a step.run result,\n * \"sleep\" = a satisfied step.sleep marker,\n * \"wait\" = a settled step.waitForEvent (data = the event, or null on timeout).\n */\n type: \"run\" | \"sleep\" | \"wait\";\n /** Output of step.run, or the matched event for \"wait\". Null otherwise. */\n data: unknown;\n}\n\n/** Service -> SDK: \"advance this run\". */\nexport interface InvokeRequest {\n runId: string;\n functionId: string;\n event: DurableEvent;\n /** Completed steps so far, keyed by step id. */\n steps: Record<string, MemoizedStep>;\n}\n\n/**\n * SDK -> service: the next operation the function reached.\n *\n * - step : a new step.run executed; persist `data` under `id`, then re-invoke.\n * - sleep : function wants to pause until `until`; arm a timer.\n * - done : function returned; the run is complete with `data`.\n * - error : the current step threw; retry (if retryable) or fail the run.\n */\nexport type InvokeResponse =\n | { op: \"step\"; id: string; data: unknown }\n | { op: \"sleep\"; id: string; until: string /* ISO timestamp */ }\n | {\n op: \"wait\";\n id: string;\n event: string;\n /** Key/value pairs the incoming event.data must contain; null = any. */\n match: Record<string, unknown> | null;\n until: string /* ISO timestamp timeout */;\n }\n | { op: \"done\"; data: unknown }\n | { op: \"error\"; id: string | null; message: string; retryable: boolean };\n\n/** A function is triggered by either an event name or a cron expression. */\nexport type Trigger = { event: string } | { cron: string };\n\n/** Optional concurrency cap for a function. */\nexport interface Concurrency {\n /** Max runs executing at once (per key, if given). */\n limit: number;\n /** event.data field name to scope the limit by (e.g. \"tenantId\"). */\n key?: string;\n}\n\n/** Wire form of a throttle/rate-limit rule (period already in ms). */\nexport interface WindowLimit {\n limit: number;\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a debounce rule (period already in ms). */\nexport interface DebounceRule {\n periodMs: number;\n key?: string;\n}\n\n/** Wire form of a batch rule (timeout already in ms). */\nexport interface BatchRule {\n maxSize: number;\n timeoutMs: number;\n key?: string;\n}\n\n/** Registry sync: app -> service, \"here are my functions and where to reach me\". */\nexport interface SyncRequest {\n /** Base URL the service should POST invocations to (callback endpoint). */\n url: string;\n /** SDK version reporting in (\"dev\" for the monorepo's tsx path); absent for older/non-JS SDKs. */\n sdkVersion?: string;\n functions: Array<{\n id: string;\n trigger: Trigger;\n concurrency?: Concurrency;\n priority?: number;\n throttle?: WindowLimit;\n rateLimit?: WindowLimit;\n debounce?: DebounceRule;\n batch?: BatchRule;\n }>;\n}\n\n/** Service <- app (pull): the app's function manifest, same shape as SyncRequest.functions. */\nexport interface ManifestResponse {\n functions: SyncRequest[\"functions\"];\n /** SDK version reporting in (\"dev\" for the monorepo's tsx path); absent for older/non-JS SDKs. */\n sdkVersion?: string;\n}\n\n/** Header carrying the HMAC signature on every service<->app request body. */\nexport const SIGNATURE_HEADER = \"x-durable-signature\";\n\n/** Header carrying the app id on app -> service requests. */\nexport const APP_HEADER = \"x-durable-app\";\n\n/** Header carrying the millisecond timestamp a signed GET (manifest pull) was made at. */\nexport const TIMESTAMP_HEADER = \"x-durable-timestamp\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/** Compute the hex HMAC-SHA256 of a raw request body. */\nexport function sign(body: string, key: string): string {\n return createHmac(\"sha256\", key).update(body).digest(\"hex\");\n}\n\n/** Constant-time verify of a signature against a raw body. */\nexport function verify(body: string, signature: string, key: string): boolean {\n const expected = sign(body, key);\n const a = Buffer.from(expected);\n const b = Buffer.from(signature);\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n","// Baked in by tsup's esbuild `define` (see tsup.config.ts) from package.json's\n// version at build time. Undefined under the monorepo's zero-build tsx path.\ndeclare const __SDK_VERSION__: string | undefined;\n\nexport const SDK_VERSION =\n typeof __SDK_VERSION__ !== \"undefined\" ? __SDK_VERSION__ : \"dev\";\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport {\n sign,\n verify,\n SIGNATURE_HEADER,\n TIMESTAMP_HEADER,\n type InvokeRequest,\n} from \"@durable/shared\";\nimport { runInvocation, buildManifest, type DurableFunction } from \"./function.js\";\nimport { SDK_VERSION } from \"./version.js\";\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (c) => chunks.push(c as Buffer));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", reject);\n });\n}\n\n/**\n * Returns a Node HTTP handler (also usable directly as an Express route) that\n * the Durable service POSTs invocations to. Verifies the HMAC, replays the run,\n * and returns the next operation — itself signed so the service can trust it.\n *\n * Mount this route WITHOUT a JSON body parser; the handler reads the raw stream\n * so it can verify the signature over the exact bytes.\n */\nexport function serve(\n functions: DurableFunction[],\n opts: { signingKey: string },\n) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n if (req.method === \"GET\") {\n const ts = req.headers[TIMESTAMP_HEADER];\n const sig = req.headers[SIGNATURE_HEADER];\n const fresh = typeof ts === \"string\" && Math.abs(Date.now() - Number(ts)) <= 5 * 60 * 1000;\n if (!fresh || typeof sig !== \"string\" || !verify(ts as string, sig, opts.signingKey)) {\n res.writeHead(401).end(\"invalid signature\");\n return;\n }\n const out = JSON.stringify({\n functions: buildManifest(functions),\n sdkVersion: SDK_VERSION,\n });\n res\n .writeHead(200, {\n \"content-type\": \"application/json\",\n [SIGNATURE_HEADER]: sign(out, opts.signingKey),\n })\n .end(out);\n return;\n }\n\n const raw = await readBody(req);\n const sig = req.headers[SIGNATURE_HEADER];\n if (typeof sig !== \"string\" || !verify(raw, sig, opts.signingKey)) {\n res.writeHead(401).end(\"invalid signature\");\n return;\n }\n\n const body = JSON.parse(raw) as InvokeRequest;\n const fn = functions.find((f) => f.id === body.functionId);\n const response = fn\n ? await runInvocation(fn, body)\n : ({\n op: \"error\" as const,\n id: null,\n message: `unknown function ${body.functionId}`,\n retryable: false,\n });\n\n const out = JSON.stringify(response);\n res\n .writeHead(200, {\n \"content-type\": \"application/json\",\n [SIGNATURE_HEADER]: sign(out, opts.signingKey),\n })\n .end(out);\n } catch (err) {\n res.writeHead(500).end(err instanceof Error ? err.message : String(err));\n }\n };\n}\n","import {\n sign,\n SIGNATURE_HEADER,\n APP_HEADER,\n type SyncRequest,\n} from \"@durable/shared\";\nimport type { DurableFunction } from \"./function.js\";\nimport { buildManifest } from \"./function.js\";\nimport { SDK_VERSION } from \"./version.js\";\n\nexport interface DurableClientOptions {\n /** Base URL of the Durable service. */\n baseUrl: string;\n /** This app's id (from the dashboard). */\n appId: string;\n /** This app's signing key (from the dashboard). */\n signingKey: string;\n /** This app's callback base URL, where the service POSTs invocations. */\n appUrl: string;\n}\n\nexport class DurableClient {\n constructor(private readonly opts: DurableClientOptions) {}\n\n private async post(path: string, payload: unknown): Promise<void> {\n const body = JSON.stringify(payload);\n const res = await fetch(this.opts.baseUrl + path, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n [APP_HEADER]: this.opts.appId,\n [SIGNATURE_HEADER]: sign(body, this.opts.signingKey),\n },\n body,\n });\n if (!res.ok) {\n throw new Error(\n `durable ${path} failed: ${res.status} ${await res.text()}`,\n );\n }\n }\n\n /** Register this app's functions and callback URL with the service. */\n async sync(functions: DurableFunction[]): Promise<void> {\n const payload: SyncRequest = {\n url: this.opts.appUrl,\n sdkVersion: SDK_VERSION,\n functions: buildManifest(functions),\n };\n await this.post(\"/fn/sync\", payload);\n }\n\n /** Send an event to the service. */\n async send(event: { name: string; data?: unknown }): Promise<void> {\n await this.post(\"/e\", { name: event.name, data: event.data ?? {} });\n }\n\n /** Create (or reuse, idempotent by name+env) this app's workspace, sending its appUrl. */\n async provision(opts: { name: string; environment?: string }): Promise<{ id: string; key: string }> {\n const res = await fetch(this.opts.baseUrl + \"/api/apps\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n name: opts.name,\n environment: opts.environment,\n appUrl: this.opts.appUrl,\n }),\n });\n if (!res.ok) throw new Error(`provision failed: ${res.status} ${await res.text()}`);\n return (await res.json()) as { id: string; key: string };\n }\n}\n"],"mappings":"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","names":["message","sig","out"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dicabrio/durable-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "TypeScript SDK for the Durable execution engine: define durable functions, sync them, send events, and serve the replay callback.",
6
6
  "license": "MIT",