@nimbusnexus/webhooks-sdk 0.3.0 → 0.4.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 CHANGED
@@ -35,12 +35,24 @@ try {
35
35
  { orderId: "ord_123", total: 4200 },
36
36
  { idempotencyKey: "order-123" }, // makes the publish safe to retry
37
37
  );
38
- console.log(event.eventUid, event.deliveriesCreated);
38
+ console.log(event.eventUid, event.projectId, event.deliveriesCreated);
39
39
  } catch (e) {
40
40
  if (e instanceof WebhookdApiError) console.error(e.statusCode, e.code, e.message);
41
41
  }
42
42
  ```
43
43
 
44
+ **Projects.** A project is addressed by its **id** (`prj_…`), not a slug. Omit `projectId` — as
45
+ above — to publish into your workspace's **default** project; the server resolves it. Pass one only to
46
+ target a specific project:
47
+
48
+ ```ts
49
+ await wh.publish("order.created", { orderId: "ord_123" }, { projectId: "prj_3f9a…" });
50
+ ```
51
+
52
+ The id is opaque and per-workspace, so there is no client-side sentinel for "the default project":
53
+ leaving `projectId` unset omits the field entirely. The response (`event.projectId`) always carries
54
+ the id the event actually landed in.
55
+
44
56
  Transient failures (network errors, `429`, `5xx`) are retried with backoff (a `429` honours
45
57
  `Retry-After`); other `4xx` throw `WebhookdApiError` carrying the `{error:{code,message}}` envelope.
46
58
 
@@ -84,6 +96,10 @@ webhookd returns the original event without re-fanning-out. A record that keeps
84
96
  with capped exponential backoff up to `maxAttempts` (default 10), then parked **dead** (never retried
85
97
  again, retrievable via `store.listDead()`) and passed to the optional `onDead` callback.
86
98
 
99
+ `enqueue` takes the same optional `projectId` as `publish` (omit it for the default project). It is
100
+ persisted on the buffered record as a nullable `project_id` — `null` means "the workspace's default
101
+ project", and `drain` then omits the field from the publish body.
102
+
87
103
  **Built-in stores** — pass one as `store` in `ClientOptions`:
88
104
 
89
105
  | Store | Durable? | Extra needed |
@@ -111,13 +127,15 @@ const wh = new WebhookdClient({ baseUrl: "https://webhooks.example.com", apiKey:
111
127
 
112
128
  // --- Endpoints ---------------------------------------------------------------
113
129
  // Create a receiver — its signing secret is in the response exactly once, so persist it now.
130
+ // Omit `projectId` (here and on listEndpoints) for the workspace's default project.
114
131
  const ep = await wh.createEndpoint("https://your-app.example/webhooks", {
115
132
  subscriptions: [{ match_kind: "prefix", pattern: "order." }],
116
133
  description: "orders service",
117
134
  });
118
135
  const { id: endpointId, secret: signingSecret } = ep;
119
136
 
120
- await wh.listEndpoints({ environment: "prod" }); // { items, next_offset }
137
+ await wh.listEndpoints(); // default project — { items, next_offset }
138
+ await wh.listEndpoints({ projectId: "prj_3f9a…" }); // a specific project, by id
121
139
  await wh.getEndpoint(endpointId);
122
140
 
123
141
  // PATCH — send only the keys you want to change (omitted = unchanged, null = cleared):
package/dist/index.cjs CHANGED
@@ -201,8 +201,7 @@ function rowToRecord(row) {
201
201
  id: row.id,
202
202
  eventType: row.event_type,
203
203
  payload: row.payload,
204
- environment: row.environment,
205
- application: row.application,
204
+ projectId: row.project_id,
206
205
  source: row.source,
207
206
  createdAt: Number(row.created_at),
208
207
  attempts: row.attempts,
@@ -239,8 +238,7 @@ var PostgresStore = class {
239
238
  id TEXT PRIMARY KEY,
240
239
  event_type TEXT NOT NULL,
241
240
  payload JSONB NOT NULL,
242
- environment TEXT NOT NULL,
243
- application TEXT NOT NULL,
241
+ project_id TEXT,
244
242
  source TEXT,
245
243
  sent BOOLEAN NOT NULL DEFAULT FALSE,
246
244
  attempts INTEGER NOT NULL,
@@ -257,13 +255,12 @@ var PostgresStore = class {
257
255
  const pool = await this.ensure();
258
256
  await pool.query(
259
257
  `INSERT INTO ${this.table}
260
- (id, event_type, payload, environment, application, source, sent, attempts, last_error, next_attempt_at, created_at)
261
- VALUES ($1, $2, $3, $4, $5, $6, FALSE, $7, $8, $9, $10)
258
+ (id, event_type, payload, project_id, source, sent, attempts, last_error, next_attempt_at, created_at)
259
+ VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7, $8, $9)
262
260
  ON CONFLICT (id) DO UPDATE SET
263
261
  event_type = EXCLUDED.event_type,
264
262
  payload = EXCLUDED.payload,
265
- environment = EXCLUDED.environment,
266
- application = EXCLUDED.application,
263
+ project_id = EXCLUDED.project_id,
267
264
  source = EXCLUDED.source,
268
265
  sent = EXCLUDED.sent,
269
266
  attempts = EXCLUDED.attempts,
@@ -274,8 +271,7 @@ var PostgresStore = class {
274
271
  record.id,
275
272
  record.eventType,
276
273
  JSON.stringify(record.payload),
277
- record.environment,
278
- record.application,
274
+ record.projectId,
279
275
  record.source,
280
276
  record.attempts,
281
277
  record.lastError,
@@ -432,8 +428,7 @@ function rowToRecord2(row) {
432
428
  id: row.id,
433
429
  eventType: row.event_type,
434
430
  payload: JSON.parse(row.payload),
435
- environment: row.environment,
436
- application: row.application,
431
+ projectId: row.project_id,
437
432
  source: row.source,
438
433
  createdAt: row.created_at,
439
434
  attempts: row.attempts,
@@ -460,12 +455,14 @@ var SqliteStore = class {
460
455
  }
461
456
  this.db = new DatabaseSync(path);
462
457
  this.db.exec(
458
+ // `project_id` is NULLABLE: NULL = the workspace's default project (the publish body simply
459
+ // omits the field). Renamed from the legacy `project` slug column — this store has no
460
+ // schema-versioning mechanism, so a table created by an older SDK build is not upgraded.
463
461
  `CREATE TABLE IF NOT EXISTS webhookd_outbox (
464
462
  id TEXT PRIMARY KEY,
465
463
  event_type TEXT NOT NULL,
466
464
  payload TEXT NOT NULL,
467
- environment TEXT NOT NULL,
468
- application TEXT NOT NULL,
465
+ project_id TEXT,
469
466
  source TEXT,
470
467
  created_at INTEGER NOT NULL,
471
468
  attempts INTEGER NOT NULL,
@@ -477,13 +474,12 @@ var SqliteStore = class {
477
474
  save(record) {
478
475
  this.db.prepare(
479
476
  `INSERT INTO webhookd_outbox
480
- (id, event_type, payload, environment, application, source, created_at, attempts, last_error, next_attempt_at)
481
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
477
+ (id, event_type, payload, project_id, source, created_at, attempts, last_error, next_attempt_at)
478
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
482
479
  ON CONFLICT(id) DO UPDATE SET
483
480
  event_type = excluded.event_type,
484
481
  payload = excluded.payload,
485
- environment = excluded.environment,
486
- application = excluded.application,
482
+ project_id = excluded.project_id,
487
483
  source = excluded.source,
488
484
  created_at = excluded.created_at,
489
485
  attempts = excluded.attempts,
@@ -493,8 +489,7 @@ var SqliteStore = class {
493
489
  record.id,
494
490
  record.eventType,
495
491
  JSON.stringify(record.payload),
496
- record.environment,
497
- record.application,
492
+ record.projectId,
498
493
  record.source,
499
494
  record.createdAt,
500
495
  record.attempts,
@@ -567,10 +562,10 @@ var WebhookdClient = class {
567
562
  async publish(eventType, payload, opts = {}) {
568
563
  const body = {
569
564
  event_type: eventType,
570
- payload,
571
- environment: opts.environment ?? "prod",
572
- application: opts.application ?? "default"
565
+ payload
573
566
  };
567
+ const projectId = normalizeProjectId(opts.projectId);
568
+ if (projectId !== null) body.project_id = projectId;
574
569
  if (opts.source !== void 0) body.source = opts.source;
575
570
  const headers = {};
576
571
  if (opts.idempotencyKey !== void 0) headers["Idempotency-Key"] = opts.idempotencyKey;
@@ -580,8 +575,7 @@ var WebhookdClient = class {
580
575
  id: String(data.id),
581
576
  eventUid: String(data.event_uid),
582
577
  eventType: String(data.event_type),
583
- application: String(data.application ?? "default"),
584
- environment: String(data.environment ?? "prod"),
578
+ projectId: String(data.project_id),
585
579
  deliveriesCreated: Number(data.deliveries_created ?? 0),
586
580
  source: data.source ?? null
587
581
  };
@@ -599,8 +593,8 @@ var WebhookdClient = class {
599
593
  id: opts.idempotencyKey ?? (0, import_node_crypto3.randomUUID)(),
600
594
  eventType,
601
595
  payload,
602
- environment: opts.environment ?? "prod",
603
- application: opts.application ?? "default",
596
+ // null = the workspace's default project (the field is omitted from the publish body on drain).
597
+ projectId: normalizeProjectId(opts.projectId),
604
598
  source: opts.source ?? null,
605
599
  createdAt: now,
606
600
  attempts: 0,
@@ -627,10 +621,9 @@ var WebhookdClient = class {
627
621
  for (const record of rows) {
628
622
  const body = {
629
623
  event_type: record.eventType,
630
- payload: record.payload,
631
- environment: record.environment,
632
- application: record.application
624
+ payload: record.payload
633
625
  };
626
+ if (record.projectId !== null) body.project_id = record.projectId;
634
627
  if (record.source !== null) body.source = record.source;
635
628
  try {
636
629
  await this.request("POST", "/v1/events", {
@@ -697,11 +690,9 @@ var WebhookdClient = class {
697
690
  // ── Endpoints ──────────────────────────────────────────────────────────────
698
691
  /** Create an endpoint. The response includes the signing `secret` exactly once — persist it. */
699
692
  async createEndpoint(url, opts = {}) {
700
- const body = {
701
- url,
702
- environment: opts.environment ?? "prod",
703
- application: opts.application ?? "default"
704
- };
693
+ const body = { url };
694
+ const projectId = normalizeProjectId(opts.projectId);
695
+ if (projectId !== null) body.project_id = projectId;
705
696
  if (opts.subscriptions !== void 0) body.subscriptions = opts.subscriptions;
706
697
  if (opts.secret !== void 0) body.secret = opts.secret;
707
698
  if (opts.maxAttempts !== void 0) body.max_attempts = opts.maxAttempts;
@@ -711,9 +702,11 @@ var WebhookdClient = class {
711
702
  if (opts.deliveryTimeoutMs !== void 0) body.delivery_timeout_ms = opts.deliveryTimeoutMs;
712
703
  return this.requestJson("POST", "/v1/endpoints", { body });
713
704
  }
714
- /** List endpoints for an environment (defaults to `prod`). */
705
+ /** List endpoints for a project. Omit `projectId` for the workspace's default project. */
715
706
  async listEndpoints(opts = {}) {
716
- const query = { environment: opts.environment ?? "prod" };
707
+ const query = {};
708
+ const projectId = normalizeProjectId(opts.projectId);
709
+ if (projectId !== null) query.project_id = projectId;
717
710
  if (opts.offset !== void 0) query.offset = opts.offset;
718
711
  if (opts.limit !== void 0) query.limit = opts.limit;
719
712
  return this.requestJson("GET", "/v1/endpoints", { query });
@@ -836,6 +829,9 @@ var WebhookdClient = class {
836
829
  throw new WebhookdError(`request failed after retries: ${String(lastErr)}`);
837
830
  }
838
831
  };
832
+ function normalizeProjectId(projectId) {
833
+ return projectId === void 0 || projectId === null || projectId === "" ? null : projectId;
834
+ }
839
835
  function backoffMs(attempt) {
840
836
  return Math.min(2e3, 200 * 2 ** attempt);
841
837
  }
@@ -861,7 +857,7 @@ function sleep(ms) {
861
857
  }
862
858
 
863
859
  // src/index.ts
864
- var VERSION = "0.3.0";
860
+ var VERSION = "0.4.0";
865
861
  // Annotate the CommonJS export names for ESM import in node:
866
862
  0 && (module.exports = {
867
863
  DEAD_NEXT_ATTEMPT_MS,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/signature.ts","../src/client.ts","../src/errors.ts","../src/outbox.ts","../src/stores/redis.ts","../src/stores/postgres.ts"],"sourcesContent":["/**\n * @nimbusnexus/webhooks-sdk — the official TypeScript client for NimbusNexus Webhooks.\n *\n * - `verify` — verify an incoming webhook's HMAC signature (for subscribers).\n * - `WebhookdClient` — publish events to webhookd (for producers).\n */\nexport { DEFAULT_TOLERANCE_SECONDS, sign, verify } from \"./signature\";\nexport type { VerifyOptions } from \"./signature\";\nexport { WebhookdClient } from \"./client\";\nexport type {\n ClientOptions,\n PublishOptions,\n WebhookdEvent,\n Subscription,\n Endpoint,\n ApiKey,\n Delivery,\n Page,\n CreateEndpointOptions,\n ListEndpointsOptions,\n EndpointPatch,\n CreateApiKeyOptions,\n ListDeliveriesOptions,\n EnqueueOptions,\n DrainOptions,\n DrainResult,\n} from \"./client\";\nexport { WebhookdApiError, WebhookdError } from \"./errors\";\n\n// Write-first async outbox (durable producer buffering — see `outbox.ts`).\nexport {\n MemoryStore,\n FileStore,\n SqliteStore,\n RedisStore,\n PostgresStore,\n DEAD_NEXT_ATTEMPT_MS,\n isDead,\n} from \"./outbox\";\nexport type { Store, OutboxRecord, RedisStoreOptions, PostgresStoreOptions } from \"./outbox\";\n\n// Keep in lockstep with package.json + the release tag (see publish.yml's bump checklist).\nexport const VERSION = \"0.3.0\";\n","/**\n * Verify webhookd webhook signatures.\n *\n * webhookd signs every delivery as `HMAC_SHA256(secret, \"<timestamp>.\" + rawBody)` (its default\n * timestamped mode) and sends `X-Webhook-Signature: sha256=<hex>` plus `X-Webhook-Timestamp`\n * (unix seconds). A subscriber MUST verify the signature to prove the request genuinely came from\n * webhookd and wasn't tampered with. Mirrors `delivery_core.webhook_outbox.{sign,verify}`.\n */\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst PREFIX = \"sha256=\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300;\n\nfunction toBuffer(value: string | Buffer): Buffer {\n return Buffer.isBuffer(value) ? value : Buffer.from(value, \"utf8\");\n}\n\nfunction signedBytes(rawBody: Buffer, timestamp: number | null): Buffer {\n if (timestamp === null) return rawBody;\n return Buffer.concat([Buffer.from(`${timestamp}.`, \"ascii\"), rawBody]);\n}\n\n/** The `sha256=<hex>` signature webhookd would send for `rawBody` (+ optional timestamp). */\nexport function sign(\n secret: string | Buffer,\n rawBody: string | Buffer,\n timestamp: number | null = null,\n): string {\n const digest = createHmac(\"sha256\", secret)\n .update(signedBytes(toBuffer(rawBody), timestamp))\n .digest(\"hex\");\n return `${PREFIX}${digest}`;\n}\n\nexport interface VerifyOptions {\n /** The `X-Webhook-Timestamp` header value. When given, the replay window is enforced — always pass it. */\n timestamp?: number | string | null;\n /** Replay window in seconds; webhookd's default is 300. */\n toleranceSeconds?: number;\n /** Override the current unix time (for tests). */\n now?: number;\n}\n\n/**\n * Return `true` iff `signature` is a valid webhookd signature for `rawBody`.\n *\n * Pass the EXACT bytes you received as `rawBody` (a string or Buffer) — do not re-serialize the\n * JSON, or the signature won't match. Comparison is constant-time.\n */\nexport function verify(\n secret: string | Buffer,\n rawBody: string | Buffer,\n signature: string,\n opts: VerifyOptions = {},\n): boolean {\n const { timestamp = null, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS, now } = opts;\n const ts = timestamp === null || timestamp === undefined ? null : Number(timestamp);\n if (ts !== null) {\n if (Number.isNaN(ts)) return false;\n const current = now ?? Math.floor(Date.now() / 1000);\n if (Math.abs(current - ts) > toleranceSeconds) return false;\n }\n const expected = Buffer.from(sign(secret, rawBody, ts));\n // X-Webhook-Signature carries one token normally, or several comma-separated tokens during a\n // signing-secret rotation (webhookd dual-sign overlap) — accept if ANY token verifies, so a\n // subscriber configured with EITHER the current or the previous secret keeps working.\n for (const raw of signature.split(\",\")) {\n const token = raw.trim();\n const candidate = Buffer.from(token.startsWith(PREFIX) ? token : `${PREFIX}${token}`);\n // timingSafeEqual requires equal-length buffers; a length mismatch is already a non-match.\n if (expected.length === candidate.length && timingSafeEqual(expected, candidate)) return true;\n }\n return false;\n}\n","/** Typed publish + management client for the webhookd API (zero runtime deps — uses the global `fetch`). */\nimport { randomUUID } from \"node:crypto\";\n\nimport { WebhookdApiError, WebhookdError } from \"./errors\";\nimport { DEAD_NEXT_ATTEMPT_MS, type OutboxRecord, type Store } from \"./outbox\";\n\nconst RETRY_STATUSES = new Set([429, 500, 502, 503, 504]);\n\n/** The published event, as returned by `POST /v1/events` (webhookd's `EventOut`). */\nexport interface WebhookdEvent {\n id: string;\n eventUid: string;\n eventType: string;\n application: string;\n environment: string;\n deliveriesCreated: number;\n source: string | null;\n}\n\n/** A subscription filter attached to an endpoint. */\nexport interface Subscription {\n match_kind: \"exact\" | \"prefix\" | \"suffix\" | \"all\";\n pattern: string;\n}\n\n/**\n * An endpoint, as returned by the management endpoints (webhookd's `EndpointOut`).\n * Fields mirror the server's snake_case wire shape verbatim. `secret` is present ONLY on the\n * create + rotate-secret responses (returned exactly once).\n */\nexport interface Endpoint {\n id: string;\n url: string;\n environment: string;\n application: string;\n status: string;\n subscriptions: Subscription[];\n /** The signing secret — returned ONCE, on create + rotate-secret only. */\n secret?: string;\n max_attempts?: number;\n retry_schedule?: number[];\n description?: string | null;\n custom_headers?: Record<string, string> | null;\n delivery_timeout_ms?: number | null;\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * An API key, as returned by `POST /v1/api-keys` (webhookd's `ApiKeyOut`). `key` is present ONLY on\n * the create response (returned exactly once).\n */\nexport interface ApiKey {\n id: string;\n name: string;\n scope: string;\n /** The raw key — returned ONCE, on create only. */\n key?: string;\n created_at?: string;\n expires_at?: string | null;\n}\n\n/** A delivery attempt record, as returned by the deliveries endpoints (webhookd's `DeliveryOut`). */\nexport interface Delivery {\n id: string;\n endpoint_id: string;\n event_id?: string;\n event_type?: string;\n status: string;\n attempts?: number;\n created_at?: string;\n updated_at?: string;\n}\n\n/** A single page of a list endpoint — items plus the cursor for the next page (`null` at the end). */\nexport interface Page<T> {\n items: T[];\n next_offset: number | null;\n}\n\nexport interface ClientOptions {\n baseUrl: string;\n /** A per-tenant API key (`whsk_…`) or a service token. */\n apiKey: string;\n timeoutMs?: number;\n maxRetries?: number;\n /** Inject a `fetch` implementation (defaults to the global `fetch`); used for tests. */\n fetchImpl?: typeof fetch;\n /**\n * Durable outbox store. When set, {@link WebhookdClient.enqueue} / {@link WebhookdClient.drain}\n * (and the background drainer) become available. Omit to use only the live {@link WebhookdClient.publish}.\n */\n store?: Store;\n /** Max delivery attempts before a record is parked dead (default 10). */\n maxAttempts?: number;\n /** How many records a single {@link WebhookdClient.drain} pulls from the store (default 100). */\n drainBatchLimit?: number;\n /** Invoked once when `drain` parks a record dead (retry budget exhausted). */\n onDead?: (record: OutboxRecord) => void;\n /** Invoked when a background-drainer tick throws (a foreground `drain` still rejects normally). */\n onDrainError?: (error: unknown) => void;\n}\n\nexport interface EnqueueOptions {\n environment?: string;\n application?: string;\n source?: string;\n /** The Idempotency-Key / record id. Defaults to a generated UUID v4. Re-enqueuing the same key is a no-op overwrite. */\n idempotencyKey?: string;\n}\n\nexport interface DrainOptions {\n /** Override the client's `drainBatchLimit` for this call. */\n batchLimit?: number;\n /** Override the client's `maxAttempts` for this call. */\n maxAttempts?: number;\n}\n\nexport interface DrainResult {\n /** Records delivered (2xx) and marked sent this drain. */\n sent: number;\n /** Records that failed this drain (rescheduled or newly parked dead). */\n failed: number;\n /** Records still buffered in the store afterwards (`store.size()`). */\n remaining: number;\n}\n\nexport interface PublishOptions {\n environment?: string;\n application?: string;\n source?: string;\n /** Makes the publish safe to retry — a replay returns the original event without re-fanning-out. */\n idempotencyKey?: string;\n}\n\nexport interface CreateEndpointOptions {\n environment?: string;\n subscriptions?: Subscription[];\n application?: string;\n secret?: string;\n maxAttempts?: number;\n retrySchedule?: number[];\n description?: string;\n customHeaders?: Record<string, string>;\n deliveryTimeoutMs?: number;\n}\n\nexport interface ListEndpointsOptions {\n environment?: string;\n offset?: number;\n limit?: number;\n}\n\n/**\n * A raw PATCH mapping for `updateEndpoint`, keyed with the server's snake_case names. PATCH semantics:\n * an OMITTED key is left unchanged; an explicit `null` CLEARS the field. The object is sent verbatim.\n */\nexport interface EndpointPatch {\n url?: string;\n max_attempts?: number | null;\n retry_schedule?: number[] | null;\n status?: \"enabled\" | \"disabled\";\n description?: string | null;\n custom_headers?: Record<string, string> | null;\n delivery_timeout_ms?: number | null;\n}\n\nexport interface CreateApiKeyOptions {\n name?: string;\n scope?: \"admin\" | \"publish\";\n expiresInDays?: number;\n}\n\nexport interface ListDeliveriesOptions {\n status?: \"queued\" | \"sending\" | \"sent\" | \"failed\" | \"dead\";\n endpointId?: string;\n eventType?: string;\n since?: string | number;\n until?: string | number;\n q?: string;\n offset?: number;\n limit?: number;\n}\n\ninterface RequestOptions {\n /** JSON body to send; omitted for GET/DELETE and bodyless POSTs (no `Content-Type` is set). */\n body?: unknown;\n /** Query params; `undefined`/`null` values are dropped so only provided params are sent. */\n query?: Record<string, unknown>;\n /** Extra request headers (e.g. `Idempotency-Key`). */\n headers?: Record<string, string>;\n}\n\nexport class WebhookdClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n private readonly store?: Store;\n private readonly maxAttempts: number;\n private readonly drainBatchLimit: number;\n private readonly onDead?: (record: OutboxRecord) => void;\n private readonly onDrainError?: (error: unknown) => void;\n private drainTimer?: ReturnType<typeof setInterval>;\n private draining = false;\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n this.apiKey = opts.apiKey;\n this.timeoutMs = opts.timeoutMs ?? 10_000;\n this.maxRetries = opts.maxRetries ?? 2;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.store = opts.store;\n this.maxAttempts = opts.maxAttempts ?? 10;\n this.drainBatchLimit = opts.drainBatchLimit ?? 100;\n this.onDead = opts.onDead;\n this.onDrainError = opts.onDrainError;\n }\n\n async publish(\n eventType: string,\n payload: Record<string, unknown>,\n opts: PublishOptions = {},\n ): Promise<WebhookdEvent> {\n const body: Record<string, unknown> = {\n event_type: eventType,\n payload,\n environment: opts.environment ?? \"prod\",\n application: opts.application ?? \"default\",\n };\n if (opts.source !== undefined) body.source = opts.source;\n\n const headers: Record<string, string> = {};\n if (opts.idempotencyKey !== undefined) headers[\"Idempotency-Key\"] = opts.idempotencyKey;\n\n const resp = await this.request(\"POST\", \"/v1/events\", { body, headers });\n const data = (await resp.json()) as Record<string, unknown>;\n return {\n id: String(data.id),\n eventUid: String(data.event_uid),\n eventType: String(data.event_type),\n application: String(data.application ?? \"default\"),\n environment: String(data.environment ?? \"prod\"),\n deliveriesCreated: Number(data.deliveries_created ?? 0),\n source: (data.source as string | null) ?? null,\n };\n }\n\n // ── Outbox (write-first, durable) ────────────────────────────────────────────\n\n /**\n * Durably buffer an event and return IMMEDIATELY — NO network call. Builds an {@link OutboxRecord}\n * (id = `opts.idempotencyKey` or a fresh UUID v4), saves it to the configured store, and returns the\n * id. Ship it later with {@link drain} (or the background drainer). Requires a `store` in options.\n */\n async enqueue(\n eventType: string,\n payload: Record<string, unknown>,\n opts: EnqueueOptions = {},\n ): Promise<{ id: string }> {\n const store = this.requireStore();\n const now = Date.now();\n const record: OutboxRecord = {\n id: opts.idempotencyKey ?? randomUUID(),\n eventType,\n payload,\n environment: opts.environment ?? \"prod\",\n application: opts.application ?? \"default\",\n source: opts.source ?? null,\n createdAt: now,\n attempts: 0,\n lastError: null,\n nextAttemptAt: now,\n };\n await store.save(record);\n return { id: record.id };\n }\n\n /**\n * Send buffered records to webhookd. Pulls a due batch (oldest first), POSTs each to `/v1/events`\n * with header `Idempotency-Key = record.id` (so a re-drain after a crash never double-publishes —\n * webhookd dedupes). On 2xx the record is marked sent; on error `attempts` is bumped and the record\n * is rescheduled with capped exponential backoff, or parked dead once it hits `maxAttempts` (the\n * `onDead` hook fires, and it is retrievable via `store.listDead()`). Requires a `store`.\n */\n async drain(opts: DrainOptions = {}): Promise<DrainResult> {\n const store = this.requireStore();\n const batchLimit = opts.batchLimit ?? this.drainBatchLimit;\n const maxAttempts = opts.maxAttempts ?? this.maxAttempts;\n const rows = await store.listPending(batchLimit);\n\n let sent = 0;\n let failed = 0;\n for (const record of rows) {\n const body: Record<string, unknown> = {\n event_type: record.eventType,\n payload: record.payload,\n environment: record.environment,\n application: record.application,\n };\n if (record.source !== null) body.source = record.source;\n\n try {\n await this.request(\"POST\", \"/v1/events\", {\n body,\n headers: { \"Idempotency-Key\": record.id },\n });\n await store.markSent(record.id);\n sent += 1;\n } catch (err) {\n const attempts = record.attempts + 1;\n const message = err instanceof Error ? err.message : String(err);\n if (attempts >= maxAttempts) {\n await store.markFailed(record.id, message, attempts, DEAD_NEXT_ATTEMPT_MS);\n this.onDead?.({\n ...record,\n attempts,\n lastError: message,\n nextAttemptAt: DEAD_NEXT_ATTEMPT_MS,\n });\n } else {\n await store.markFailed(record.id, message, attempts, Date.now() + backoffMs(attempts - 1));\n }\n failed += 1;\n }\n }\n\n return { sent, failed, remaining: await store.size() };\n }\n\n /**\n * Start a background loop calling {@link drain} every `intervalSeconds`. Overlapping ticks are\n * skipped while a drain is in flight, and a tick that throws is swallowed (routed to `onDrainError`)\n * so the loop keeps running. The timer is `unref`'d so it never blocks process exit. Idempotent.\n */\n startDrainer(intervalSeconds: number): void {\n this.requireStore();\n if (this.drainTimer) return;\n const ms = Math.max(1, Math.floor(intervalSeconds * 1000));\n this.drainTimer = setInterval(() => void this.drainTick(), ms);\n this.drainTimer.unref?.();\n }\n\n /** Stop the background drainer started by {@link startDrainer}. Safe to call when not running. */\n stopDrainer(): void {\n if (this.drainTimer) {\n clearInterval(this.drainTimer);\n this.drainTimer = undefined;\n }\n }\n\n private async drainTick(): Promise<void> {\n if (this.draining) return; // a previous tick is still draining — skip this one\n this.draining = true;\n try {\n await this.drain();\n } catch (err) {\n this.onDrainError?.(err);\n } finally {\n this.draining = false;\n }\n }\n\n private requireStore(): Store {\n if (!this.store) {\n throw new WebhookdError(\"no outbox store configured — pass `store` in ClientOptions\");\n }\n return this.store;\n }\n\n // ── Endpoints ──────────────────────────────────────────────────────────────\n\n /** Create an endpoint. The response includes the signing `secret` exactly once — persist it. */\n async createEndpoint(url: string, opts: CreateEndpointOptions = {}): Promise<Endpoint> {\n const body: Record<string, unknown> = {\n url,\n environment: opts.environment ?? \"prod\",\n application: opts.application ?? \"default\",\n };\n if (opts.subscriptions !== undefined) body.subscriptions = opts.subscriptions;\n if (opts.secret !== undefined) body.secret = opts.secret;\n if (opts.maxAttempts !== undefined) body.max_attempts = opts.maxAttempts;\n if (opts.retrySchedule !== undefined) body.retry_schedule = opts.retrySchedule;\n if (opts.description !== undefined) body.description = opts.description;\n if (opts.customHeaders !== undefined) body.custom_headers = opts.customHeaders;\n if (opts.deliveryTimeoutMs !== undefined) body.delivery_timeout_ms = opts.deliveryTimeoutMs;\n return this.requestJson<Endpoint>(\"POST\", \"/v1/endpoints\", { body });\n }\n\n /** List endpoints for an environment (defaults to `prod`). */\n async listEndpoints(opts: ListEndpointsOptions = {}): Promise<Page<Endpoint>> {\n const query: Record<string, unknown> = { environment: opts.environment ?? \"prod\" };\n if (opts.offset !== undefined) query.offset = opts.offset;\n if (opts.limit !== undefined) query.limit = opts.limit;\n return this.requestJson<Page<Endpoint>>(\"GET\", \"/v1/endpoints\", { query });\n }\n\n /** Fetch a single endpoint by id. */\n async getEndpoint(id: string): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"GET\", `/v1/endpoints/${encodeURIComponent(id)}`);\n }\n\n /**\n * Partially update an endpoint. `patch` is sent verbatim (snake_case keys): an omitted key is left\n * unchanged, an explicit `null` clears the field. No defaults are injected.\n */\n async updateEndpoint(id: string, patch: EndpointPatch): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"PATCH\", `/v1/endpoints/${encodeURIComponent(id)}`, {\n body: patch,\n });\n }\n\n /** Delete an endpoint. Returns nothing (the server replies `204 No Content`). */\n async deleteEndpoint(id: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/endpoints/${encodeURIComponent(id)}`);\n }\n\n /** Rotate an endpoint's signing secret. The response includes the new `secret` exactly once. */\n async rotateEndpointSecret(id: string): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"POST\", `/v1/endpoints/${encodeURIComponent(id)}/rotate-secret`);\n }\n\n /** Re-enable an endpoint that webhookd auto-disabled after repeated delivery failures. */\n async enableEndpoint(id: string): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"POST\", `/v1/endpoints/${encodeURIComponent(id)}/enable`);\n }\n\n // ── API keys ───────────────────────────────────────────────────────────────\n\n /** Create an API key. The response includes the raw `key` exactly once — persist it. */\n async createApiKey(opts: CreateApiKeyOptions = {}): Promise<ApiKey> {\n const body: Record<string, unknown> = {\n name: opts.name ?? \"\",\n scope: opts.scope ?? \"admin\",\n };\n if (opts.expiresInDays !== undefined) body.expires_in_days = opts.expiresInDays;\n return this.requestJson<ApiKey>(\"POST\", \"/v1/api-keys\", { body });\n }\n\n /** Revoke an API key. Returns nothing (the server replies `204 No Content`). */\n async revokeApiKey(id: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/api-keys/${encodeURIComponent(id)}`);\n }\n\n // ── Deliveries ─────────────────────────────────────────────────────────────\n\n /** List deliveries. Only the filters you provide are sent as query params. */\n async listDeliveries(opts: ListDeliveriesOptions = {}): Promise<Page<Delivery>> {\n const query: Record<string, unknown> = {};\n if (opts.status !== undefined) query.status = opts.status;\n if (opts.endpointId !== undefined) query.endpoint_id = opts.endpointId;\n if (opts.eventType !== undefined) query.event_type = opts.eventType;\n if (opts.since !== undefined) query.since = opts.since;\n if (opts.until !== undefined) query.until = opts.until;\n if (opts.q !== undefined) query.q = opts.q;\n if (opts.offset !== undefined) query.offset = opts.offset;\n if (opts.limit !== undefined) query.limit = opts.limit;\n return this.requestJson<Page<Delivery>>(\"GET\", \"/v1/deliveries\", { query });\n }\n\n /** Re-queue a delivery for another attempt. */\n async redeliver(deliveryId: string): Promise<Delivery> {\n return this.requestJson<Delivery>(\n \"POST\",\n `/v1/deliveries/${encodeURIComponent(deliveryId)}/redeliver`,\n );\n }\n\n // ── Internals ──────────────────────────────────────────────────────────────\n\n /** Issue a request and parse the JSON response as `T`. Never use for 204s (there's no body). */\n private async requestJson<T>(\n method: string,\n path: string,\n opts: RequestOptions = {},\n ): Promise<T> {\n const resp = await this.request(method, path, opts);\n return (await resp.json()) as T;\n }\n\n /**\n * The single request path used by every method: builds the URL (+ query), attaches auth, and runs\n * the shared retry loop (connection errors + 429 honoring Retry-After + 5xx, capped exponential\n * backoff), raising a typed {@link WebhookdApiError} on other 4xx/5xx. The caller reads the body.\n */\n private async request(\n method: string,\n path: string,\n opts: RequestOptions = {},\n ): Promise<Response> {\n let url = `${this.baseUrl}${path}`;\n if (opts.query) {\n const qs = new URLSearchParams();\n for (const [key, value] of Object.entries(opts.query)) {\n if (value !== undefined && value !== null) qs.append(key, String(value));\n }\n const suffix = qs.toString();\n if (suffix) url += `?${suffix}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n ...opts.headers,\n };\n let body: string | undefined;\n if (opts.body !== undefined) {\n body = JSON.stringify(opts.body);\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n let lastErr: unknown;\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n let resp: Response;\n try {\n resp = await this.fetchImpl(url, {\n method,\n headers,\n body,\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (err) {\n lastErr = err;\n if (attempt < this.maxRetries) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw new WebhookdError(`request failed: ${String(err)}`);\n }\n\n if (RETRY_STATUSES.has(resp.status) && attempt < this.maxRetries) {\n await sleep(retryAfterMs(resp) ?? backoffMs(attempt));\n continue;\n }\n if (resp.status >= 400) throw await apiError(resp);\n return resp;\n }\n throw new WebhookdError(`request failed after retries: ${String(lastErr)}`);\n }\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(2000, 200 * 2 ** attempt);\n}\n\nfunction retryAfterMs(resp: Response): number | null {\n const raw = resp.headers.get(\"Retry-After\");\n if (raw && /^\\d+$/.test(raw)) return Number(raw) * 1000;\n return null;\n}\n\nasync function apiError(resp: Response): Promise<WebhookdApiError> {\n // Read the body once; a non-envelope (or non-JSON) error body falls back to the raw text.\n const text = await resp.text();\n let code = \"error\";\n let message = text;\n try {\n const data = JSON.parse(text) as { error?: { code?: string; message?: string } };\n code = data.error?.code ?? code;\n message = data.error?.message ?? message;\n } catch {\n // not JSON — keep the raw text as the message\n }\n return new WebhookdApiError(resp.status, code, message);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/** Base class for all webhookd SDK errors (network failures, etc.). */\nexport class WebhookdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookdError\";\n }\n}\n\n/**\n * A non-2xx response from the webhookd API. Carries the M5a error envelope: a stable machine\n * `code` (e.g. `\"rate_limited\"`, `\"not_found\"`, `\"validation_error\"`) and a human `message`,\n * plus the HTTP `statusCode`.\n */\nexport class WebhookdApiError extends WebhookdError {\n readonly statusCode: number;\n readonly code: string;\n\n constructor(statusCode: number, code: string, message: string) {\n super(`[${statusCode} ${code}] ${message}`);\n this.name = \"WebhookdApiError\";\n this.statusCode = statusCode;\n this.code = code;\n }\n}\n","/**\n * Write-first async outbox for webhookd producers.\n *\n * `enqueue()` writes a {@link OutboxRecord} to a pluggable {@link Store} and returns immediately (no\n * network). `drain()` later ships the buffered records to webhookd, sending each with header\n * `Idempotency-Key = record.id` so a re-drain after a crash / lost response never double-publishes\n * (webhookd dedupes). Delivery is at-least-once — nothing is lost while webhookd is down.\n *\n * This module holds the storage layer: the {@link Store} contract, the {@link OutboxRecord} shape,\n * and the built-in stores. The client-side `enqueue`/`drain`/`startDrainer` live on\n * {@link WebhookdClient} (see `client.ts`), which reuses the shared request + backoff helpers.\n */\nimport { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { createRequire } from \"node:module\";\n\n/**\n * A sentinel `nextAttemptAt` (JS max timestamp) used to park a record that has exhausted its retry\n * budget. It is never `<= now`, so {@link Store.listPending} never returns it again — the record\n * stays durably in the store, flagged dead, retrievable via {@link Store.listDead}.\n */\nexport const DEAD_NEXT_ATTEMPT_MS = 8.64e15;\n\n/**\n * A single buffered event. `id` doubles as the webhookd `Idempotency-Key`, so re-saving the same\n * `id` (an idempotent enqueue) simply overwrites, and re-draining after a crash never double-sends.\n * Timestamps are epoch milliseconds.\n */\nexport interface OutboxRecord {\n /** The Idempotency-Key — caller-supplied or a generated UUID v4. */\n id: string;\n eventType: string;\n payload: Record<string, unknown>;\n environment: string;\n application: string;\n source: string | null;\n /** Epoch ms the record was first enqueued; `listPending` orders by this, oldest first. */\n createdAt: number;\n /** Delivery attempts made so far; starts at 0. */\n attempts: number;\n lastError: string | null;\n /** Epoch ms the record next becomes due; initially `now`. `DEAD_NEXT_ATTEMPT_MS` when dead. */\n nextAttemptAt: number;\n}\n\n/** Whether a record has been parked as dead (retry budget exhausted). */\nexport function isDead(record: OutboxRecord): boolean {\n return record.nextAttemptAt >= DEAD_NEXT_ATTEMPT_MS;\n}\n\n/**\n * A durable buffer of pending events. Implementations may be sync or async; every method returns a\n * value or a promise, and callers always `await`. Built-ins: {@link MemoryStore}, {@link FileStore},\n * {@link SqliteStore}, and (optional-dep) `RedisStore` / `PostgresStore`.\n */\nexport interface Store {\n /** Insert-or-update by `record.id`. Idempotent: the same id overwrites, so enqueue is safe to repeat. */\n save(record: OutboxRecord): void | Promise<void>;\n /** Records not yet sent whose `nextAttemptAt <= now`, oldest first (by `createdAt`), capped at `limit`. */\n listPending(limit: number): OutboxRecord[] | Promise<OutboxRecord[]>;\n /** Remove (or flag sent) a record after a 2xx. */\n markSent(id: string): void | Promise<void>;\n /** Persist a failure + schedule the next retry (or park it dead via `DEAD_NEXT_ATTEMPT_MS`). */\n markFailed(\n id: string,\n error: string,\n attempts: number,\n nextAttemptAt: number,\n ): void | Promise<void>;\n /** Count of records still in the store (i.e. not yet sent), dead ones included. */\n size(): number | Promise<number>;\n /** Records parked dead, oldest first. */\n listDead(limit?: number): OutboxRecord[] | Promise<OutboxRecord[]>;\n /** Release any resources (file handles, DB connections, timers). */\n close(): void | Promise<void>;\n}\n\n/** Deep-ish clone so callers can't mutate a stored record (payload is copied structurally). */\nexport function cloneRecord(record: OutboxRecord): OutboxRecord {\n return { ...record, payload: structuredClone(record.payload) };\n}\n\n/** Oldest-first by `createdAt`, id as a stable tiebreak. */\nfunction byCreatedAt(a: OutboxRecord, b: OutboxRecord): number {\n return a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n}\n\n// ── MemoryStore ────────────────────────────────────────────────────────────────\n\n/** In-process, non-durable store. The default for tests and single-process best-effort buffering. */\nexport class MemoryStore implements Store {\n private readonly records = new Map<string, OutboxRecord>();\n\n save(record: OutboxRecord): void {\n this.records.set(record.id, cloneRecord(record));\n }\n\n listPending(limit: number): OutboxRecord[] {\n const now = Date.now();\n return [...this.records.values()]\n .filter((r) => r.nextAttemptAt <= now)\n .sort(byCreatedAt)\n .slice(0, limit)\n .map(cloneRecord);\n }\n\n markSent(id: string): void {\n this.records.delete(id);\n }\n\n markFailed(id: string, error: string, attempts: number, nextAttemptAt: number): void {\n const record = this.records.get(id);\n if (!record) return;\n record.attempts = attempts;\n record.lastError = error;\n record.nextAttemptAt = nextAttemptAt;\n }\n\n size(): number {\n return this.records.size;\n }\n\n listDead(limit?: number): OutboxRecord[] {\n const dead = [...this.records.values()].filter(isDead).sort(byCreatedAt).map(cloneRecord);\n return limit === undefined ? dead : dead.slice(0, limit);\n }\n\n close(): void {\n this.records.clear();\n }\n}\n\n// ── FileStore ──────────────────────────────────────────────────────────────────\n\n/**\n * Durable store backed by a directory of per-record JSON files with atomic writes (write-tmp +\n * rename). Survives process restarts. `markSent` unlinks the file. Suitable for a single process; it\n * does not coordinate concurrent drainers across processes.\n */\nexport class FileStore implements Store {\n private readonly dir: string;\n\n constructor(dir: string) {\n this.dir = dir;\n mkdirSync(dir, { recursive: true });\n }\n\n /** Map an arbitrary id to a safe filename (ids may be caller-supplied idempotency keys). */\n private pathFor(id: string): string {\n return join(this.dir, `${encodeURIComponent(id)}.json`);\n }\n\n private readAll(): OutboxRecord[] {\n const out: OutboxRecord[] = [];\n for (const name of readdirSync(this.dir)) {\n if (!name.endsWith(\".json\")) continue;\n try {\n out.push(JSON.parse(readFileSync(join(this.dir, name), \"utf8\")) as OutboxRecord);\n } catch {\n // A partially-written or stray file — skip it (atomic rename means a valid file is complete).\n }\n }\n return out;\n }\n\n save(record: OutboxRecord): void {\n const dest = this.pathFor(record.id);\n const tmp = `${dest}.tmp-${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, JSON.stringify(record), \"utf8\");\n renameSync(tmp, dest);\n }\n\n listPending(limit: number): OutboxRecord[] {\n const now = Date.now();\n return this.readAll()\n .filter((r) => r.nextAttemptAt <= now)\n .sort(byCreatedAt)\n .slice(0, limit);\n }\n\n markSent(id: string): void {\n rmSync(this.pathFor(id), { force: true });\n }\n\n markFailed(id: string, error: string, attempts: number, nextAttemptAt: number): void {\n let record: OutboxRecord;\n try {\n record = JSON.parse(readFileSync(this.pathFor(id), \"utf8\")) as OutboxRecord;\n } catch {\n return; // gone — nothing to update\n }\n record.attempts = attempts;\n record.lastError = error;\n record.nextAttemptAt = nextAttemptAt;\n this.save(record);\n }\n\n size(): number {\n return readdirSync(this.dir).filter((n) => n.endsWith(\".json\")).length;\n }\n\n listDead(limit?: number): OutboxRecord[] {\n const dead = this.readAll().filter(isDead).sort(byCreatedAt);\n return limit === undefined ? dead : dead.slice(0, limit);\n }\n\n close(): void {\n // Nothing to release — every operation is a discrete fs call.\n }\n}\n\n// ── SqliteStore ──────────────────────────────────────────────────────────────\n\n/** The subset of `node:sqlite`'s `DatabaseSync` we use — declared locally to keep it a soft dep. */\ninterface SqliteStatement {\n run(...params: unknown[]): unknown;\n get(...params: unknown[]): unknown;\n all(...params: unknown[]): unknown[];\n}\ninterface SqliteDatabase {\n exec(sql: string): void;\n prepare(sql: string): SqliteStatement;\n close(): void;\n}\n\ninterface SqliteRow {\n id: string;\n event_type: string;\n payload: string;\n environment: string;\n application: string;\n source: string | null;\n created_at: number;\n attempts: number;\n last_error: string | null;\n next_attempt_at: number;\n}\n\nfunction rowToRecord(row: SqliteRow): OutboxRecord {\n return {\n id: row.id,\n eventType: row.event_type,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n environment: row.environment,\n application: row.application,\n source: row.source,\n createdAt: row.created_at,\n attempts: row.attempts,\n lastError: row.last_error,\n nextAttemptAt: row.next_attempt_at,\n };\n}\n\n/**\n * Durable, transactional store on the built-in `node:sqlite` (Node >= 22.5 — no native dependency).\n * `markSent` deletes the row. Pass a file path to persist across restarts, or `\":memory:\"` for tests.\n */\nexport class SqliteStore implements Store {\n private readonly db: SqliteDatabase;\n\n constructor(path = \":memory:\") {\n // `node:sqlite` is a built-in but experimental — require Node >= 22.5. Load it synchronously so\n // the constructor stays sync. Prefer `createRequire(import.meta.url)` (works in the ESM build and\n // in source); in the CJS build `import.meta.url` is stubbed, so fall back to the ambient `require`.\n // Guard the whole load for a clear error on older runtimes.\n let DatabaseSync: new (path: string) => SqliteDatabase;\n try {\n let load: NodeRequire;\n try {\n load = createRequire(import.meta.url);\n } catch {\n load = require;\n }\n ({ DatabaseSync } = load(\"node:sqlite\") as {\n DatabaseSync: new (path: string) => SqliteDatabase;\n });\n } catch (err) {\n throw new Error(\n `SqliteStore requires the built-in node:sqlite (Node >= 22.5): ${String(err)}`,\n );\n }\n this.db = new DatabaseSync(path);\n this.db.exec(\n `CREATE TABLE IF NOT EXISTS webhookd_outbox (\n id TEXT PRIMARY KEY,\n event_type TEXT NOT NULL,\n payload TEXT NOT NULL,\n environment TEXT NOT NULL,\n application TEXT NOT NULL,\n source TEXT,\n created_at INTEGER NOT NULL,\n attempts INTEGER NOT NULL,\n last_error TEXT,\n next_attempt_at INTEGER NOT NULL\n )`,\n );\n }\n\n save(record: OutboxRecord): void {\n this.db\n .prepare(\n `INSERT INTO webhookd_outbox\n (id, event_type, payload, environment, application, source, created_at, attempts, last_error, next_attempt_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n event_type = excluded.event_type,\n payload = excluded.payload,\n environment = excluded.environment,\n application = excluded.application,\n source = excluded.source,\n created_at = excluded.created_at,\n attempts = excluded.attempts,\n last_error = excluded.last_error,\n next_attempt_at = excluded.next_attempt_at`,\n )\n .run(\n record.id,\n record.eventType,\n JSON.stringify(record.payload),\n record.environment,\n record.application,\n record.source,\n record.createdAt,\n record.attempts,\n record.lastError,\n record.nextAttemptAt,\n );\n }\n\n listPending(limit: number): OutboxRecord[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM webhookd_outbox\n WHERE next_attempt_at <= ? AND next_attempt_at < ?\n ORDER BY created_at ASC, id ASC\n LIMIT ?`,\n )\n .all(Date.now(), DEAD_NEXT_ATTEMPT_MS, limit) as unknown as SqliteRow[];\n return rows.map(rowToRecord);\n }\n\n markSent(id: string): void {\n this.db.prepare(`DELETE FROM webhookd_outbox WHERE id = ?`).run(id);\n }\n\n markFailed(id: string, error: string, attempts: number, nextAttemptAt: number): void {\n this.db\n .prepare(\n `UPDATE webhookd_outbox SET attempts = ?, last_error = ?, next_attempt_at = ? WHERE id = ?`,\n )\n .run(attempts, error, nextAttemptAt, id);\n }\n\n size(): number {\n const row = this.db.prepare(`SELECT COUNT(*) AS n FROM webhookd_outbox`).get() as { n: number };\n return Number(row.n);\n }\n\n listDead(limit?: number): OutboxRecord[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM webhookd_outbox\n WHERE next_attempt_at >= ?\n ORDER BY created_at ASC, id ASC\n ${limit === undefined ? \"\" : \"LIMIT ?\"}`,\n )\n .all(...(limit === undefined ? [DEAD_NEXT_ATTEMPT_MS] : [DEAD_NEXT_ATTEMPT_MS, limit])) as unknown as SqliteRow[];\n return rows.map(rowToRecord);\n }\n\n close(): void {\n this.db.close();\n }\n}\n\nexport { RedisStore } from \"./stores/redis\";\nexport type { RedisStoreOptions } from \"./stores/redis\";\nexport { PostgresStore } from \"./stores/postgres\";\nexport type { PostgresStoreOptions } from \"./stores/postgres\";\n","/**\n * Redis-backed {@link Store}. Durable ordering via a sorted set scored on `nextAttemptAt` (for due\n * filtering) plus a hash of record bodies keyed by id. The `redis` driver is an OPTIONAL dependency,\n * imported lazily inside {@link RedisStore.ensure} — the SDK core stays zero-runtime-dependency and\n * importing this module never pulls in `redis` unless you actually construct the store.\n */\nimport type { createClient } from \"redis\";\n\nimport { cloneRecord, DEAD_NEXT_ATTEMPT_MS, isDead, type OutboxRecord, type Store } from \"../outbox\";\n\ntype RedisClient = ReturnType<typeof createClient>;\n\nexport interface RedisStoreOptions {\n /** Redis connection URL, e.g. `redis://localhost:6379`. Ignored if `client` is supplied. */\n url?: string;\n /** Reuse an already-created (not necessarily connected) `redis` client instead of `url`. */\n client?: RedisClient;\n /** Namespace for the two keys this store uses. Default `webhookd:outbox`. */\n keyPrefix?: string;\n}\n\nfunction byCreatedAt(a: OutboxRecord, b: OutboxRecord): number {\n return a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n}\n\nexport class RedisStore implements Store {\n private readonly url?: string;\n private readonly keyPrefix: string;\n private client: RedisClient | undefined;\n private connecting: Promise<RedisClient> | undefined;\n\n constructor(opts: RedisStoreOptions = {}) {\n this.url = opts.url;\n this.client = opts.client;\n this.keyPrefix = opts.keyPrefix ?? \"webhookd:outbox\";\n }\n\n private get zsetKey(): string {\n return `${this.keyPrefix}:due`;\n }\n private get hashKey(): string {\n return `${this.keyPrefix}:records`;\n }\n\n /** Lazily import the driver + connect exactly once. */\n private async ensure(): Promise<RedisClient> {\n if (this.client && this.client.isOpen) return this.client;\n if (this.connecting) return this.connecting;\n this.connecting = (async () => {\n if (!this.client) {\n const { createClient: create } = await import(\"redis\");\n this.client = create({ url: this.url });\n }\n if (!this.client.isOpen) await this.client.connect();\n return this.client;\n })();\n try {\n return await this.connecting;\n } finally {\n this.connecting = undefined;\n }\n }\n\n async save(record: OutboxRecord): Promise<void> {\n const client = await this.ensure();\n await client.hSet(this.hashKey, record.id, JSON.stringify(record));\n await client.zAdd(this.zsetKey, { score: record.nextAttemptAt, value: record.id });\n }\n\n async listPending(limit: number): Promise<OutboxRecord[]> {\n const client = await this.ensure();\n // Due = score (nextAttemptAt) <= now; the dead sentinel scores above now, so it's excluded here.\n const ids = await client.zRangeByScore(this.zsetKey, \"-inf\", Date.now());\n return this.loadSorted(client, ids, limit);\n }\n\n async markSent(id: string): Promise<void> {\n const client = await this.ensure();\n await client.hDel(this.hashKey, id);\n await client.zRem(this.zsetKey, id);\n }\n\n async markFailed(\n id: string,\n error: string,\n attempts: number,\n nextAttemptAt: number,\n ): Promise<void> {\n const client = await this.ensure();\n const raw = await client.hGet(this.hashKey, id);\n if (raw === undefined || raw === null) return;\n const record = JSON.parse(raw) as OutboxRecord;\n record.attempts = attempts;\n record.lastError = error;\n record.nextAttemptAt = nextAttemptAt;\n await client.hSet(this.hashKey, id, JSON.stringify(record));\n await client.zAdd(this.zsetKey, { score: nextAttemptAt, value: id });\n }\n\n async size(): Promise<number> {\n const client = await this.ensure();\n return client.hLen(this.hashKey);\n }\n\n async listDead(limit?: number): Promise<OutboxRecord[]> {\n const client = await this.ensure();\n const ids = await client.zRangeByScore(this.zsetKey, DEAD_NEXT_ATTEMPT_MS, \"+inf\");\n const dead = (await this.loadSorted(client, ids, ids.length)).filter(isDead);\n return limit === undefined ? dead : dead.slice(0, limit);\n }\n\n async close(): Promise<void> {\n if (this.client && this.client.isOpen) await this.client.close();\n }\n\n /** Load records for `ids` from the hash, drop any missing, sort oldest-first, cap at `limit`. */\n private async loadSorted(\n client: RedisClient,\n ids: string[],\n limit: number,\n ): Promise<OutboxRecord[]> {\n const records: OutboxRecord[] = [];\n for (const id of ids) {\n const raw = await client.hGet(this.hashKey, id);\n if (raw === undefined || raw === null) continue;\n records.push(cloneRecord(JSON.parse(raw) as OutboxRecord));\n }\n return records.sort(byCreatedAt).slice(0, limit);\n }\n}\n","/**\n * Postgres-backed {@link Store}. A single `webhookd_outbox` table with a `sent` flag; upsert on `id`;\n * pending = `WHERE NOT sent AND next_attempt_at <= now`. The `pg` driver is an OPTIONAL dependency,\n * imported lazily inside {@link PostgresStore.ensure} — importing this module never pulls in `pg`\n * unless you actually construct the store.\n */\nimport type { Pool as PgPool } from \"pg\";\n\nimport { DEAD_NEXT_ATTEMPT_MS, type OutboxRecord, type Store } from \"../outbox\";\n\nexport interface PostgresStoreOptions {\n /** Postgres connection string, e.g. `postgres://user:pass@host:5432/db`. Ignored if `pool` is given. */\n connectionString?: string;\n /** Reuse an existing `pg` Pool instead of `connectionString`. */\n pool?: PgPool;\n /** Table name (must be a plain identifier). Default `webhookd_outbox`. */\n table?: string;\n}\n\ninterface PgRow {\n id: string;\n event_type: string;\n payload: Record<string, unknown>;\n environment: string;\n application: string;\n source: string | null;\n created_at: string;\n attempts: number;\n last_error: string | null;\n next_attempt_at: string;\n}\n\nfunction rowToRecord(row: PgRow): OutboxRecord {\n return {\n id: row.id,\n eventType: row.event_type,\n payload: row.payload,\n environment: row.environment,\n application: row.application,\n source: row.source,\n createdAt: Number(row.created_at),\n attempts: row.attempts,\n lastError: row.last_error,\n nextAttemptAt: Number(row.next_attempt_at),\n };\n}\n\nexport class PostgresStore implements Store {\n private readonly connectionString?: string;\n private readonly table: string;\n private pool: PgPool | undefined;\n private ready: Promise<PgPool> | undefined;\n\n constructor(opts: PostgresStoreOptions = {}) {\n this.connectionString = opts.connectionString;\n this.pool = opts.pool;\n const table = opts.table ?? \"webhookd_outbox\";\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {\n throw new Error(`invalid table name: ${table}`);\n }\n this.table = table;\n }\n\n /** Lazily import the driver, open the pool, and create the table exactly once. */\n private async ensure(): Promise<PgPool> {\n if (this.ready) return this.ready;\n this.ready = (async () => {\n if (!this.pool) {\n const pg = (await import(\"pg\")) as unknown as {\n Pool: new (config?: { connectionString?: string }) => PgPool;\n default?: { Pool: new (config?: { connectionString?: string }) => PgPool };\n };\n const Pool = pg.Pool ?? pg.default?.Pool;\n if (!Pool) throw new Error(\"pg: could not resolve Pool export\");\n this.pool = new Pool({ connectionString: this.connectionString });\n }\n await this.pool.query(\n `CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY,\n event_type TEXT NOT NULL,\n payload JSONB NOT NULL,\n environment TEXT NOT NULL,\n application TEXT NOT NULL,\n source TEXT,\n sent BOOLEAN NOT NULL DEFAULT FALSE,\n attempts INTEGER NOT NULL,\n last_error TEXT,\n next_attempt_at BIGINT NOT NULL,\n created_at BIGINT NOT NULL\n )`,\n );\n return this.pool;\n })();\n return this.ready;\n }\n\n async save(record: OutboxRecord): Promise<void> {\n const pool = await this.ensure();\n await pool.query(\n `INSERT INTO ${this.table}\n (id, event_type, payload, environment, application, source, sent, attempts, last_error, next_attempt_at, created_at)\n VALUES ($1, $2, $3, $4, $5, $6, FALSE, $7, $8, $9, $10)\n ON CONFLICT (id) DO UPDATE SET\n event_type = EXCLUDED.event_type,\n payload = EXCLUDED.payload,\n environment = EXCLUDED.environment,\n application = EXCLUDED.application,\n source = EXCLUDED.source,\n sent = EXCLUDED.sent,\n attempts = EXCLUDED.attempts,\n last_error = EXCLUDED.last_error,\n next_attempt_at = EXCLUDED.next_attempt_at,\n created_at = EXCLUDED.created_at`,\n [\n record.id,\n record.eventType,\n JSON.stringify(record.payload),\n record.environment,\n record.application,\n record.source,\n record.attempts,\n record.lastError,\n record.nextAttemptAt,\n record.createdAt,\n ],\n );\n }\n\n async listPending(limit: number): Promise<OutboxRecord[]> {\n const pool = await this.ensure();\n const res = await pool.query<PgRow>(\n `SELECT * FROM ${this.table}\n WHERE NOT sent AND next_attempt_at <= $1 AND next_attempt_at < $2\n ORDER BY created_at ASC, id ASC\n LIMIT $3`,\n [Date.now(), DEAD_NEXT_ATTEMPT_MS, limit],\n );\n return res.rows.map(rowToRecord);\n }\n\n async markSent(id: string): Promise<void> {\n const pool = await this.ensure();\n await pool.query(`UPDATE ${this.table} SET sent = TRUE WHERE id = $1`, [id]);\n }\n\n async markFailed(\n id: string,\n error: string,\n attempts: number,\n nextAttemptAt: number,\n ): Promise<void> {\n const pool = await this.ensure();\n await pool.query(\n `UPDATE ${this.table} SET attempts = $2, last_error = $3, next_attempt_at = $4 WHERE id = $1`,\n [id, attempts, error, nextAttemptAt],\n );\n }\n\n async size(): Promise<number> {\n const pool = await this.ensure();\n const res = await pool.query<{ n: string }>(\n `SELECT COUNT(*) AS n FROM ${this.table} WHERE NOT sent`,\n );\n return Number(res.rows[0].n);\n }\n\n async listDead(limit?: number): Promise<OutboxRecord[]> {\n const pool = await this.ensure();\n const res = await pool.query<PgRow>(\n `SELECT * FROM ${this.table}\n WHERE NOT sent AND next_attempt_at >= $1\n ORDER BY created_at ASC, id ASC\n ${limit === undefined ? \"\" : \"LIMIT $2\"}`,\n limit === undefined ? [DEAD_NEXT_ATTEMPT_MS] : [DEAD_NEXT_ATTEMPT_MS, limit],\n );\n return res.rows.map(rowToRecord);\n }\n\n async close(): Promise<void> {\n if (this.pool) await this.pool.end();\n }\n}\n"],"mappings":"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","names":["import_node_crypto","import_node_crypto","byCreatedAt","rowToRecord"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/signature.ts","../src/client.ts","../src/errors.ts","../src/outbox.ts","../src/stores/redis.ts","../src/stores/postgres.ts"],"sourcesContent":["/**\n * @nimbusnexus/webhooks-sdk — the official TypeScript client for NimbusNexus Webhooks.\n *\n * - `verify` — verify an incoming webhook's HMAC signature (for subscribers).\n * - `WebhookdClient` — publish events to webhookd (for producers).\n */\nexport { DEFAULT_TOLERANCE_SECONDS, sign, verify } from \"./signature\";\nexport type { VerifyOptions } from \"./signature\";\nexport { WebhookdClient } from \"./client\";\nexport type {\n ClientOptions,\n PublishOptions,\n WebhookdEvent,\n Subscription,\n Endpoint,\n ApiKey,\n Delivery,\n Page,\n CreateEndpointOptions,\n ListEndpointsOptions,\n EndpointPatch,\n CreateApiKeyOptions,\n ListDeliveriesOptions,\n EnqueueOptions,\n DrainOptions,\n DrainResult,\n} from \"./client\";\nexport { WebhookdApiError, WebhookdError } from \"./errors\";\n\n// Write-first async outbox (durable producer buffering — see `outbox.ts`).\nexport {\n MemoryStore,\n FileStore,\n SqliteStore,\n RedisStore,\n PostgresStore,\n DEAD_NEXT_ATTEMPT_MS,\n isDead,\n} from \"./outbox\";\nexport type { Store, OutboxRecord, RedisStoreOptions, PostgresStoreOptions } from \"./outbox\";\n\n// Keep in lockstep with package.json + the release tag (see publish.yml's bump checklist).\nexport const VERSION = \"0.4.0\";\n","/**\n * Verify webhookd webhook signatures.\n *\n * webhookd signs every delivery as `HMAC_SHA256(secret, \"<timestamp>.\" + rawBody)` (its default\n * timestamped mode) and sends `X-Webhook-Signature: sha256=<hex>` plus `X-Webhook-Timestamp`\n * (unix seconds). A subscriber MUST verify the signature to prove the request genuinely came from\n * webhookd and wasn't tampered with. Mirrors `delivery_core.webhook_outbox.{sign,verify}`.\n */\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst PREFIX = \"sha256=\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300;\n\nfunction toBuffer(value: string | Buffer): Buffer {\n return Buffer.isBuffer(value) ? value : Buffer.from(value, \"utf8\");\n}\n\nfunction signedBytes(rawBody: Buffer, timestamp: number | null): Buffer {\n if (timestamp === null) return rawBody;\n return Buffer.concat([Buffer.from(`${timestamp}.`, \"ascii\"), rawBody]);\n}\n\n/** The `sha256=<hex>` signature webhookd would send for `rawBody` (+ optional timestamp). */\nexport function sign(\n secret: string | Buffer,\n rawBody: string | Buffer,\n timestamp: number | null = null,\n): string {\n const digest = createHmac(\"sha256\", secret)\n .update(signedBytes(toBuffer(rawBody), timestamp))\n .digest(\"hex\");\n return `${PREFIX}${digest}`;\n}\n\nexport interface VerifyOptions {\n /** The `X-Webhook-Timestamp` header value. When given, the replay window is enforced — always pass it. */\n timestamp?: number | string | null;\n /** Replay window in seconds; webhookd's default is 300. */\n toleranceSeconds?: number;\n /** Override the current unix time (for tests). */\n now?: number;\n}\n\n/**\n * Return `true` iff `signature` is a valid webhookd signature for `rawBody`.\n *\n * Pass the EXACT bytes you received as `rawBody` (a string or Buffer) — do not re-serialize the\n * JSON, or the signature won't match. Comparison is constant-time.\n */\nexport function verify(\n secret: string | Buffer,\n rawBody: string | Buffer,\n signature: string,\n opts: VerifyOptions = {},\n): boolean {\n const { timestamp = null, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS, now } = opts;\n const ts = timestamp === null || timestamp === undefined ? null : Number(timestamp);\n if (ts !== null) {\n if (Number.isNaN(ts)) return false;\n const current = now ?? Math.floor(Date.now() / 1000);\n if (Math.abs(current - ts) > toleranceSeconds) return false;\n }\n const expected = Buffer.from(sign(secret, rawBody, ts));\n // X-Webhook-Signature carries one token normally, or several comma-separated tokens during a\n // signing-secret rotation (webhookd dual-sign overlap) — accept if ANY token verifies, so a\n // subscriber configured with EITHER the current or the previous secret keeps working.\n for (const raw of signature.split(\",\")) {\n const token = raw.trim();\n const candidate = Buffer.from(token.startsWith(PREFIX) ? token : `${PREFIX}${token}`);\n // timingSafeEqual requires equal-length buffers; a length mismatch is already a non-match.\n if (expected.length === candidate.length && timingSafeEqual(expected, candidate)) return true;\n }\n return false;\n}\n","/** Typed publish + management client for the webhookd API (zero runtime deps — uses the global `fetch`). */\nimport { randomUUID } from \"node:crypto\";\n\nimport { WebhookdApiError, WebhookdError } from \"./errors\";\nimport { DEAD_NEXT_ATTEMPT_MS, type OutboxRecord, type Store } from \"./outbox\";\n\nconst RETRY_STATUSES = new Set([429, 500, 502, 503, 504]);\n\n/** The published event, as returned by `POST /v1/events` (webhookd's `EventOut`). */\nexport interface WebhookdEvent {\n id: string;\n eventUid: string;\n eventType: string;\n /** The id of the project the event was published into (always resolved server-side). */\n projectId: string;\n deliveriesCreated: number;\n source: string | null;\n}\n\n/** A subscription filter attached to an endpoint. */\nexport interface Subscription {\n match_kind: \"exact\" | \"prefix\" | \"suffix\" | \"all\";\n pattern: string;\n}\n\n/**\n * An endpoint, as returned by the management endpoints (webhookd's `EndpointOut`).\n * Fields mirror the server's snake_case wire shape verbatim. `secret` is present ONLY on the\n * create + rotate-secret responses (returned exactly once).\n */\nexport interface Endpoint {\n id: string;\n url: string;\n /** The id of the project the endpoint belongs to. */\n project_id: string;\n status: string;\n subscriptions: Subscription[];\n /** The signing secret — returned ONCE, on create + rotate-secret only. */\n secret?: string;\n max_attempts?: number;\n retry_schedule?: number[];\n description?: string | null;\n custom_headers?: Record<string, string> | null;\n delivery_timeout_ms?: number | null;\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * An API key, as returned by `POST /v1/api-keys` (webhookd's `ApiKeyOut`). `key` is present ONLY on\n * the create response (returned exactly once).\n */\nexport interface ApiKey {\n id: string;\n name: string;\n scope: string;\n /** The raw key — returned ONCE, on create only. */\n key?: string;\n created_at?: string;\n expires_at?: string | null;\n}\n\n/** A delivery attempt record, as returned by the deliveries endpoints (webhookd's `DeliveryOut`). */\nexport interface Delivery {\n id: string;\n endpoint_id: string;\n event_id?: string;\n event_type?: string;\n status: string;\n attempts?: number;\n created_at?: string;\n updated_at?: string;\n}\n\n/** A single page of a list endpoint — items plus the cursor for the next page (`null` at the end). */\nexport interface Page<T> {\n items: T[];\n next_offset: number | null;\n}\n\nexport interface ClientOptions {\n baseUrl: string;\n /** A per-workspace API key (`whsk_…`) or a service token. */\n apiKey: string;\n timeoutMs?: number;\n maxRetries?: number;\n /** Inject a `fetch` implementation (defaults to the global `fetch`); used for tests. */\n fetchImpl?: typeof fetch;\n /**\n * Durable outbox store. When set, {@link WebhookdClient.enqueue} / {@link WebhookdClient.drain}\n * (and the background drainer) become available. Omit to use only the live {@link WebhookdClient.publish}.\n */\n store?: Store;\n /** Max delivery attempts before a record is parked dead (default 10). */\n maxAttempts?: number;\n /** How many records a single {@link WebhookdClient.drain} pulls from the store (default 100). */\n drainBatchLimit?: number;\n /** Invoked once when `drain` parks a record dead (retry budget exhausted). */\n onDead?: (record: OutboxRecord) => void;\n /** Invoked when a background-drainer tick throws (a foreground `drain` still rejects normally). */\n onDrainError?: (error: unknown) => void;\n}\n\nexport interface EnqueueOptions {\n /**\n * The id of the project to publish into (`prj_…`). Omit (or pass an empty string) to target the\n * workspace's default project — the server resolves it, there is no client-side sentinel.\n */\n projectId?: string;\n source?: string;\n /** The Idempotency-Key / record id. Defaults to a generated UUID v4. Re-enqueuing the same key is a no-op overwrite. */\n idempotencyKey?: string;\n}\n\nexport interface DrainOptions {\n /** Override the client's `drainBatchLimit` for this call. */\n batchLimit?: number;\n /** Override the client's `maxAttempts` for this call. */\n maxAttempts?: number;\n}\n\nexport interface DrainResult {\n /** Records delivered (2xx) and marked sent this drain. */\n sent: number;\n /** Records that failed this drain (rescheduled or newly parked dead). */\n failed: number;\n /** Records still buffered in the store afterwards (`store.size()`). */\n remaining: number;\n}\n\nexport interface PublishOptions {\n /**\n * The id of the project to publish into (`prj_…`). Omit (or pass an empty string) to target the\n * workspace's default project — the server resolves it, there is no client-side sentinel.\n */\n projectId?: string;\n source?: string;\n /** Makes the publish safe to retry — a replay returns the original event without re-fanning-out. */\n idempotencyKey?: string;\n}\n\nexport interface CreateEndpointOptions {\n /**\n * The id of the project to create the endpoint in (`prj_…`). Omit (or pass an empty string) for\n * the workspace's default project.\n */\n projectId?: string;\n subscriptions?: Subscription[];\n secret?: string;\n maxAttempts?: number;\n retrySchedule?: number[];\n description?: string;\n customHeaders?: Record<string, string>;\n deliveryTimeoutMs?: number;\n}\n\nexport interface ListEndpointsOptions {\n /**\n * The id of the project to list endpoints for (`prj_…`). Omit (or pass an empty string) for the\n * workspace's default project.\n */\n projectId?: string;\n offset?: number;\n limit?: number;\n}\n\n/**\n * A raw PATCH mapping for `updateEndpoint`, keyed with the server's snake_case names. PATCH semantics:\n * an OMITTED key is left unchanged; an explicit `null` CLEARS the field. The object is sent verbatim.\n */\nexport interface EndpointPatch {\n url?: string;\n max_attempts?: number | null;\n retry_schedule?: number[] | null;\n status?: \"enabled\" | \"disabled\";\n description?: string | null;\n custom_headers?: Record<string, string> | null;\n delivery_timeout_ms?: number | null;\n}\n\nexport interface CreateApiKeyOptions {\n name?: string;\n scope?: \"admin\" | \"publish\";\n expiresInDays?: number;\n}\n\nexport interface ListDeliveriesOptions {\n status?: \"queued\" | \"sending\" | \"sent\" | \"failed\" | \"dead\";\n endpointId?: string;\n eventType?: string;\n since?: string | number;\n until?: string | number;\n q?: string;\n offset?: number;\n limit?: number;\n}\n\ninterface RequestOptions {\n /** JSON body to send; omitted for GET/DELETE and bodyless POSTs (no `Content-Type` is set). */\n body?: unknown;\n /** Query params; `undefined`/`null` values are dropped so only provided params are sent. */\n query?: Record<string, unknown>;\n /** Extra request headers (e.g. `Idempotency-Key`). */\n headers?: Record<string, string>;\n}\n\nexport class WebhookdClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n private readonly store?: Store;\n private readonly maxAttempts: number;\n private readonly drainBatchLimit: number;\n private readonly onDead?: (record: OutboxRecord) => void;\n private readonly onDrainError?: (error: unknown) => void;\n private drainTimer?: ReturnType<typeof setInterval>;\n private draining = false;\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n this.apiKey = opts.apiKey;\n this.timeoutMs = opts.timeoutMs ?? 10_000;\n this.maxRetries = opts.maxRetries ?? 2;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.store = opts.store;\n this.maxAttempts = opts.maxAttempts ?? 10;\n this.drainBatchLimit = opts.drainBatchLimit ?? 100;\n this.onDead = opts.onDead;\n this.onDrainError = opts.onDrainError;\n }\n\n async publish(\n eventType: string,\n payload: Record<string, unknown>,\n opts: PublishOptions = {},\n ): Promise<WebhookdEvent> {\n const body: Record<string, unknown> = {\n event_type: eventType,\n payload,\n };\n // No project id = OMIT the field; the server resolves the workspace's default project.\n const projectId = normalizeProjectId(opts.projectId);\n if (projectId !== null) body.project_id = projectId;\n if (opts.source !== undefined) body.source = opts.source;\n\n const headers: Record<string, string> = {};\n if (opts.idempotencyKey !== undefined) headers[\"Idempotency-Key\"] = opts.idempotencyKey;\n\n const resp = await this.request(\"POST\", \"/v1/events\", { body, headers });\n const data = (await resp.json()) as Record<string, unknown>;\n return {\n id: String(data.id),\n eventUid: String(data.event_uid),\n eventType: String(data.event_type),\n projectId: String(data.project_id),\n deliveriesCreated: Number(data.deliveries_created ?? 0),\n source: (data.source as string | null) ?? null,\n };\n }\n\n // ── Outbox (write-first, durable) ────────────────────────────────────────────\n\n /**\n * Durably buffer an event and return IMMEDIATELY — NO network call. Builds an {@link OutboxRecord}\n * (id = `opts.idempotencyKey` or a fresh UUID v4), saves it to the configured store, and returns the\n * id. Ship it later with {@link drain} (or the background drainer). Requires a `store` in options.\n */\n async enqueue(\n eventType: string,\n payload: Record<string, unknown>,\n opts: EnqueueOptions = {},\n ): Promise<{ id: string }> {\n const store = this.requireStore();\n const now = Date.now();\n const record: OutboxRecord = {\n id: opts.idempotencyKey ?? randomUUID(),\n eventType,\n payload,\n // null = the workspace's default project (the field is omitted from the publish body on drain).\n projectId: normalizeProjectId(opts.projectId),\n source: opts.source ?? null,\n createdAt: now,\n attempts: 0,\n lastError: null,\n nextAttemptAt: now,\n };\n await store.save(record);\n return { id: record.id };\n }\n\n /**\n * Send buffered records to webhookd. Pulls a due batch (oldest first), POSTs each to `/v1/events`\n * with header `Idempotency-Key = record.id` (so a re-drain after a crash never double-publishes —\n * webhookd dedupes). On 2xx the record is marked sent; on error `attempts` is bumped and the record\n * is rescheduled with capped exponential backoff, or parked dead once it hits `maxAttempts` (the\n * `onDead` hook fires, and it is retrievable via `store.listDead()`). Requires a `store`.\n */\n async drain(opts: DrainOptions = {}): Promise<DrainResult> {\n const store = this.requireStore();\n const batchLimit = opts.batchLimit ?? this.drainBatchLimit;\n const maxAttempts = opts.maxAttempts ?? this.maxAttempts;\n const rows = await store.listPending(batchLimit);\n\n let sent = 0;\n let failed = 0;\n for (const record of rows) {\n const body: Record<string, unknown> = {\n event_type: record.eventType,\n payload: record.payload,\n };\n if (record.projectId !== null) body.project_id = record.projectId;\n if (record.source !== null) body.source = record.source;\n\n try {\n await this.request(\"POST\", \"/v1/events\", {\n body,\n headers: { \"Idempotency-Key\": record.id },\n });\n await store.markSent(record.id);\n sent += 1;\n } catch (err) {\n const attempts = record.attempts + 1;\n const message = err instanceof Error ? err.message : String(err);\n if (attempts >= maxAttempts) {\n await store.markFailed(record.id, message, attempts, DEAD_NEXT_ATTEMPT_MS);\n this.onDead?.({\n ...record,\n attempts,\n lastError: message,\n nextAttemptAt: DEAD_NEXT_ATTEMPT_MS,\n });\n } else {\n await store.markFailed(record.id, message, attempts, Date.now() + backoffMs(attempts - 1));\n }\n failed += 1;\n }\n }\n\n return { sent, failed, remaining: await store.size() };\n }\n\n /**\n * Start a background loop calling {@link drain} every `intervalSeconds`. Overlapping ticks are\n * skipped while a drain is in flight, and a tick that throws is swallowed (routed to `onDrainError`)\n * so the loop keeps running. The timer is `unref`'d so it never blocks process exit. Idempotent.\n */\n startDrainer(intervalSeconds: number): void {\n this.requireStore();\n if (this.drainTimer) return;\n const ms = Math.max(1, Math.floor(intervalSeconds * 1000));\n this.drainTimer = setInterval(() => void this.drainTick(), ms);\n this.drainTimer.unref?.();\n }\n\n /** Stop the background drainer started by {@link startDrainer}. Safe to call when not running. */\n stopDrainer(): void {\n if (this.drainTimer) {\n clearInterval(this.drainTimer);\n this.drainTimer = undefined;\n }\n }\n\n private async drainTick(): Promise<void> {\n if (this.draining) return; // a previous tick is still draining — skip this one\n this.draining = true;\n try {\n await this.drain();\n } catch (err) {\n this.onDrainError?.(err);\n } finally {\n this.draining = false;\n }\n }\n\n private requireStore(): Store {\n if (!this.store) {\n throw new WebhookdError(\"no outbox store configured — pass `store` in ClientOptions\");\n }\n return this.store;\n }\n\n // ── Endpoints ──────────────────────────────────────────────────────────────\n\n /** Create an endpoint. The response includes the signing `secret` exactly once — persist it. */\n async createEndpoint(url: string, opts: CreateEndpointOptions = {}): Promise<Endpoint> {\n const body: Record<string, unknown> = { url };\n // No project id = OMIT the field; the server resolves the workspace's default project.\n const projectId = normalizeProjectId(opts.projectId);\n if (projectId !== null) body.project_id = projectId;\n if (opts.subscriptions !== undefined) body.subscriptions = opts.subscriptions;\n if (opts.secret !== undefined) body.secret = opts.secret;\n if (opts.maxAttempts !== undefined) body.max_attempts = opts.maxAttempts;\n if (opts.retrySchedule !== undefined) body.retry_schedule = opts.retrySchedule;\n if (opts.description !== undefined) body.description = opts.description;\n if (opts.customHeaders !== undefined) body.custom_headers = opts.customHeaders;\n if (opts.deliveryTimeoutMs !== undefined) body.delivery_timeout_ms = opts.deliveryTimeoutMs;\n return this.requestJson<Endpoint>(\"POST\", \"/v1/endpoints\", { body });\n }\n\n /** List endpoints for a project. Omit `projectId` for the workspace's default project. */\n async listEndpoints(opts: ListEndpointsOptions = {}): Promise<Page<Endpoint>> {\n const query: Record<string, unknown> = {};\n // No project id = send NO project_id param; the server falls back to the default project.\n const projectId = normalizeProjectId(opts.projectId);\n if (projectId !== null) query.project_id = projectId;\n if (opts.offset !== undefined) query.offset = opts.offset;\n if (opts.limit !== undefined) query.limit = opts.limit;\n return this.requestJson<Page<Endpoint>>(\"GET\", \"/v1/endpoints\", { query });\n }\n\n /** Fetch a single endpoint by id. */\n async getEndpoint(id: string): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"GET\", `/v1/endpoints/${encodeURIComponent(id)}`);\n }\n\n /**\n * Partially update an endpoint. `patch` is sent verbatim (snake_case keys): an omitted key is left\n * unchanged, an explicit `null` clears the field. No defaults are injected.\n */\n async updateEndpoint(id: string, patch: EndpointPatch): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"PATCH\", `/v1/endpoints/${encodeURIComponent(id)}`, {\n body: patch,\n });\n }\n\n /** Delete an endpoint. Returns nothing (the server replies `204 No Content`). */\n async deleteEndpoint(id: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/endpoints/${encodeURIComponent(id)}`);\n }\n\n /** Rotate an endpoint's signing secret. The response includes the new `secret` exactly once. */\n async rotateEndpointSecret(id: string): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"POST\", `/v1/endpoints/${encodeURIComponent(id)}/rotate-secret`);\n }\n\n /** Re-enable an endpoint that webhookd auto-disabled after repeated delivery failures. */\n async enableEndpoint(id: string): Promise<Endpoint> {\n return this.requestJson<Endpoint>(\"POST\", `/v1/endpoints/${encodeURIComponent(id)}/enable`);\n }\n\n // ── API keys ───────────────────────────────────────────────────────────────\n\n /** Create an API key. The response includes the raw `key` exactly once — persist it. */\n async createApiKey(opts: CreateApiKeyOptions = {}): Promise<ApiKey> {\n const body: Record<string, unknown> = {\n name: opts.name ?? \"\",\n scope: opts.scope ?? \"admin\",\n };\n if (opts.expiresInDays !== undefined) body.expires_in_days = opts.expiresInDays;\n return this.requestJson<ApiKey>(\"POST\", \"/v1/api-keys\", { body });\n }\n\n /** Revoke an API key. Returns nothing (the server replies `204 No Content`). */\n async revokeApiKey(id: string): Promise<void> {\n await this.request(\"DELETE\", `/v1/api-keys/${encodeURIComponent(id)}`);\n }\n\n // ── Deliveries ─────────────────────────────────────────────────────────────\n\n /** List deliveries. Only the filters you provide are sent as query params. */\n async listDeliveries(opts: ListDeliveriesOptions = {}): Promise<Page<Delivery>> {\n const query: Record<string, unknown> = {};\n if (opts.status !== undefined) query.status = opts.status;\n if (opts.endpointId !== undefined) query.endpoint_id = opts.endpointId;\n if (opts.eventType !== undefined) query.event_type = opts.eventType;\n if (opts.since !== undefined) query.since = opts.since;\n if (opts.until !== undefined) query.until = opts.until;\n if (opts.q !== undefined) query.q = opts.q;\n if (opts.offset !== undefined) query.offset = opts.offset;\n if (opts.limit !== undefined) query.limit = opts.limit;\n return this.requestJson<Page<Delivery>>(\"GET\", \"/v1/deliveries\", { query });\n }\n\n /** Re-queue a delivery for another attempt. */\n async redeliver(deliveryId: string): Promise<Delivery> {\n return this.requestJson<Delivery>(\n \"POST\",\n `/v1/deliveries/${encodeURIComponent(deliveryId)}/redeliver`,\n );\n }\n\n // ── Internals ──────────────────────────────────────────────────────────────\n\n /** Issue a request and parse the JSON response as `T`. Never use for 204s (there's no body). */\n private async requestJson<T>(\n method: string,\n path: string,\n opts: RequestOptions = {},\n ): Promise<T> {\n const resp = await this.request(method, path, opts);\n return (await resp.json()) as T;\n }\n\n /**\n * The single request path used by every method: builds the URL (+ query), attaches auth, and runs\n * the shared retry loop (connection errors + 429 honoring Retry-After + 5xx, capped exponential\n * backoff), raising a typed {@link WebhookdApiError} on other 4xx/5xx. The caller reads the body.\n */\n private async request(\n method: string,\n path: string,\n opts: RequestOptions = {},\n ): Promise<Response> {\n let url = `${this.baseUrl}${path}`;\n if (opts.query) {\n const qs = new URLSearchParams();\n for (const [key, value] of Object.entries(opts.query)) {\n if (value !== undefined && value !== null) qs.append(key, String(value));\n }\n const suffix = qs.toString();\n if (suffix) url += `?${suffix}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n ...opts.headers,\n };\n let body: string | undefined;\n if (opts.body !== undefined) {\n body = JSON.stringify(opts.body);\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n let lastErr: unknown;\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n let resp: Response;\n try {\n resp = await this.fetchImpl(url, {\n method,\n headers,\n body,\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (err) {\n lastErr = err;\n if (attempt < this.maxRetries) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw new WebhookdError(`request failed: ${String(err)}`);\n }\n\n if (RETRY_STATUSES.has(resp.status) && attempt < this.maxRetries) {\n await sleep(retryAfterMs(resp) ?? backoffMs(attempt));\n continue;\n }\n if (resp.status >= 400) throw await apiError(resp);\n return resp;\n }\n throw new WebhookdError(`request failed after retries: ${String(lastErr)}`);\n }\n}\n\n/**\n * Normalize a caller-supplied project id. A project has no slug — it is addressed by its opaque\n * per-workspace id — and there is NO client-side sentinel for \"the default project\": unset (or empty)\n * returns `null`, which every caller turns into an OMITTED `project_id`, letting the server resolve\n * the workspace's default.\n */\nfunction normalizeProjectId(projectId?: string | null): string | null {\n return projectId === undefined || projectId === null || projectId === \"\" ? null : projectId;\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(2000, 200 * 2 ** attempt);\n}\n\nfunction retryAfterMs(resp: Response): number | null {\n const raw = resp.headers.get(\"Retry-After\");\n if (raw && /^\\d+$/.test(raw)) return Number(raw) * 1000;\n return null;\n}\n\nasync function apiError(resp: Response): Promise<WebhookdApiError> {\n // Read the body once; a non-envelope (or non-JSON) error body falls back to the raw text.\n const text = await resp.text();\n let code = \"error\";\n let message = text;\n try {\n const data = JSON.parse(text) as { error?: { code?: string; message?: string } };\n code = data.error?.code ?? code;\n message = data.error?.message ?? message;\n } catch {\n // not JSON — keep the raw text as the message\n }\n return new WebhookdApiError(resp.status, code, message);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/** Base class for all webhookd SDK errors (network failures, etc.). */\nexport class WebhookdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookdError\";\n }\n}\n\n/**\n * A non-2xx response from the webhookd API. Carries the M5a error envelope: a stable machine\n * `code` (e.g. `\"rate_limited\"`, `\"not_found\"`, `\"validation_error\"`) and a human `message`,\n * plus the HTTP `statusCode`.\n */\nexport class WebhookdApiError extends WebhookdError {\n readonly statusCode: number;\n readonly code: string;\n\n constructor(statusCode: number, code: string, message: string) {\n super(`[${statusCode} ${code}] ${message}`);\n this.name = \"WebhookdApiError\";\n this.statusCode = statusCode;\n this.code = code;\n }\n}\n","/**\n * Write-first async outbox for webhookd producers.\n *\n * `enqueue()` writes a {@link OutboxRecord} to a pluggable {@link Store} and returns immediately (no\n * network). `drain()` later ships the buffered records to webhookd, sending each with header\n * `Idempotency-Key = record.id` so a re-drain after a crash / lost response never double-publishes\n * (webhookd dedupes). Delivery is at-least-once — nothing is lost while webhookd is down.\n *\n * This module holds the storage layer: the {@link Store} contract, the {@link OutboxRecord} shape,\n * and the built-in stores. The client-side `enqueue`/`drain`/`startDrainer` live on\n * {@link WebhookdClient} (see `client.ts`), which reuses the shared request + backoff helpers.\n */\nimport { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { createRequire } from \"node:module\";\n\n/**\n * A sentinel `nextAttemptAt` (JS max timestamp) used to park a record that has exhausted its retry\n * budget. It is never `<= now`, so {@link Store.listPending} never returns it again — the record\n * stays durably in the store, flagged dead, retrievable via {@link Store.listDead}.\n */\nexport const DEAD_NEXT_ATTEMPT_MS = 8.64e15;\n\n/**\n * A single buffered event. `id` doubles as the webhookd `Idempotency-Key`, so re-saving the same\n * `id` (an idempotent enqueue) simply overwrites, and re-draining after a crash never double-sends.\n * Timestamps are epoch milliseconds.\n */\nexport interface OutboxRecord {\n /** The Idempotency-Key — caller-supplied or a generated UUID v4. */\n id: string;\n eventType: string;\n payload: Record<string, unknown>;\n /**\n * The id of the project the event is published into (`prj_…`), or `null` for the workspace's default\n * project. `null` means the `project_id` field is OMITTED from the publish body — the id is opaque\n * and per-workspace, so only the server can resolve the default.\n */\n projectId: string | null;\n source: string | null;\n /** Epoch ms the record was first enqueued; `listPending` orders by this, oldest first. */\n createdAt: number;\n /** Delivery attempts made so far; starts at 0. */\n attempts: number;\n lastError: string | null;\n /** Epoch ms the record next becomes due; initially `now`. `DEAD_NEXT_ATTEMPT_MS` when dead. */\n nextAttemptAt: number;\n}\n\n/** Whether a record has been parked as dead (retry budget exhausted). */\nexport function isDead(record: OutboxRecord): boolean {\n return record.nextAttemptAt >= DEAD_NEXT_ATTEMPT_MS;\n}\n\n/**\n * A durable buffer of pending events. Implementations may be sync or async; every method returns a\n * value or a promise, and callers always `await`. Built-ins: {@link MemoryStore}, {@link FileStore},\n * {@link SqliteStore}, and (optional-dep) `RedisStore` / `PostgresStore`.\n */\nexport interface Store {\n /** Insert-or-update by `record.id`. Idempotent: the same id overwrites, so enqueue is safe to repeat. */\n save(record: OutboxRecord): void | Promise<void>;\n /** Records not yet sent whose `nextAttemptAt <= now`, oldest first (by `createdAt`), capped at `limit`. */\n listPending(limit: number): OutboxRecord[] | Promise<OutboxRecord[]>;\n /** Remove (or flag sent) a record after a 2xx. */\n markSent(id: string): void | Promise<void>;\n /** Persist a failure + schedule the next retry (or park it dead via `DEAD_NEXT_ATTEMPT_MS`). */\n markFailed(\n id: string,\n error: string,\n attempts: number,\n nextAttemptAt: number,\n ): void | Promise<void>;\n /** Count of records still in the store (i.e. not yet sent), dead ones included. */\n size(): number | Promise<number>;\n /** Records parked dead, oldest first. */\n listDead(limit?: number): OutboxRecord[] | Promise<OutboxRecord[]>;\n /** Release any resources (file handles, DB connections, timers). */\n close(): void | Promise<void>;\n}\n\n/** Deep-ish clone so callers can't mutate a stored record (payload is copied structurally). */\nexport function cloneRecord(record: OutboxRecord): OutboxRecord {\n return { ...record, payload: structuredClone(record.payload) };\n}\n\n/** Oldest-first by `createdAt`, id as a stable tiebreak. */\nfunction byCreatedAt(a: OutboxRecord, b: OutboxRecord): number {\n return a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n}\n\n// ── MemoryStore ────────────────────────────────────────────────────────────────\n\n/** In-process, non-durable store. The default for tests and single-process best-effort buffering. */\nexport class MemoryStore implements Store {\n private readonly records = new Map<string, OutboxRecord>();\n\n save(record: OutboxRecord): void {\n this.records.set(record.id, cloneRecord(record));\n }\n\n listPending(limit: number): OutboxRecord[] {\n const now = Date.now();\n return [...this.records.values()]\n .filter((r) => r.nextAttemptAt <= now)\n .sort(byCreatedAt)\n .slice(0, limit)\n .map(cloneRecord);\n }\n\n markSent(id: string): void {\n this.records.delete(id);\n }\n\n markFailed(id: string, error: string, attempts: number, nextAttemptAt: number): void {\n const record = this.records.get(id);\n if (!record) return;\n record.attempts = attempts;\n record.lastError = error;\n record.nextAttemptAt = nextAttemptAt;\n }\n\n size(): number {\n return this.records.size;\n }\n\n listDead(limit?: number): OutboxRecord[] {\n const dead = [...this.records.values()].filter(isDead).sort(byCreatedAt).map(cloneRecord);\n return limit === undefined ? dead : dead.slice(0, limit);\n }\n\n close(): void {\n this.records.clear();\n }\n}\n\n// ── FileStore ──────────────────────────────────────────────────────────────────\n\n/**\n * Durable store backed by a directory of per-record JSON files with atomic writes (write-tmp +\n * rename). Survives process restarts. `markSent` unlinks the file. Suitable for a single process; it\n * does not coordinate concurrent drainers across processes.\n */\nexport class FileStore implements Store {\n private readonly dir: string;\n\n constructor(dir: string) {\n this.dir = dir;\n mkdirSync(dir, { recursive: true });\n }\n\n /** Map an arbitrary id to a safe filename (ids may be caller-supplied idempotency keys). */\n private pathFor(id: string): string {\n return join(this.dir, `${encodeURIComponent(id)}.json`);\n }\n\n private readAll(): OutboxRecord[] {\n const out: OutboxRecord[] = [];\n for (const name of readdirSync(this.dir)) {\n if (!name.endsWith(\".json\")) continue;\n try {\n out.push(JSON.parse(readFileSync(join(this.dir, name), \"utf8\")) as OutboxRecord);\n } catch {\n // A partially-written or stray file — skip it (atomic rename means a valid file is complete).\n }\n }\n return out;\n }\n\n save(record: OutboxRecord): void {\n const dest = this.pathFor(record.id);\n const tmp = `${dest}.tmp-${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, JSON.stringify(record), \"utf8\");\n renameSync(tmp, dest);\n }\n\n listPending(limit: number): OutboxRecord[] {\n const now = Date.now();\n return this.readAll()\n .filter((r) => r.nextAttemptAt <= now)\n .sort(byCreatedAt)\n .slice(0, limit);\n }\n\n markSent(id: string): void {\n rmSync(this.pathFor(id), { force: true });\n }\n\n markFailed(id: string, error: string, attempts: number, nextAttemptAt: number): void {\n let record: OutboxRecord;\n try {\n record = JSON.parse(readFileSync(this.pathFor(id), \"utf8\")) as OutboxRecord;\n } catch {\n return; // gone — nothing to update\n }\n record.attempts = attempts;\n record.lastError = error;\n record.nextAttemptAt = nextAttemptAt;\n this.save(record);\n }\n\n size(): number {\n return readdirSync(this.dir).filter((n) => n.endsWith(\".json\")).length;\n }\n\n listDead(limit?: number): OutboxRecord[] {\n const dead = this.readAll().filter(isDead).sort(byCreatedAt);\n return limit === undefined ? dead : dead.slice(0, limit);\n }\n\n close(): void {\n // Nothing to release — every operation is a discrete fs call.\n }\n}\n\n// ── SqliteStore ──────────────────────────────────────────────────────────────\n\n/** The subset of `node:sqlite`'s `DatabaseSync` we use — declared locally to keep it a soft dep. */\ninterface SqliteStatement {\n run(...params: unknown[]): unknown;\n get(...params: unknown[]): unknown;\n all(...params: unknown[]): unknown[];\n}\ninterface SqliteDatabase {\n exec(sql: string): void;\n prepare(sql: string): SqliteStatement;\n close(): void;\n}\n\ninterface SqliteRow {\n id: string;\n event_type: string;\n payload: string;\n /** NULL = the workspace's default project. */\n project_id: string | null;\n source: string | null;\n created_at: number;\n attempts: number;\n last_error: string | null;\n next_attempt_at: number;\n}\n\nfunction rowToRecord(row: SqliteRow): OutboxRecord {\n return {\n id: row.id,\n eventType: row.event_type,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n projectId: row.project_id,\n source: row.source,\n createdAt: row.created_at,\n attempts: row.attempts,\n lastError: row.last_error,\n nextAttemptAt: row.next_attempt_at,\n };\n}\n\n/**\n * Durable, transactional store on the built-in `node:sqlite` (Node >= 22.5 — no native dependency).\n * `markSent` deletes the row. Pass a file path to persist across restarts, or `\":memory:\"` for tests.\n */\nexport class SqliteStore implements Store {\n private readonly db: SqliteDatabase;\n\n constructor(path = \":memory:\") {\n // `node:sqlite` is a built-in but experimental — require Node >= 22.5. Load it synchronously so\n // the constructor stays sync. Prefer `createRequire(import.meta.url)` (works in the ESM build and\n // in source); in the CJS build `import.meta.url` is stubbed, so fall back to the ambient `require`.\n // Guard the whole load for a clear error on older runtimes.\n let DatabaseSync: new (path: string) => SqliteDatabase;\n try {\n let load: NodeRequire;\n try {\n load = createRequire(import.meta.url);\n } catch {\n load = require;\n }\n ({ DatabaseSync } = load(\"node:sqlite\") as {\n DatabaseSync: new (path: string) => SqliteDatabase;\n });\n } catch (err) {\n throw new Error(\n `SqliteStore requires the built-in node:sqlite (Node >= 22.5): ${String(err)}`,\n );\n }\n this.db = new DatabaseSync(path);\n this.db.exec(\n // `project_id` is NULLABLE: NULL = the workspace's default project (the publish body simply\n // omits the field). Renamed from the legacy `project` slug column — this store has no\n // schema-versioning mechanism, so a table created by an older SDK build is not upgraded.\n `CREATE TABLE IF NOT EXISTS webhookd_outbox (\n id TEXT PRIMARY KEY,\n event_type TEXT NOT NULL,\n payload TEXT NOT NULL,\n project_id TEXT,\n source TEXT,\n created_at INTEGER NOT NULL,\n attempts INTEGER NOT NULL,\n last_error TEXT,\n next_attempt_at INTEGER NOT NULL\n )`,\n );\n }\n\n save(record: OutboxRecord): void {\n this.db\n .prepare(\n `INSERT INTO webhookd_outbox\n (id, event_type, payload, project_id, source, created_at, attempts, last_error, next_attempt_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n event_type = excluded.event_type,\n payload = excluded.payload,\n project_id = excluded.project_id,\n source = excluded.source,\n created_at = excluded.created_at,\n attempts = excluded.attempts,\n last_error = excluded.last_error,\n next_attempt_at = excluded.next_attempt_at`,\n )\n .run(\n record.id,\n record.eventType,\n JSON.stringify(record.payload),\n record.projectId,\n record.source,\n record.createdAt,\n record.attempts,\n record.lastError,\n record.nextAttemptAt,\n );\n }\n\n listPending(limit: number): OutboxRecord[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM webhookd_outbox\n WHERE next_attempt_at <= ? AND next_attempt_at < ?\n ORDER BY created_at ASC, id ASC\n LIMIT ?`,\n )\n .all(Date.now(), DEAD_NEXT_ATTEMPT_MS, limit) as unknown as SqliteRow[];\n return rows.map(rowToRecord);\n }\n\n markSent(id: string): void {\n this.db.prepare(`DELETE FROM webhookd_outbox WHERE id = ?`).run(id);\n }\n\n markFailed(id: string, error: string, attempts: number, nextAttemptAt: number): void {\n this.db\n .prepare(\n `UPDATE webhookd_outbox SET attempts = ?, last_error = ?, next_attempt_at = ? WHERE id = ?`,\n )\n .run(attempts, error, nextAttemptAt, id);\n }\n\n size(): number {\n const row = this.db.prepare(`SELECT COUNT(*) AS n FROM webhookd_outbox`).get() as { n: number };\n return Number(row.n);\n }\n\n listDead(limit?: number): OutboxRecord[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM webhookd_outbox\n WHERE next_attempt_at >= ?\n ORDER BY created_at ASC, id ASC\n ${limit === undefined ? \"\" : \"LIMIT ?\"}`,\n )\n .all(...(limit === undefined ? [DEAD_NEXT_ATTEMPT_MS] : [DEAD_NEXT_ATTEMPT_MS, limit])) as unknown as SqliteRow[];\n return rows.map(rowToRecord);\n }\n\n close(): void {\n this.db.close();\n }\n}\n\nexport { RedisStore } from \"./stores/redis\";\nexport type { RedisStoreOptions } from \"./stores/redis\";\nexport { PostgresStore } from \"./stores/postgres\";\nexport type { PostgresStoreOptions } from \"./stores/postgres\";\n","/**\n * Redis-backed {@link Store}. Durable ordering via a sorted set scored on `nextAttemptAt` (for due\n * filtering) plus a hash of record bodies keyed by id. The `redis` driver is an OPTIONAL dependency,\n * imported lazily inside {@link RedisStore.ensure} — the SDK core stays zero-runtime-dependency and\n * importing this module never pulls in `redis` unless you actually construct the store.\n */\nimport type { createClient } from \"redis\";\n\nimport { cloneRecord, DEAD_NEXT_ATTEMPT_MS, isDead, type OutboxRecord, type Store } from \"../outbox\";\n\ntype RedisClient = ReturnType<typeof createClient>;\n\nexport interface RedisStoreOptions {\n /** Redis connection URL, e.g. `redis://localhost:6379`. Ignored if `client` is supplied. */\n url?: string;\n /** Reuse an already-created (not necessarily connected) `redis` client instead of `url`. */\n client?: RedisClient;\n /** Namespace for the two keys this store uses. Default `webhookd:outbox`. */\n keyPrefix?: string;\n}\n\nfunction byCreatedAt(a: OutboxRecord, b: OutboxRecord): number {\n return a.createdAt - b.createdAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n}\n\nexport class RedisStore implements Store {\n private readonly url?: string;\n private readonly keyPrefix: string;\n private client: RedisClient | undefined;\n private connecting: Promise<RedisClient> | undefined;\n\n constructor(opts: RedisStoreOptions = {}) {\n this.url = opts.url;\n this.client = opts.client;\n this.keyPrefix = opts.keyPrefix ?? \"webhookd:outbox\";\n }\n\n private get zsetKey(): string {\n return `${this.keyPrefix}:due`;\n }\n private get hashKey(): string {\n return `${this.keyPrefix}:records`;\n }\n\n /** Lazily import the driver + connect exactly once. */\n private async ensure(): Promise<RedisClient> {\n if (this.client && this.client.isOpen) return this.client;\n if (this.connecting) return this.connecting;\n this.connecting = (async () => {\n if (!this.client) {\n const { createClient: create } = await import(\"redis\");\n this.client = create({ url: this.url });\n }\n if (!this.client.isOpen) await this.client.connect();\n return this.client;\n })();\n try {\n return await this.connecting;\n } finally {\n this.connecting = undefined;\n }\n }\n\n async save(record: OutboxRecord): Promise<void> {\n const client = await this.ensure();\n await client.hSet(this.hashKey, record.id, JSON.stringify(record));\n await client.zAdd(this.zsetKey, { score: record.nextAttemptAt, value: record.id });\n }\n\n async listPending(limit: number): Promise<OutboxRecord[]> {\n const client = await this.ensure();\n // Due = score (nextAttemptAt) <= now; the dead sentinel scores above now, so it's excluded here.\n const ids = await client.zRangeByScore(this.zsetKey, \"-inf\", Date.now());\n return this.loadSorted(client, ids, limit);\n }\n\n async markSent(id: string): Promise<void> {\n const client = await this.ensure();\n await client.hDel(this.hashKey, id);\n await client.zRem(this.zsetKey, id);\n }\n\n async markFailed(\n id: string,\n error: string,\n attempts: number,\n nextAttemptAt: number,\n ): Promise<void> {\n const client = await this.ensure();\n const raw = await client.hGet(this.hashKey, id);\n if (raw === undefined || raw === null) return;\n const record = JSON.parse(raw) as OutboxRecord;\n record.attempts = attempts;\n record.lastError = error;\n record.nextAttemptAt = nextAttemptAt;\n await client.hSet(this.hashKey, id, JSON.stringify(record));\n await client.zAdd(this.zsetKey, { score: nextAttemptAt, value: id });\n }\n\n async size(): Promise<number> {\n const client = await this.ensure();\n return client.hLen(this.hashKey);\n }\n\n async listDead(limit?: number): Promise<OutboxRecord[]> {\n const client = await this.ensure();\n const ids = await client.zRangeByScore(this.zsetKey, DEAD_NEXT_ATTEMPT_MS, \"+inf\");\n const dead = (await this.loadSorted(client, ids, ids.length)).filter(isDead);\n return limit === undefined ? dead : dead.slice(0, limit);\n }\n\n async close(): Promise<void> {\n if (this.client && this.client.isOpen) await this.client.close();\n }\n\n /** Load records for `ids` from the hash, drop any missing, sort oldest-first, cap at `limit`. */\n private async loadSorted(\n client: RedisClient,\n ids: string[],\n limit: number,\n ): Promise<OutboxRecord[]> {\n const records: OutboxRecord[] = [];\n for (const id of ids) {\n const raw = await client.hGet(this.hashKey, id);\n if (raw === undefined || raw === null) continue;\n records.push(cloneRecord(JSON.parse(raw) as OutboxRecord));\n }\n return records.sort(byCreatedAt).slice(0, limit);\n }\n}\n","/**\n * Postgres-backed {@link Store}. A single `webhookd_outbox` table with a `sent` flag; upsert on `id`;\n * pending = `WHERE NOT sent AND next_attempt_at <= now`. The `pg` driver is an OPTIONAL dependency,\n * imported lazily inside {@link PostgresStore.ensure} — importing this module never pulls in `pg`\n * unless you actually construct the store.\n */\nimport type { Pool as PgPool } from \"pg\";\n\nimport { DEAD_NEXT_ATTEMPT_MS, type OutboxRecord, type Store } from \"../outbox\";\n\nexport interface PostgresStoreOptions {\n /** Postgres connection string, e.g. `postgres://user:pass@host:5432/db`. Ignored if `pool` is given. */\n connectionString?: string;\n /** Reuse an existing `pg` Pool instead of `connectionString`. */\n pool?: PgPool;\n /** Table name (must be a plain identifier). Default `webhookd_outbox`. */\n table?: string;\n}\n\ninterface PgRow {\n id: string;\n event_type: string;\n payload: Record<string, unknown>;\n /** NULL = the workspace's default project. */\n project_id: string | null;\n source: string | null;\n created_at: string;\n attempts: number;\n last_error: string | null;\n next_attempt_at: string;\n}\n\nfunction rowToRecord(row: PgRow): OutboxRecord {\n return {\n id: row.id,\n eventType: row.event_type,\n payload: row.payload,\n projectId: row.project_id,\n source: row.source,\n createdAt: Number(row.created_at),\n attempts: row.attempts,\n lastError: row.last_error,\n nextAttemptAt: Number(row.next_attempt_at),\n };\n}\n\nexport class PostgresStore implements Store {\n private readonly connectionString?: string;\n private readonly table: string;\n private pool: PgPool | undefined;\n private ready: Promise<PgPool> | undefined;\n\n constructor(opts: PostgresStoreOptions = {}) {\n this.connectionString = opts.connectionString;\n this.pool = opts.pool;\n const table = opts.table ?? \"webhookd_outbox\";\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {\n throw new Error(`invalid table name: ${table}`);\n }\n this.table = table;\n }\n\n /** Lazily import the driver, open the pool, and create the table exactly once. */\n private async ensure(): Promise<PgPool> {\n if (this.ready) return this.ready;\n this.ready = (async () => {\n if (!this.pool) {\n const pg = (await import(\"pg\")) as unknown as {\n Pool: new (config?: { connectionString?: string }) => PgPool;\n default?: { Pool: new (config?: { connectionString?: string }) => PgPool };\n };\n const Pool = pg.Pool ?? pg.default?.Pool;\n if (!Pool) throw new Error(\"pg: could not resolve Pool export\");\n this.pool = new Pool({ connectionString: this.connectionString });\n }\n // `project_id` is NULLABLE: NULL = the workspace's default project (the publish body simply\n // omits the field). Renamed from the legacy `project` slug column — this store has no\n // schema-versioning mechanism, so a table created by an older SDK build is not upgraded.\n await this.pool.query(\n `CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY,\n event_type TEXT NOT NULL,\n payload JSONB NOT NULL,\n project_id TEXT,\n source TEXT,\n sent BOOLEAN NOT NULL DEFAULT FALSE,\n attempts INTEGER NOT NULL,\n last_error TEXT,\n next_attempt_at BIGINT NOT NULL,\n created_at BIGINT NOT NULL\n )`,\n );\n return this.pool;\n })();\n return this.ready;\n }\n\n async save(record: OutboxRecord): Promise<void> {\n const pool = await this.ensure();\n await pool.query(\n `INSERT INTO ${this.table}\n (id, event_type, payload, project_id, source, sent, attempts, last_error, next_attempt_at, created_at)\n VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7, $8, $9)\n ON CONFLICT (id) DO UPDATE SET\n event_type = EXCLUDED.event_type,\n payload = EXCLUDED.payload,\n project_id = EXCLUDED.project_id,\n source = EXCLUDED.source,\n sent = EXCLUDED.sent,\n attempts = EXCLUDED.attempts,\n last_error = EXCLUDED.last_error,\n next_attempt_at = EXCLUDED.next_attempt_at,\n created_at = EXCLUDED.created_at`,\n [\n record.id,\n record.eventType,\n JSON.stringify(record.payload),\n record.projectId,\n record.source,\n record.attempts,\n record.lastError,\n record.nextAttemptAt,\n record.createdAt,\n ],\n );\n }\n\n async listPending(limit: number): Promise<OutboxRecord[]> {\n const pool = await this.ensure();\n const res = await pool.query<PgRow>(\n `SELECT * FROM ${this.table}\n WHERE NOT sent AND next_attempt_at <= $1 AND next_attempt_at < $2\n ORDER BY created_at ASC, id ASC\n LIMIT $3`,\n [Date.now(), DEAD_NEXT_ATTEMPT_MS, limit],\n );\n return res.rows.map(rowToRecord);\n }\n\n async markSent(id: string): Promise<void> {\n const pool = await this.ensure();\n await pool.query(`UPDATE ${this.table} SET sent = TRUE WHERE id = $1`, [id]);\n }\n\n async markFailed(\n id: string,\n error: string,\n attempts: number,\n nextAttemptAt: number,\n ): Promise<void> {\n const pool = await this.ensure();\n await pool.query(\n `UPDATE ${this.table} SET attempts = $2, last_error = $3, next_attempt_at = $4 WHERE id = $1`,\n [id, attempts, error, nextAttemptAt],\n );\n }\n\n async size(): Promise<number> {\n const pool = await this.ensure();\n const res = await pool.query<{ n: string }>(\n `SELECT COUNT(*) AS n FROM ${this.table} WHERE NOT sent`,\n );\n return Number(res.rows[0].n);\n }\n\n async listDead(limit?: number): Promise<OutboxRecord[]> {\n const pool = await this.ensure();\n const res = await pool.query<PgRow>(\n `SELECT * FROM ${this.table}\n WHERE NOT sent AND next_attempt_at >= $1\n ORDER BY created_at ASC, id ASC\n ${limit === undefined ? \"\" : \"LIMIT $2\"}`,\n limit === undefined ? [DEAD_NEXT_ATTEMPT_MS] : [DEAD_NEXT_ATTEMPT_MS, limit],\n );\n return res.rows.map(rowToRecord);\n }\n\n async close(): Promise<void> {\n if (this.pool) await this.pool.end();\n }\n}\n"],"mappings":"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","names":["import_node_crypto","import_node_crypto","byCreatedAt","rowToRecord"]}