@dicabrio/durable-sdk 0.2.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,
@@ -157,6 +158,9 @@ function verify(body, signature, key) {
157
158
  return (0, import_node_crypto.timingSafeEqual)(a, b);
158
159
  }
159
160
 
161
+ // src/version.ts
162
+ var SDK_VERSION = true ? "0.2.1" : "dev";
163
+
160
164
  // src/serve.ts
161
165
  function readBody(req) {
162
166
  return new Promise((resolve, reject) => {
@@ -177,7 +181,10 @@ function serve(functions, opts) {
177
181
  res.writeHead(401).end("invalid signature");
178
182
  return;
179
183
  }
180
- const out2 = JSON.stringify({ functions: buildManifest(functions) });
184
+ const out2 = JSON.stringify({
185
+ functions: buildManifest(functions),
186
+ sdkVersion: SDK_VERSION
187
+ });
181
188
  res.writeHead(200, {
182
189
  "content-type": "application/json",
183
190
  [SIGNATURE_HEADER]: sign(out2, opts.signingKey)
@@ -236,6 +243,7 @@ var DurableClient = class {
236
243
  async sync(functions) {
237
244
  const payload = {
238
245
  url: this.opts.appUrl,
246
+ sdkVersion: SDK_VERSION,
239
247
  functions: buildManifest(functions)
240
248
  };
241
249
  await this.post("/fn/sync", payload);
@@ -262,6 +270,7 @@ var DurableClient = class {
262
270
  // Annotate the CommonJS export names for ESM import in node:
263
271
  0 && (module.exports = {
264
272
  DurableClient,
273
+ SDK_VERSION,
265
274
  StepInterrupt,
266
275
  createFunction,
267
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 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 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}\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","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\";\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({ functions: buildManifest(functions) });\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\";\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: 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,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;;;ACvEO,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;AAEA,SAAS,SAAS,GAAuF;AACvG,SAAO,IAAI,EAAE,OAAO,EAAE,OAAO,UAAU,cAAc,EAAE,MAAM,GAAG,KAAK,EAAE,IAAI,IAAI;AACjF;AAGO,SAAS,cAAc,WAAwD;AACpF,SAAO,UAAU,IAAI,CAAC,OAAO;AAAA,IAC3B,IAAI,EAAE;AAAA,IACN,SAAS,EAAE;AAAA,IACX,aAAa,EAAE;AAAA,IACf,UAAU,EAAE;AAAA,IACZ,UAAU,SAAS,EAAE,QAAQ;AAAA,IAC7B,WAAW,SAAS,EAAE,SAAS;AAAA,IAC/B,UAAU,EAAE,WACR,EAAE,UAAU,cAAc,EAAE,SAAS,MAAM,GAAG,KAAK,EAAE,SAAS,IAAI,IAClE;AAAA,IACJ,OAAO,EAAE,QACL,EAAE,SAAS,EAAE,MAAM,SAAS,WAAW,cAAc,EAAE,MAAM,OAAO,GAAG,KAAK,EAAE,MAAM,IAAI,IACxF;AAAA,EACN,EAAE;AACJ;;;AC4BO,IAAM,mBAAmB;AAGzB,IAAM,aAAa;AAGnB,IAAM,mBAAmB;;;ACrHhC,yBAA4C;AAGrC,SAAS,KAAK,MAAc,KAAqB;AACtD,aAAO,+BAAW,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,aAAO,oCAAgB,GAAG,CAAC;AAC7B;;;ACJA,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,UAAI,IAAI,WAAW,OAAO;AACxB,cAAM,KAAK,IAAI,QAAQ,gBAAgB;AACvC,cAAMC,OAAM,IAAI,QAAQ,gBAAgB;AACxC,cAAM,QAAQ,OAAO,OAAO,YAAY,KAAK,IAAI,KAAK,IAAI,IAAI,OAAO,EAAE,CAAC,KAAK,IAAI,KAAK;AACtF,YAAI,CAAC,SAAS,OAAOA,SAAQ,YAAY,CAAC,OAAO,IAAcA,MAAK,KAAK,UAAU,GAAG;AACpF,cAAI,UAAU,GAAG,EAAE,IAAI,mBAAmB;AAC1C;AAAA,QACF;AACA,cAAMC,OAAM,KAAK,UAAU,EAAE,WAAW,cAAc,SAAS,EAAE,CAAC;AAClE,YACG,UAAU,KAAK;AAAA,UACd,gBAAgB;AAAA,UAChB,CAAC,gBAAgB,GAAG,KAAKA,MAAK,KAAK,UAAU;AAAA,QAC/C,CAAC,EACA,IAAIA,IAAG;AACV;AAAA,MACF;AAEA,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;;;AC5DO,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,cAAc,SAAS;AAAA,IACpC;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;AAAA;AAAA,EAGA,MAAM,UAAU,MAAoF;AAClG,UAAM,MAAM,MAAM,MAAM,KAAK,KAAK,UAAU,aAAa;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,QAAQ,KAAK,KAAK;AAAA,MACpB,CAAC;AAAA,IACH,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,qBAAqB,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK,CAAC,EAAE;AAClF,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AACF;","names":["message","sig","out"]}
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
@@ -124,4 +124,6 @@ declare class DurableClient {
124
124
  }>;
125
125
  }
126
126
 
127
- 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
@@ -124,4 +124,6 @@ declare class DurableClient {
124
124
  }>;
125
125
  }
126
126
 
127
- 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
@@ -125,6 +125,9 @@ function verify(body, signature, key) {
125
125
  return timingSafeEqual(a, b);
126
126
  }
127
127
 
128
+ // src/version.ts
129
+ var SDK_VERSION = true ? "0.2.1" : "dev";
130
+
128
131
  // src/serve.ts
129
132
  function readBody(req) {
130
133
  return new Promise((resolve, reject) => {
@@ -145,7 +148,10 @@ function serve(functions, opts) {
145
148
  res.writeHead(401).end("invalid signature");
146
149
  return;
147
150
  }
148
- const out2 = JSON.stringify({ functions: buildManifest(functions) });
151
+ const out2 = JSON.stringify({
152
+ functions: buildManifest(functions),
153
+ sdkVersion: SDK_VERSION
154
+ });
149
155
  res.writeHead(200, {
150
156
  "content-type": "application/json",
151
157
  [SIGNATURE_HEADER]: sign(out2, opts.signingKey)
@@ -204,6 +210,7 @@ var DurableClient = class {
204
210
  async sync(functions) {
205
211
  const payload = {
206
212
  url: this.opts.appUrl,
213
+ sdkVersion: SDK_VERSION,
207
214
  functions: buildManifest(functions)
208
215
  };
209
216
  await this.post("/fn/sync", payload);
@@ -229,6 +236,7 @@ var DurableClient = class {
229
236
  };
230
237
  export {
231
238
  DurableClient,
239
+ SDK_VERSION,
232
240
  StepInterrupt,
233
241
  createFunction,
234
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 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 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}\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","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\";\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({ functions: buildManifest(functions) });\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\";\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: 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"]}
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.2.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",