@dicabrio/durable-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @dicabrio/durable-sdk
2
+
3
+ TypeScript SDK for [Durable](https://durable.dicabrio.com) — a self-hosted
4
+ durable-execution engine. Write workflows as ordinary async code; the engine
5
+ memoizes each step and replays your function, so runs survive crashes, deploys,
6
+ retries, and long sleeps.
7
+
8
+ The engine never runs your code. Your app embeds this SDK and exposes one HTTP
9
+ endpoint; the Durable service calls it back, one step per round-trip.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @dicabrio/durable-sdk
15
+ ```
16
+
17
+ Zero runtime dependencies. Ships ESM + CommonJS with type definitions.
18
+
19
+ ## Define a function
20
+
21
+ ```ts
22
+ import { createFunction } from "@dicabrio/durable-sdk";
23
+
24
+ export const onboarding = createFunction({
25
+ id: "onboarding",
26
+ trigger: { event: "user.created" },
27
+ handler: async ({ event, step }) => {
28
+ const user = await step.run("load-user", async () => {
29
+ return { id: event.data.id, email: `${event.data.id}@example.com` };
30
+ });
31
+
32
+ await step.sleep("cooldown", "5s");
33
+
34
+ await step.run("send-email", async () => {
35
+ // ...send the welcome email
36
+ return { sentTo: user.email };
37
+ });
38
+
39
+ return { ok: true };
40
+ },
41
+ });
42
+ ```
43
+
44
+ Each `step.run` result is persisted and memoized: on replay the engine returns
45
+ the stored value instead of re-executing the step.
46
+
47
+ ## Serve the callback and send events
48
+
49
+ ```ts
50
+ import express from "express";
51
+ import { serve, DurableClient } from "@dicabrio/durable-sdk";
52
+ import { onboarding } from "./onboarding.js";
53
+
54
+ const signingKey = process.env.DURABLE_APP_KEY!;
55
+
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 }));
59
+ app.listen(4000);
60
+
61
+ const client = new DurableClient({
62
+ baseUrl: "https://app.durable.dicabrio.com",
63
+ appId: process.env.APP_ID!,
64
+ signingKey,
65
+ appUrl: "https://my-app.example.com/api/durable",
66
+ });
67
+
68
+ // Register functions + callback URL with the service, then fire an event.
69
+ await client.sync([onboarding]);
70
+ await client.send({ name: "user.created", data: { id: "alice" } });
71
+ ```
72
+
73
+ ## Step primitives
74
+
75
+ | Primitive | What it does |
76
+ |---|---|
77
+ | `step.run(id, fn)` | Run once, memoize the result, skip on replay |
78
+ | `step.sleep(id, "5s")` | Durable delay — survives restarts |
79
+ | `step.waitForEvent(id, opts)` | Park the run until a matching event arrives (or times out) |
80
+
81
+ ## License
82
+
83
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DurableClient: () => DurableClient,
24
+ StepInterrupt: () => StepInterrupt,
25
+ createFunction: () => createFunction,
26
+ createStep: () => createStep,
27
+ parseDuration: () => parseDuration,
28
+ runInvocation: () => runInvocation,
29
+ serve: () => serve
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/step.ts
34
+ var StepInterrupt = class extends Error {
35
+ constructor(response) {
36
+ super("durable:step-interrupt");
37
+ this.response = response;
38
+ }
39
+ response;
40
+ };
41
+ var UNITS = {
42
+ ms: 1,
43
+ s: 1e3,
44
+ m: 6e4,
45
+ h: 36e5,
46
+ d: 864e5
47
+ };
48
+ function parseDuration(d) {
49
+ if (typeof d === "number") return d;
50
+ const m = /^(\d+)\s*(ms|s|m|h|d)$/.exec(d.trim());
51
+ if (!m) throw new Error(`invalid duration: ${d}`);
52
+ return Number(m[1]) * UNITS[m[2]];
53
+ }
54
+ function message(err) {
55
+ return err instanceof Error ? err.message : String(err);
56
+ }
57
+ function createStep(memoized) {
58
+ return {
59
+ async run(id, cb) {
60
+ const memo = memoized[id];
61
+ if (memo) {
62
+ if (memo.type !== "run") {
63
+ throw new Error(`step "${id}" already used as a ${memo.type}`);
64
+ }
65
+ return memo.data;
66
+ }
67
+ let data;
68
+ try {
69
+ data = await cb();
70
+ } catch (err) {
71
+ throw new StepInterrupt({
72
+ op: "error",
73
+ id,
74
+ message: message(err),
75
+ retryable: true
76
+ });
77
+ }
78
+ throw new StepInterrupt({ op: "step", id, data });
79
+ },
80
+ async sleep(id, duration) {
81
+ const memo = memoized[id];
82
+ if (memo) {
83
+ if (memo.type !== "sleep") {
84
+ throw new Error(`step "${id}" already used as a ${memo.type}`);
85
+ }
86
+ return;
87
+ }
88
+ const until = new Date(Date.now() + parseDuration(duration)).toISOString();
89
+ throw new StepInterrupt({ op: "sleep", id, until });
90
+ },
91
+ async waitForEvent(id, opts) {
92
+ const memo = memoized[id];
93
+ if (memo) {
94
+ if (memo.type !== "wait") {
95
+ throw new Error(`step "${id}" already used as a ${memo.type}`);
96
+ }
97
+ return memo.data ?? null;
98
+ }
99
+ const until = new Date(Date.now() + parseDuration(opts.timeout)).toISOString();
100
+ throw new StepInterrupt({
101
+ op: "wait",
102
+ id,
103
+ event: opts.event,
104
+ match: opts.match ?? null,
105
+ until
106
+ });
107
+ }
108
+ };
109
+ }
110
+
111
+ // src/function.ts
112
+ function createFunction(def) {
113
+ return def;
114
+ }
115
+ async function runInvocation(fn, req) {
116
+ const step = createStep(req.steps);
117
+ try {
118
+ const data = await fn.handler({ event: req.event, step });
119
+ return { op: "done", data };
120
+ } catch (err) {
121
+ if (err instanceof StepInterrupt) return err.response;
122
+ const message2 = err instanceof Error ? err.message : String(err);
123
+ return { op: "error", id: null, message: message2, retryable: true };
124
+ }
125
+ }
126
+
127
+ // ../shared/src/protocol.ts
128
+ var SIGNATURE_HEADER = "x-durable-signature";
129
+ var APP_HEADER = "x-durable-app";
130
+
131
+ // ../shared/src/hmac.ts
132
+ var import_node_crypto = require("crypto");
133
+ function sign(body, key) {
134
+ return (0, import_node_crypto.createHmac)("sha256", key).update(body).digest("hex");
135
+ }
136
+ function verify(body, signature, key) {
137
+ const expected = sign(body, key);
138
+ const a = Buffer.from(expected);
139
+ const b = Buffer.from(signature);
140
+ if (a.length !== b.length) return false;
141
+ return (0, import_node_crypto.timingSafeEqual)(a, b);
142
+ }
143
+
144
+ // src/serve.ts
145
+ function readBody(req) {
146
+ return new Promise((resolve, reject) => {
147
+ const chunks = [];
148
+ req.on("data", (c) => chunks.push(c));
149
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
150
+ req.on("error", reject);
151
+ });
152
+ }
153
+ function serve(functions, opts) {
154
+ return async (req, res) => {
155
+ try {
156
+ const raw = await readBody(req);
157
+ const sig = req.headers[SIGNATURE_HEADER];
158
+ if (typeof sig !== "string" || !verify(raw, sig, opts.signingKey)) {
159
+ res.writeHead(401).end("invalid signature");
160
+ return;
161
+ }
162
+ const body = JSON.parse(raw);
163
+ const fn = functions.find((f) => f.id === body.functionId);
164
+ const response = fn ? await runInvocation(fn, body) : {
165
+ op: "error",
166
+ id: null,
167
+ message: `unknown function ${body.functionId}`,
168
+ retryable: false
169
+ };
170
+ const out = JSON.stringify(response);
171
+ res.writeHead(200, {
172
+ "content-type": "application/json",
173
+ [SIGNATURE_HEADER]: sign(out, opts.signingKey)
174
+ }).end(out);
175
+ } catch (err) {
176
+ res.writeHead(500).end(err instanceof Error ? err.message : String(err));
177
+ }
178
+ };
179
+ }
180
+
181
+ // src/client.ts
182
+ function toWindow(w) {
183
+ return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : void 0;
184
+ }
185
+ var DurableClient = class {
186
+ constructor(opts) {
187
+ this.opts = opts;
188
+ }
189
+ opts;
190
+ async post(path, payload) {
191
+ const body = JSON.stringify(payload);
192
+ const res = await fetch(this.opts.baseUrl + path, {
193
+ method: "POST",
194
+ headers: {
195
+ "content-type": "application/json",
196
+ [APP_HEADER]: this.opts.appId,
197
+ [SIGNATURE_HEADER]: sign(body, this.opts.signingKey)
198
+ },
199
+ body
200
+ });
201
+ if (!res.ok) {
202
+ throw new Error(
203
+ `durable ${path} failed: ${res.status} ${await res.text()}`
204
+ );
205
+ }
206
+ }
207
+ /** Register this app's functions and callback URL with the service. */
208
+ async sync(functions) {
209
+ const payload = {
210
+ 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
+ }))
221
+ };
222
+ await this.post("/fn/sync", payload);
223
+ }
224
+ /** Send an event to the service. */
225
+ async send(event) {
226
+ await this.post("/e", { name: event.name, data: event.data ?? {} });
227
+ }
228
+ };
229
+ // Annotate the CommonJS export names for ESM import in node:
230
+ 0 && (module.exports = {
231
+ DurableClient,
232
+ StepInterrupt,
233
+ createFunction,
234
+ createStep,
235
+ parseDuration,
236
+ runInvocation,
237
+ serve
238
+ });
239
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,119 @@
1
+ import { DurableEvent, InvokeResponse, MemoizedStep, Trigger, Concurrency, InvokeRequest } from '@durable/shared';
2
+ import { IncomingMessage, ServerResponse } from 'node:http';
3
+
4
+ /**
5
+ * Thrown to unwind the handler the moment it reaches a new operation. The
6
+ * carried response is exactly what the SDK reports to the service. Using an
7
+ * exception means at most one *new* step executes per invocation — everything
8
+ * after it is skipped until the service re-invokes with that step memoized.
9
+ */
10
+ declare class StepInterrupt extends Error {
11
+ readonly response: InvokeResponse;
12
+ constructor(response: InvokeResponse);
13
+ }
14
+ interface WaitForEventOptions {
15
+ /** The event name to wait for. */
16
+ event: string;
17
+ /** Key/value pairs the incoming event.data must contain to match. */
18
+ match?: Record<string, unknown>;
19
+ /** How long to wait before giving up (e.g. "7d", "1h", or ms). */
20
+ timeout: string | number;
21
+ }
22
+ interface StepTools {
23
+ /** Run a side-effecting step once; its result is memoized and replayed. */
24
+ run<T>(id: string, cb: () => Promise<T> | T): Promise<T>;
25
+ /** Pause the run until the duration elapses (e.g. "10s", "2d", or ms). */
26
+ sleep(id: string, duration: string | number): Promise<void>;
27
+ /**
28
+ * Pause the run until a matching event arrives, or the timeout elapses.
29
+ * Resolves with the matched event, or `null` if it timed out.
30
+ */
31
+ waitForEvent(id: string, opts: WaitForEventOptions): Promise<DurableEvent | null>;
32
+ }
33
+ declare function parseDuration(d: string | number): number;
34
+ /** Build the step tools bound to the steps already memoized for this run. */
35
+ declare function createStep(memoized: Record<string, MemoizedStep>): StepTools;
36
+
37
+ interface FunctionContext {
38
+ event: DurableEvent;
39
+ step: StepTools;
40
+ }
41
+ interface DurableFunction {
42
+ id: string;
43
+ /** Triggered by an event name (`{ event }`) or a cron expression (`{ cron }`). */
44
+ trigger: Trigger;
45
+ /** Optional cap on how many runs execute at once (per key, if given). */
46
+ concurrency?: Concurrency;
47
+ /** Higher runs sooner when jobs compete in the queue (default 0). */
48
+ priority?: number;
49
+ /** Spread run starts over time: max `limit` per `period` (excess delayed). */
50
+ throttle?: {
51
+ limit: number;
52
+ period: string | number;
53
+ key?: string;
54
+ };
55
+ /** Cap new runs: max `limit` per `period` (excess dropped). */
56
+ rateLimit?: {
57
+ limit: number;
58
+ period: string | number;
59
+ key?: string;
60
+ };
61
+ /** Collapse rapid events: one run with the LAST event, once quiet for `period`. */
62
+ debounce?: {
63
+ period: string | number;
64
+ key?: string;
65
+ };
66
+ /**
67
+ * Group events: ONE run receives event.data = the whole list, flushed at
68
+ * `maxSize` events or `timeout` after the first (event.name = "$batch").
69
+ */
70
+ batch?: {
71
+ maxSize: number;
72
+ timeout: string | number;
73
+ key?: string;
74
+ };
75
+ handler: (ctx: FunctionContext) => Promise<unknown>;
76
+ }
77
+ declare function createFunction(def: DurableFunction): DurableFunction;
78
+ /**
79
+ * Replay a single invocation: run the handler from the top, returning the next
80
+ * operation it reaches (a new step, a sleep, completion, or an error).
81
+ */
82
+ declare function runInvocation(fn: DurableFunction, req: InvokeRequest): Promise<InvokeResponse>;
83
+
84
+ /**
85
+ * Returns a Node HTTP handler (also usable directly as an Express route) that
86
+ * the Durable service POSTs invocations to. Verifies the HMAC, replays the run,
87
+ * and returns the next operation — itself signed so the service can trust it.
88
+ *
89
+ * Mount this route WITHOUT a JSON body parser; the handler reads the raw stream
90
+ * so it can verify the signature over the exact bytes.
91
+ */
92
+ declare function serve(functions: DurableFunction[], opts: {
93
+ signingKey: string;
94
+ }): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
95
+
96
+ interface DurableClientOptions {
97
+ /** Base URL of the Durable service. */
98
+ baseUrl: string;
99
+ /** This app's id (from the dashboard). */
100
+ appId: string;
101
+ /** This app's signing key (from the dashboard). */
102
+ signingKey: string;
103
+ /** This app's callback base URL, where the service POSTs invocations. */
104
+ appUrl: string;
105
+ }
106
+ declare class DurableClient {
107
+ private readonly opts;
108
+ constructor(opts: DurableClientOptions);
109
+ private post;
110
+ /** Register this app's functions and callback URL with the service. */
111
+ sync(functions: DurableFunction[]): Promise<void>;
112
+ /** Send an event to the service. */
113
+ send(event: {
114
+ name: string;
115
+ data?: unknown;
116
+ }): Promise<void>;
117
+ }
118
+
119
+ export { DurableClient, type DurableClientOptions, type DurableFunction, type FunctionContext, StepInterrupt, type StepTools, type WaitForEventOptions, createFunction, createStep, parseDuration, runInvocation, serve };
@@ -0,0 +1,119 @@
1
+ import { DurableEvent, InvokeResponse, MemoizedStep, Trigger, Concurrency, InvokeRequest } from '@durable/shared';
2
+ import { IncomingMessage, ServerResponse } from 'node:http';
3
+
4
+ /**
5
+ * Thrown to unwind the handler the moment it reaches a new operation. The
6
+ * carried response is exactly what the SDK reports to the service. Using an
7
+ * exception means at most one *new* step executes per invocation — everything
8
+ * after it is skipped until the service re-invokes with that step memoized.
9
+ */
10
+ declare class StepInterrupt extends Error {
11
+ readonly response: InvokeResponse;
12
+ constructor(response: InvokeResponse);
13
+ }
14
+ interface WaitForEventOptions {
15
+ /** The event name to wait for. */
16
+ event: string;
17
+ /** Key/value pairs the incoming event.data must contain to match. */
18
+ match?: Record<string, unknown>;
19
+ /** How long to wait before giving up (e.g. "7d", "1h", or ms). */
20
+ timeout: string | number;
21
+ }
22
+ interface StepTools {
23
+ /** Run a side-effecting step once; its result is memoized and replayed. */
24
+ run<T>(id: string, cb: () => Promise<T> | T): Promise<T>;
25
+ /** Pause the run until the duration elapses (e.g. "10s", "2d", or ms). */
26
+ sleep(id: string, duration: string | number): Promise<void>;
27
+ /**
28
+ * Pause the run until a matching event arrives, or the timeout elapses.
29
+ * Resolves with the matched event, or `null` if it timed out.
30
+ */
31
+ waitForEvent(id: string, opts: WaitForEventOptions): Promise<DurableEvent | null>;
32
+ }
33
+ declare function parseDuration(d: string | number): number;
34
+ /** Build the step tools bound to the steps already memoized for this run. */
35
+ declare function createStep(memoized: Record<string, MemoizedStep>): StepTools;
36
+
37
+ interface FunctionContext {
38
+ event: DurableEvent;
39
+ step: StepTools;
40
+ }
41
+ interface DurableFunction {
42
+ id: string;
43
+ /** Triggered by an event name (`{ event }`) or a cron expression (`{ cron }`). */
44
+ trigger: Trigger;
45
+ /** Optional cap on how many runs execute at once (per key, if given). */
46
+ concurrency?: Concurrency;
47
+ /** Higher runs sooner when jobs compete in the queue (default 0). */
48
+ priority?: number;
49
+ /** Spread run starts over time: max `limit` per `period` (excess delayed). */
50
+ throttle?: {
51
+ limit: number;
52
+ period: string | number;
53
+ key?: string;
54
+ };
55
+ /** Cap new runs: max `limit` per `period` (excess dropped). */
56
+ rateLimit?: {
57
+ limit: number;
58
+ period: string | number;
59
+ key?: string;
60
+ };
61
+ /** Collapse rapid events: one run with the LAST event, once quiet for `period`. */
62
+ debounce?: {
63
+ period: string | number;
64
+ key?: string;
65
+ };
66
+ /**
67
+ * Group events: ONE run receives event.data = the whole list, flushed at
68
+ * `maxSize` events or `timeout` after the first (event.name = "$batch").
69
+ */
70
+ batch?: {
71
+ maxSize: number;
72
+ timeout: string | number;
73
+ key?: string;
74
+ };
75
+ handler: (ctx: FunctionContext) => Promise<unknown>;
76
+ }
77
+ declare function createFunction(def: DurableFunction): DurableFunction;
78
+ /**
79
+ * Replay a single invocation: run the handler from the top, returning the next
80
+ * operation it reaches (a new step, a sleep, completion, or an error).
81
+ */
82
+ declare function runInvocation(fn: DurableFunction, req: InvokeRequest): Promise<InvokeResponse>;
83
+
84
+ /**
85
+ * Returns a Node HTTP handler (also usable directly as an Express route) that
86
+ * the Durable service POSTs invocations to. Verifies the HMAC, replays the run,
87
+ * and returns the next operation — itself signed so the service can trust it.
88
+ *
89
+ * Mount this route WITHOUT a JSON body parser; the handler reads the raw stream
90
+ * so it can verify the signature over the exact bytes.
91
+ */
92
+ declare function serve(functions: DurableFunction[], opts: {
93
+ signingKey: string;
94
+ }): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
95
+
96
+ interface DurableClientOptions {
97
+ /** Base URL of the Durable service. */
98
+ baseUrl: string;
99
+ /** This app's id (from the dashboard). */
100
+ appId: string;
101
+ /** This app's signing key (from the dashboard). */
102
+ signingKey: string;
103
+ /** This app's callback base URL, where the service POSTs invocations. */
104
+ appUrl: string;
105
+ }
106
+ declare class DurableClient {
107
+ private readonly opts;
108
+ constructor(opts: DurableClientOptions);
109
+ private post;
110
+ /** Register this app's functions and callback URL with the service. */
111
+ sync(functions: DurableFunction[]): Promise<void>;
112
+ /** Send an event to the service. */
113
+ send(event: {
114
+ name: string;
115
+ data?: unknown;
116
+ }): Promise<void>;
117
+ }
118
+
119
+ export { DurableClient, type DurableClientOptions, type DurableFunction, type FunctionContext, StepInterrupt, type StepTools, type WaitForEventOptions, createFunction, createStep, parseDuration, runInvocation, serve };
package/dist/index.js ADDED
@@ -0,0 +1,206 @@
1
+ // src/step.ts
2
+ var StepInterrupt = class extends Error {
3
+ constructor(response) {
4
+ super("durable:step-interrupt");
5
+ this.response = response;
6
+ }
7
+ response;
8
+ };
9
+ var UNITS = {
10
+ ms: 1,
11
+ s: 1e3,
12
+ m: 6e4,
13
+ h: 36e5,
14
+ d: 864e5
15
+ };
16
+ function parseDuration(d) {
17
+ if (typeof d === "number") return d;
18
+ const m = /^(\d+)\s*(ms|s|m|h|d)$/.exec(d.trim());
19
+ if (!m) throw new Error(`invalid duration: ${d}`);
20
+ return Number(m[1]) * UNITS[m[2]];
21
+ }
22
+ function message(err) {
23
+ return err instanceof Error ? err.message : String(err);
24
+ }
25
+ function createStep(memoized) {
26
+ return {
27
+ async run(id, cb) {
28
+ const memo = memoized[id];
29
+ if (memo) {
30
+ if (memo.type !== "run") {
31
+ throw new Error(`step "${id}" already used as a ${memo.type}`);
32
+ }
33
+ return memo.data;
34
+ }
35
+ let data;
36
+ try {
37
+ data = await cb();
38
+ } catch (err) {
39
+ throw new StepInterrupt({
40
+ op: "error",
41
+ id,
42
+ message: message(err),
43
+ retryable: true
44
+ });
45
+ }
46
+ throw new StepInterrupt({ op: "step", id, data });
47
+ },
48
+ async sleep(id, duration) {
49
+ const memo = memoized[id];
50
+ if (memo) {
51
+ if (memo.type !== "sleep") {
52
+ throw new Error(`step "${id}" already used as a ${memo.type}`);
53
+ }
54
+ return;
55
+ }
56
+ const until = new Date(Date.now() + parseDuration(duration)).toISOString();
57
+ throw new StepInterrupt({ op: "sleep", id, until });
58
+ },
59
+ async waitForEvent(id, opts) {
60
+ const memo = memoized[id];
61
+ if (memo) {
62
+ if (memo.type !== "wait") {
63
+ throw new Error(`step "${id}" already used as a ${memo.type}`);
64
+ }
65
+ return memo.data ?? null;
66
+ }
67
+ const until = new Date(Date.now() + parseDuration(opts.timeout)).toISOString();
68
+ throw new StepInterrupt({
69
+ op: "wait",
70
+ id,
71
+ event: opts.event,
72
+ match: opts.match ?? null,
73
+ until
74
+ });
75
+ }
76
+ };
77
+ }
78
+
79
+ // src/function.ts
80
+ function createFunction(def) {
81
+ return def;
82
+ }
83
+ async function runInvocation(fn, req) {
84
+ const step = createStep(req.steps);
85
+ try {
86
+ const data = await fn.handler({ event: req.event, step });
87
+ return { op: "done", data };
88
+ } catch (err) {
89
+ if (err instanceof StepInterrupt) return err.response;
90
+ const message2 = err instanceof Error ? err.message : String(err);
91
+ return { op: "error", id: null, message: message2, retryable: true };
92
+ }
93
+ }
94
+
95
+ // ../shared/src/protocol.ts
96
+ var SIGNATURE_HEADER = "x-durable-signature";
97
+ var APP_HEADER = "x-durable-app";
98
+
99
+ // ../shared/src/hmac.ts
100
+ import { createHmac, timingSafeEqual } from "crypto";
101
+ function sign(body, key) {
102
+ return createHmac("sha256", key).update(body).digest("hex");
103
+ }
104
+ function verify(body, signature, key) {
105
+ const expected = sign(body, key);
106
+ const a = Buffer.from(expected);
107
+ const b = Buffer.from(signature);
108
+ if (a.length !== b.length) return false;
109
+ return timingSafeEqual(a, b);
110
+ }
111
+
112
+ // src/serve.ts
113
+ function readBody(req) {
114
+ return new Promise((resolve, reject) => {
115
+ const chunks = [];
116
+ req.on("data", (c) => chunks.push(c));
117
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
118
+ req.on("error", reject);
119
+ });
120
+ }
121
+ function serve(functions, opts) {
122
+ return async (req, res) => {
123
+ try {
124
+ const raw = await readBody(req);
125
+ const sig = req.headers[SIGNATURE_HEADER];
126
+ if (typeof sig !== "string" || !verify(raw, sig, opts.signingKey)) {
127
+ res.writeHead(401).end("invalid signature");
128
+ return;
129
+ }
130
+ const body = JSON.parse(raw);
131
+ const fn = functions.find((f) => f.id === body.functionId);
132
+ const response = fn ? await runInvocation(fn, body) : {
133
+ op: "error",
134
+ id: null,
135
+ message: `unknown function ${body.functionId}`,
136
+ retryable: false
137
+ };
138
+ const out = JSON.stringify(response);
139
+ res.writeHead(200, {
140
+ "content-type": "application/json",
141
+ [SIGNATURE_HEADER]: sign(out, opts.signingKey)
142
+ }).end(out);
143
+ } catch (err) {
144
+ res.writeHead(500).end(err instanceof Error ? err.message : String(err));
145
+ }
146
+ };
147
+ }
148
+
149
+ // src/client.ts
150
+ function toWindow(w) {
151
+ return w ? { limit: w.limit, periodMs: parseDuration(w.period), key: w.key } : void 0;
152
+ }
153
+ var DurableClient = class {
154
+ constructor(opts) {
155
+ this.opts = opts;
156
+ }
157
+ opts;
158
+ async post(path, payload) {
159
+ const body = JSON.stringify(payload);
160
+ const res = await fetch(this.opts.baseUrl + path, {
161
+ method: "POST",
162
+ headers: {
163
+ "content-type": "application/json",
164
+ [APP_HEADER]: this.opts.appId,
165
+ [SIGNATURE_HEADER]: sign(body, this.opts.signingKey)
166
+ },
167
+ body
168
+ });
169
+ if (!res.ok) {
170
+ throw new Error(
171
+ `durable ${path} failed: ${res.status} ${await res.text()}`
172
+ );
173
+ }
174
+ }
175
+ /** Register this app's functions and callback URL with the service. */
176
+ async sync(functions) {
177
+ const payload = {
178
+ 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
+ }))
189
+ };
190
+ await this.post("/fn/sync", payload);
191
+ }
192
+ /** Send an event to the service. */
193
+ async send(event) {
194
+ await this.post("/e", { name: event.name, data: event.data ?? {} });
195
+ }
196
+ };
197
+ export {
198
+ DurableClient,
199
+ StepInterrupt,
200
+ createFunction,
201
+ createStep,
202
+ parseDuration,
203
+ runInvocation,
204
+ serve
205
+ };
206
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":"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","names":["message"]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@dicabrio/durable-sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "TypeScript SDK for the Durable execution engine: define durable functions, sync them, send events, and serve the replay callback.",
6
+ "license": "MIT",
7
+ "author": "dicabrio",
8
+ "homepage": "https://durable.dicabrio.com",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://bitbucket.org/dicabriocom/durable.git",
12
+ "directory": "packages/sdk"
13
+ },
14
+ "keywords": [
15
+ "durable",
16
+ "durable-execution",
17
+ "workflow",
18
+ "step-functions",
19
+ "background-jobs",
20
+ "inngest"
21
+ ],
22
+ "sideEffects": false,
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.cjs"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "prepublishOnly": "tsup"
40
+ },
41
+ "devDependencies": {
42
+ "tsup": "^8.5.1"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }