@nimbusnexus/webhooks-sdk 0.5.1 → 0.5.2

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
@@ -25,9 +25,9 @@ if (!ok) return res.status(400).end(); // forged, tampered, or outside the 300s
25
25
  ## Publish an event (producers)
26
26
 
27
27
  ```ts
28
- import { WebhookdClient, WebhookdApiError } from "@nimbusnexus/webhooks-sdk";
28
+ import { WebhooksClient, WebhooksApiError } from "@nimbusnexus/webhooks-sdk";
29
29
 
30
- const wh = new WebhookdClient({ baseUrl: "https://webhooks.example.com", apiKey: "whsk_…" });
30
+ const wh = new WebhooksClient({ baseUrl: "https://webhooks.example.com", apiKey: "whsk_…" });
31
31
 
32
32
  try {
33
33
  const event = await wh.publish(
@@ -37,7 +37,7 @@ try {
37
37
  );
38
38
  console.log(event.eventUid, event.projectId, event.deliveriesCreated);
39
39
  } catch (e) {
40
- if (e instanceof WebhookdApiError) console.error(e.statusCode, e.code, e.message);
40
+ if (e instanceof WebhooksApiError) console.error(e.statusCode, e.code, e.message);
41
41
  }
42
42
  ```
43
43
 
@@ -54,7 +54,7 @@ leaving `projectId` unset omits the field entirely. The response (`event.project
54
54
  the id the event actually landed in.
55
55
 
56
56
  Transient failures (network errors, `429`, `5xx`) are retried with backoff (a `429` honours
57
- `Retry-After`); other `4xx` throw `WebhookdApiError` carrying the `{error:{code,message}}` envelope.
57
+ `Retry-After`); other `4xx` throw `WebhooksApiError` carrying the `{error:{code,message}}` envelope.
58
58
 
59
59
  ## Outbox / durable buffering (producers)
60
60
 
@@ -66,12 +66,12 @@ or a lost response never double-publishes — webhookd dedupes. Delivery is **at
66
66
  is lost while webhookd is down.
67
67
 
68
68
  ```ts
69
- import { WebhookdClient, SqliteStore } from "@nimbusnexus/webhooks-sdk";
69
+ import { WebhooksClient, SqliteStore } from "@nimbusnexus/webhooks-sdk";
70
70
 
71
71
  // 1. Configure a durable store (survives process restarts; needs Node >= 22.5 for node:sqlite).
72
72
  const store = new SqliteStore("outbox.db");
73
73
 
74
- const wh = new WebhookdClient({
74
+ const wh = new WebhooksClient({
75
75
  baseUrl: "https://webhooks.example.com",
76
76
  apiKey: "whsk_…",
77
77
  store,
@@ -121,9 +121,9 @@ snake_case JSON through typed interfaces (`Endpoint`, `ApiKey`, `Delivery`, `Pag
121
121
  return `{ items, next_offset }`; `deleteEndpoint` / `revokeApiKey` resolve to `void` (a `204`).
122
122
 
123
123
  ```ts
124
- import { WebhookdClient } from "@nimbusnexus/webhooks-sdk";
124
+ import { WebhooksClient } from "@nimbusnexus/webhooks-sdk";
125
125
 
126
- const wh = new WebhookdClient({ baseUrl: "https://webhooks.example.com", apiKey: "whsk_admin_…" });
126
+ const wh = new WebhooksClient({ baseUrl: "https://webhooks.example.com", apiKey: "whsk_admin_…" });
127
127
 
128
128
  // --- Endpoints ---------------------------------------------------------------
129
129
  // Create a receiver — its signing secret is in the response exactly once, so persist it now.
package/dist/index.cjs CHANGED
@@ -857,7 +857,7 @@ function sleep(ms) {
857
857
  }
858
858
 
859
859
  // src/index.ts
860
- var VERSION = "0.5.1";
860
+ var VERSION = "0.5.2";
861
861
  // Annotate the CommonJS export names for ESM import in node:
862
862
  0 && (module.exports = {
863
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 * - `WebhooksClient` — publish events to webhookd (for producers).\n */\nexport { DEFAULT_TOLERANCE_SECONDS, sign, verify } from \"./signature\";\nexport type { VerifyOptions } from \"./signature\";\nexport { WebhooksClient } from \"./client\";\nexport type {\n ClientOptions,\n PublishOptions,\n WebhooksEvent,\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 { WebhooksApiError, WebhooksError } 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.5.1\";\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 { WebhooksApiError, WebhooksError } 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 WebhooksEvent {\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 WebhooksClient.enqueue} / {@link WebhooksClient.drain}\n * (and the background drainer) become available. Omit to use only the live {@link WebhooksClient.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 WebhooksClient.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 WebhooksClient {\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<WebhooksEvent> {\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 WebhooksError(\"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 WebhooksApiError} 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 WebhooksError(`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 WebhooksError(`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<WebhooksApiError> {\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 WebhooksApiError(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 WebhooksError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhooksError\";\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 WebhooksApiError extends WebhooksError {\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 = \"WebhooksApiError\";\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 WebhooksClient} (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"]}
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 * - `WebhooksClient` — publish events to webhookd (for producers).\n */\nexport { DEFAULT_TOLERANCE_SECONDS, sign, verify } from \"./signature\";\nexport type { VerifyOptions } from \"./signature\";\nexport { WebhooksClient } from \"./client\";\nexport type {\n ClientOptions,\n PublishOptions,\n WebhooksEvent,\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 { WebhooksApiError, WebhooksError } 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.5.2\";\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 { WebhooksApiError, WebhooksError } 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 WebhooksEvent {\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 WebhooksClient.enqueue} / {@link WebhooksClient.drain}\n * (and the background drainer) become available. Omit to use only the live {@link WebhooksClient.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 WebhooksClient.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 WebhooksClient {\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<WebhooksEvent> {\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 WebhooksError(\"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 WebhooksApiError} 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 WebhooksError(`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 WebhooksError(`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<WebhooksApiError> {\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 WebhooksApiError(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 WebhooksError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhooksError\";\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 WebhooksApiError extends WebhooksError {\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 = \"WebhooksApiError\";\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 WebhooksClient} (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"]}
package/dist/index.d.cts CHANGED
@@ -460,6 +460,6 @@ declare class WebhooksApiError extends WebhooksError {
460
460
  * - `WebhooksClient` — publish events to webhookd (for producers).
461
461
  */
462
462
 
463
- declare const VERSION = "0.5.1";
463
+ declare const VERSION = "0.5.2";
464
464
 
465
465
  export { type ApiKey, type ClientOptions, type CreateApiKeyOptions, type CreateEndpointOptions, DEAD_NEXT_ATTEMPT_MS, DEFAULT_TOLERANCE_SECONDS, type Delivery, type DrainOptions, type DrainResult, type Endpoint, type EndpointPatch, type EnqueueOptions, FileStore, type ListDeliveriesOptions, type ListEndpointsOptions, MemoryStore, type OutboxRecord, type Page, PostgresStore, type PostgresStoreOptions, type PublishOptions, RedisStore, type RedisStoreOptions, SqliteStore, type Store, type Subscription, VERSION, type VerifyOptions, WebhooksApiError, WebhooksClient, WebhooksError, type WebhooksEvent, isDead, sign, verify };
package/dist/index.d.ts CHANGED
@@ -460,6 +460,6 @@ declare class WebhooksApiError extends WebhooksError {
460
460
  * - `WebhooksClient` — publish events to webhookd (for producers).
461
461
  */
462
462
 
463
- declare const VERSION = "0.5.1";
463
+ declare const VERSION = "0.5.2";
464
464
 
465
465
  export { type ApiKey, type ClientOptions, type CreateApiKeyOptions, type CreateEndpointOptions, DEAD_NEXT_ATTEMPT_MS, DEFAULT_TOLERANCE_SECONDS, type Delivery, type DrainOptions, type DrainResult, type Endpoint, type EndpointPatch, type EnqueueOptions, FileStore, type ListDeliveriesOptions, type ListEndpointsOptions, MemoryStore, type OutboxRecord, type Page, PostgresStore, type PostgresStoreOptions, type PublishOptions, RedisStore, type RedisStoreOptions, SqliteStore, type Store, type Subscription, VERSION, type VerifyOptions, WebhooksApiError, WebhooksClient, WebhooksError, type WebhooksEvent, isDead, sign, verify };
package/dist/index.js CHANGED
@@ -814,7 +814,7 @@ function sleep(ms) {
814
814
  }
815
815
 
816
816
  // src/index.ts
817
- var VERSION = "0.5.1";
817
+ var VERSION = "0.5.2";
818
818
  export {
819
819
  DEAD_NEXT_ATTEMPT_MS,
820
820
  DEFAULT_TOLERANCE_SECONDS,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/signature.ts","../src/client.ts","../src/errors.ts","../src/outbox.ts","../src/stores/redis.ts","../src/stores/postgres.ts","../src/index.ts"],"sourcesContent":["/**\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 { WebhooksApiError, WebhooksError } 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 WebhooksEvent {\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 WebhooksClient.enqueue} / {@link WebhooksClient.drain}\n * (and the background drainer) become available. Omit to use only the live {@link WebhooksClient.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 WebhooksClient.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 WebhooksClient {\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<WebhooksEvent> {\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 WebhooksError(\"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 WebhooksApiError} 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 WebhooksError(`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 WebhooksError(`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<WebhooksApiError> {\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 WebhooksApiError(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 WebhooksError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhooksError\";\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 WebhooksApiError extends WebhooksError {\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 = \"WebhooksApiError\";\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 WebhooksClient} (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","/**\n * @nimbusnexus/webhooks-sdk — the official TypeScript client for NimbusNexus Webhooks.\n *\n * - `verify` — verify an incoming webhook's HMAC signature (for subscribers).\n * - `WebhooksClient` — publish events to webhookd (for producers).\n */\nexport { DEFAULT_TOLERANCE_SECONDS, sign, verify } from \"./signature\";\nexport type { VerifyOptions } from \"./signature\";\nexport { WebhooksClient } from \"./client\";\nexport type {\n ClientOptions,\n PublishOptions,\n WebhooksEvent,\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 { WebhooksApiError, WebhooksError } 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.5.1\";\n"],"mappings":";;;;;;;;AAQA,SAAS,YAAY,uBAAuB;AAE5C,IAAM,SAAS;AACR,IAAM,4BAA4B;AAEzC,SAAS,SAAS,OAAgC;AAChD,SAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,OAAO,MAAM;AACnE;AAEA,SAAS,YAAY,SAAiB,WAAkC;AACtE,MAAI,cAAc,KAAM,QAAO;AAC/B,SAAO,OAAO,OAAO,CAAC,OAAO,KAAK,GAAG,SAAS,KAAK,OAAO,GAAG,OAAO,CAAC;AACvE;AAGO,SAAS,KACd,QACA,SACA,YAA2B,MACnB;AACR,QAAM,SAAS,WAAW,UAAU,MAAM,EACvC,OAAO,YAAY,SAAS,OAAO,GAAG,SAAS,CAAC,EAChD,OAAO,KAAK;AACf,SAAO,GAAG,MAAM,GAAG,MAAM;AAC3B;AAiBO,SAAS,OACd,QACA,SACA,WACA,OAAsB,CAAC,GACd;AACT,QAAM,EAAE,YAAY,MAAM,mBAAmB,2BAA2B,IAAI,IAAI;AAChF,QAAM,KAAK,cAAc,QAAQ,cAAc,SAAY,OAAO,OAAO,SAAS;AAClF,MAAI,OAAO,MAAM;AACf,QAAI,OAAO,MAAM,EAAE,EAAG,QAAO;AAC7B,UAAM,UAAU,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACnD,QAAI,KAAK,IAAI,UAAU,EAAE,IAAI,iBAAkB,QAAO;AAAA,EACxD;AACA,QAAM,WAAW,OAAO,KAAK,KAAK,QAAQ,SAAS,EAAE,CAAC;AAItD,aAAW,OAAO,UAAU,MAAM,GAAG,GAAG;AACtC,UAAM,QAAQ,IAAI,KAAK;AACvB,UAAM,YAAY,OAAO,KAAK,MAAM,WAAW,MAAM,IAAI,QAAQ,GAAG,MAAM,GAAG,KAAK,EAAE;AAEpF,QAAI,SAAS,WAAW,UAAU,UAAU,gBAAgB,UAAU,SAAS,EAAG,QAAO;AAAA,EAC3F;AACA,SAAO;AACT;;;ACxEA,SAAS,kBAAkB;;;ACApB,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAOO,IAAM,mBAAN,cAA+B,cAAc;AAAA,EACzC;AAAA,EACA;AAAA,EAET,YAAY,YAAoB,MAAc,SAAiB;AAC7D,UAAM,IAAI,UAAU,IAAI,IAAI,KAAK,OAAO,EAAE;AAC1C,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,SAAK,OAAO;AAAA,EACd;AACF;;;ACXA,SAAS,WAAW,aAAa,cAAc,YAAY,QAAQ,qBAAqB;AACxF,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;;;ACM9B,SAAS,YAAY,GAAiB,GAAyB;AAC7D,SAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI;AAC5E;AAEO,IAAM,aAAN,MAAkC;AAAA,EACtB;AAAA,EACA;AAAA,EACT;AAAA,EACA;AAAA,EAER,YAAY,OAA0B,CAAC,GAAG;AACxC,SAAK,MAAM,KAAK;AAChB,SAAK,SAAS,KAAK;AACnB,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA,EAEA,IAAY,UAAkB;AAC5B,WAAO,GAAG,KAAK,SAAS;AAAA,EAC1B;AAAA,EACA,IAAY,UAAkB;AAC5B,WAAO,GAAG,KAAK,SAAS;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAc,SAA+B;AAC3C,QAAI,KAAK,UAAU,KAAK,OAAO,OAAQ,QAAO,KAAK;AACnD,QAAI,KAAK,WAAY,QAAO,KAAK;AACjC,SAAK,cAAc,YAAY;AAC7B,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAM,EAAE,cAAc,OAAO,IAAI,MAAM,OAAO,OAAO;AACrD,aAAK,SAAS,OAAO,EAAE,KAAK,KAAK,IAAI,CAAC;AAAA,MACxC;AACA,UAAI,CAAC,KAAK,OAAO,OAAQ,OAAM,KAAK,OAAO,QAAQ;AACnD,aAAO,KAAK;AAAA,IACd,GAAG;AACH,QAAI;AACF,aAAO,MAAM,KAAK;AAAA,IACpB,UAAE;AACA,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,QAAqC;AAC9C,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,OAAO,KAAK,KAAK,SAAS,OAAO,IAAI,KAAK,UAAU,MAAM,CAAC;AACjE,UAAM,OAAO,KAAK,KAAK,SAAS,EAAE,OAAO,OAAO,eAAe,OAAO,OAAO,GAAG,CAAC;AAAA,EACnF;AAAA,EAEA,MAAM,YAAY,OAAwC;AACxD,UAAM,SAAS,MAAM,KAAK,OAAO;AAEjC,UAAM,MAAM,MAAM,OAAO,cAAc,KAAK,SAAS,QAAQ,KAAK,IAAI,CAAC;AACvE,WAAO,KAAK,WAAW,QAAQ,KAAK,KAAK;AAAA,EAC3C;AAAA,EAEA,MAAM,SAAS,IAA2B;AACxC,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,OAAO,KAAK,KAAK,SAAS,EAAE;AAClC,UAAM,OAAO,KAAK,KAAK,SAAS,EAAE;AAAA,EACpC;AAAA,EAEA,MAAM,WACJ,IACA,OACA,UACA,eACe;AACf,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,MAAM,MAAM,OAAO,KAAK,KAAK,SAAS,EAAE;AAC9C,QAAI,QAAQ,UAAa,QAAQ,KAAM;AACvC,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,WAAW;AAClB,WAAO,YAAY;AACnB,WAAO,gBAAgB;AACvB,UAAM,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,UAAU,MAAM,CAAC;AAC1D,UAAM,OAAO,KAAK,KAAK,SAAS,EAAE,OAAO,eAAe,OAAO,GAAG,CAAC;AAAA,EACrE;AAAA,EAEA,MAAM,OAAwB;AAC5B,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,WAAO,OAAO,KAAK,KAAK,OAAO;AAAA,EACjC;AAAA,EAEA,MAAM,SAAS,OAAyC;AACtD,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,MAAM,MAAM,OAAO,cAAc,KAAK,SAAS,sBAAsB,MAAM;AACjF,UAAM,QAAQ,MAAM,KAAK,WAAW,QAAQ,KAAK,IAAI,MAAM,GAAG,OAAO,MAAM;AAC3E,WAAO,UAAU,SAAY,OAAO,KAAK,MAAM,GAAG,KAAK;AAAA,EACzD;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,UAAU,KAAK,OAAO,OAAQ,OAAM,KAAK,OAAO,MAAM;AAAA,EACjE;AAAA;AAAA,EAGA,MAAc,WACZ,QACA,KACA,OACyB;AACzB,UAAM,UAA0B,CAAC;AACjC,eAAW,MAAM,KAAK;AACpB,YAAM,MAAM,MAAM,OAAO,KAAK,KAAK,SAAS,EAAE;AAC9C,UAAI,QAAQ,UAAa,QAAQ,KAAM;AACvC,cAAQ,KAAK,YAAY,KAAK,MAAM,GAAG,CAAiB,CAAC;AAAA,IAC3D;AACA,WAAO,QAAQ,KAAK,WAAW,EAAE,MAAM,GAAG,KAAK;AAAA,EACjD;AACF;;;ACjGA,SAAS,YAAY,KAA0B;AAC7C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,SAAS,IAAI;AAAA,IACb,WAAW,IAAI;AAAA,IACf,QAAQ,IAAI;AAAA,IACZ,WAAW,OAAO,IAAI,UAAU;AAAA,IAChC,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,eAAe,OAAO,IAAI,eAAe;AAAA,EAC3C;AACF;AAEO,IAAM,gBAAN,MAAqC;AAAA,EACzB;AAAA,EACA;AAAA,EACT;AAAA,EACA;AAAA,EAER,YAAY,OAA6B,CAAC,GAAG;AAC3C,SAAK,mBAAmB,KAAK;AAC7B,SAAK,OAAO,KAAK;AACjB,UAAM,QAAQ,KAAK,SAAS;AAC5B,QAAI,CAAC,2BAA2B,KAAK,KAAK,GAAG;AAC3C,YAAM,IAAI,MAAM,uBAAuB,KAAK,EAAE;AAAA,IAChD;AACA,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,MAAc,SAA0B;AACtC,QAAI,KAAK,MAAO,QAAO,KAAK;AAC5B,SAAK,SAAS,YAAY;AACxB,UAAI,CAAC,KAAK,MAAM;AACd,cAAM,KAAM,MAAM,OAAO,IAAI;AAI7B,cAAM,OAAO,GAAG,QAAQ,GAAG,SAAS;AACpC,YAAI,CAAC,KAAM,OAAM,IAAI,MAAM,mCAAmC;AAC9D,aAAK,OAAO,IAAI,KAAK,EAAE,kBAAkB,KAAK,iBAAiB,CAAC;AAAA,MAClE;AAIA,YAAM,KAAK,KAAK;AAAA,QACd,8BAA8B,KAAK,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAY1C;AACA,aAAO,KAAK;AAAA,IACd,GAAG;AACH,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,KAAK,QAAqC;AAC9C,UAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,UAAM,KAAK;AAAA,MACT,eAAe,KAAK,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAazB;AAAA,QACE,OAAO;AAAA,QACP,OAAO;AAAA,QACP,KAAK,UAAU,OAAO,OAAO;AAAA,QAC7B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAAwC;AACxD,UAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB,iBAAiB,KAAK,KAAK;AAAA;AAAA;AAAA;AAAA,MAI3B,CAAC,KAAK,IAAI,GAAG,sBAAsB,KAAK;AAAA,IAC1C;AACA,WAAO,IAAI,KAAK,IAAI,WAAW;AAAA,EACjC;AAAA,EAEA,MAAM,SAAS,IAA2B;AACxC,UAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,UAAM,KAAK,MAAM,UAAU,KAAK,KAAK,kCAAkC,CAAC,EAAE,CAAC;AAAA,EAC7E;AAAA,EAEA,MAAM,WACJ,IACA,OACA,UACA,eACe;AACf,UAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,UAAM,KAAK;AAAA,MACT,UAAU,KAAK,KAAK;AAAA,MACpB,CAAC,IAAI,UAAU,OAAO,aAAa;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,MAAM,OAAwB;AAC5B,UAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB,6BAA6B,KAAK,KAAK;AAAA,IACzC;AACA,WAAO,OAAO,IAAI,KAAK,CAAC,EAAE,CAAC;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAS,OAAyC;AACtD,UAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB,iBAAiB,KAAK,KAAK;AAAA;AAAA;AAAA,SAGxB,UAAU,SAAY,KAAK,UAAU;AAAA,MACxC,UAAU,SAAY,CAAC,oBAAoB,IAAI,CAAC,sBAAsB,KAAK;AAAA,IAC7E;AACA,WAAO,IAAI,KAAK,IAAI,WAAW;AAAA,EACjC;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,KAAM,OAAM,KAAK,KAAK,IAAI;AAAA,EACrC;AACF;;;AF9JO,IAAM,uBAAuB;AA6B7B,SAAS,OAAO,QAA+B;AACpD,SAAO,OAAO,iBAAiB;AACjC;AA8BO,SAAS,YAAY,QAAoC;AAC9D,SAAO,EAAE,GAAG,QAAQ,SAAS,gBAAgB,OAAO,OAAO,EAAE;AAC/D;AAGA,SAASA,aAAY,GAAiB,GAAyB;AAC7D,SAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI;AAC5E;AAKO,IAAM,cAAN,MAAmC;AAAA,EACvB,UAAU,oBAAI,IAA0B;AAAA,EAEzD,KAAK,QAA4B;AAC/B,SAAK,QAAQ,IAAI,OAAO,IAAI,YAAY,MAAM,CAAC;AAAA,EACjD;AAAA,EAEA,YAAY,OAA+B;AACzC,UAAM,MAAM,KAAK,IAAI;AACrB,WAAO,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC,EAC7B,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,EACpC,KAAKA,YAAW,EAChB,MAAM,GAAG,KAAK,EACd,IAAI,WAAW;AAAA,EACpB;AAAA,EAEA,SAAS,IAAkB;AACzB,SAAK,QAAQ,OAAO,EAAE;AAAA,EACxB;AAAA,EAEA,WAAW,IAAY,OAAe,UAAkB,eAA6B;AACnF,UAAM,SAAS,KAAK,QAAQ,IAAI,EAAE;AAClC,QAAI,CAAC,OAAQ;AACb,WAAO,WAAW;AAClB,WAAO,YAAY;AACnB,WAAO,gBAAgB;AAAA,EACzB;AAAA,EAEA,OAAe;AACb,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEA,SAAS,OAAgC;AACvC,UAAM,OAAO,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC,EAAE,OAAO,MAAM,EAAE,KAAKA,YAAW,EAAE,IAAI,WAAW;AACxF,WAAO,UAAU,SAAY,OAAO,KAAK,MAAM,GAAG,KAAK;AAAA,EACzD;AAAA,EAEA,QAAc;AACZ,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;AASO,IAAM,YAAN,MAAiC;AAAA,EACrB;AAAA,EAEjB,YAAY,KAAa;AACvB,SAAK,MAAM;AACX,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACpC;AAAA;AAAA,EAGQ,QAAQ,IAAoB;AAClC,WAAO,KAAK,KAAK,KAAK,GAAG,mBAAmB,EAAE,CAAC,OAAO;AAAA,EACxD;AAAA,EAEQ,UAA0B;AAChC,UAAM,MAAsB,CAAC;AAC7B,eAAW,QAAQ,YAAY,KAAK,GAAG,GAAG;AACxC,UAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAC7B,UAAI;AACF,YAAI,KAAK,KAAK,MAAM,aAAa,KAAK,KAAK,KAAK,IAAI,GAAG,MAAM,CAAC,CAAiB;AAAA,MACjF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,QAA4B;AAC/B,UAAM,OAAO,KAAK,QAAQ,OAAO,EAAE;AACnC,UAAM,MAAM,GAAG,IAAI,QAAQ,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC;AACzD,kBAAc,KAAK,KAAK,UAAU,MAAM,GAAG,MAAM;AACjD,eAAW,KAAK,IAAI;AAAA,EACtB;AAAA,EAEA,YAAY,OAA+B;AACzC,UAAM,MAAM,KAAK,IAAI;AACrB,WAAO,KAAK,QAAQ,EACjB,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,EACpC,KAAKA,YAAW,EAChB,MAAM,GAAG,KAAK;AAAA,EACnB;AAAA,EAEA,SAAS,IAAkB;AACzB,WAAO,KAAK,QAAQ,EAAE,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EAC1C;AAAA,EAEA,WAAW,IAAY,OAAe,UAAkB,eAA6B;AACnF,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,aAAa,KAAK,QAAQ,EAAE,GAAG,MAAM,CAAC;AAAA,IAC5D,QAAQ;AACN;AAAA,IACF;AACA,WAAO,WAAW;AAClB,WAAO,YAAY;AACnB,WAAO,gBAAgB;AACvB,SAAK,KAAK,MAAM;AAAA,EAClB;AAAA,EAEA,OAAe;AACb,WAAO,YAAY,KAAK,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC,EAAE;AAAA,EAClE;AAAA,EAEA,SAAS,OAAgC;AACvC,UAAM,OAAO,KAAK,QAAQ,EAAE,OAAO,MAAM,EAAE,KAAKA,YAAW;AAC3D,WAAO,UAAU,SAAY,OAAO,KAAK,MAAM,GAAG,KAAK;AAAA,EACzD;AAAA,EAEA,QAAc;AAAA,EAEd;AACF;AA6BA,SAASC,aAAY,KAA8B;AACjD,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,SAAS,KAAK,MAAM,IAAI,OAAO;AAAA,IAC/B,WAAW,IAAI;AAAA,IACf,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,IACf,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,eAAe,IAAI;AAAA,EACrB;AACF;AAMO,IAAM,cAAN,MAAmC;AAAA,EACvB;AAAA,EAEjB,YAAY,OAAO,YAAY;AAK7B,QAAI;AACJ,QAAI;AACF,UAAI;AACJ,UAAI;AACF,eAAO,cAAc,YAAY,GAAG;AAAA,MACtC,QAAQ;AACN,eAAO;AAAA,MACT;AACA,OAAC,EAAE,aAAa,IAAI,KAAK,aAAa;AAAA,IAGxC,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,iEAAiE,OAAO,GAAG,CAAC;AAAA,MAC9E;AAAA,IACF;AACA,SAAK,KAAK,IAAI,aAAa,IAAI;AAC/B,SAAK,GAAG;AAAA;AAAA;AAAA;AAAA,MAIN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWF;AAAA,EACF;AAAA,EAEA,KAAK,QAA4B;AAC/B,SAAK,GACF;AAAA,MACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYF,EACC;AAAA,MACC,OAAO;AAAA,MACP,OAAO;AAAA,MACP,KAAK,UAAU,OAAO,OAAO;AAAA,MAC7B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,EACJ;AAAA,EAEA,YAAY,OAA+B;AACzC,UAAM,OAAO,KAAK,GACf;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,KAAK,IAAI,GAAG,sBAAsB,KAAK;AAC9C,WAAO,KAAK,IAAIA,YAAW;AAAA,EAC7B;AAAA,EAEA,SAAS,IAAkB;AACzB,SAAK,GAAG,QAAQ,0CAA0C,EAAE,IAAI,EAAE;AAAA,EACpE;AAAA,EAEA,WAAW,IAAY,OAAe,UAAkB,eAA6B;AACnF,SAAK,GACF;AAAA,MACC;AAAA,IACF,EACC,IAAI,UAAU,OAAO,eAAe,EAAE;AAAA,EAC3C;AAAA,EAEA,OAAe;AACb,UAAM,MAAM,KAAK,GAAG,QAAQ,2CAA2C,EAAE,IAAI;AAC7E,WAAO,OAAO,IAAI,CAAC;AAAA,EACrB;AAAA,EAEA,SAAS,OAAgC;AACvC,UAAM,OAAO,KAAK,GACf;AAAA,MACC;AAAA;AAAA;AAAA,WAGG,UAAU,SAAY,KAAK,SAAS;AAAA,IACzC,EACC,IAAI,GAAI,UAAU,SAAY,CAAC,oBAAoB,IAAI,CAAC,sBAAsB,KAAK,CAAE;AACxF,WAAO,KAAK,IAAIA,YAAW;AAAA,EAC7B;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;;;AFnXA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAwMjD,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA,WAAW;AAAA,EAEnB,YAAY,MAAqB;AAC/B,SAAK,UAAU,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC9C,SAAK,SAAS,KAAK;AACnB,SAAK,YAAY,KAAK,aAAa;AACnC,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,YAAY,KAAK,aAAa;AACnC,SAAK,QAAQ,KAAK;AAClB,SAAK,cAAc,KAAK,eAAe;AACvC,SAAK,kBAAkB,KAAK,mBAAmB;AAC/C,SAAK,SAAS,KAAK;AACnB,SAAK,eAAe,KAAK;AAAA,EAC3B;AAAA,EAEA,MAAM,QACJ,WACA,SACA,OAAuB,CAAC,GACA;AACxB,UAAM,OAAgC;AAAA,MACpC,YAAY;AAAA,MACZ;AAAA,IACF;AAEA,UAAM,YAAY,mBAAmB,KAAK,SAAS;AACnD,QAAI,cAAc,KAAM,MAAK,aAAa;AAC1C,QAAI,KAAK,WAAW,OAAW,MAAK,SAAS,KAAK;AAElD,UAAM,UAAkC,CAAC;AACzC,QAAI,KAAK,mBAAmB,OAAW,SAAQ,iBAAiB,IAAI,KAAK;AAEzE,UAAM,OAAO,MAAM,KAAK,QAAQ,QAAQ,cAAc,EAAE,MAAM,QAAQ,CAAC;AACvE,UAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,WAAO;AAAA,MACL,IAAI,OAAO,KAAK,EAAE;AAAA,MAClB,UAAU,OAAO,KAAK,SAAS;AAAA,MAC/B,WAAW,OAAO,KAAK,UAAU;AAAA,MACjC,WAAW,OAAO,KAAK,UAAU;AAAA,MACjC,mBAAmB,OAAO,KAAK,sBAAsB,CAAC;AAAA,MACtD,QAAS,KAAK,UAA4B;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,QACJ,WACA,SACA,OAAuB,CAAC,GACC;AACzB,UAAM,QAAQ,KAAK,aAAa;AAChC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAuB;AAAA,MAC3B,IAAI,KAAK,kBAAkB,WAAW;AAAA,MACtC;AAAA,MACA;AAAA;AAAA,MAEA,WAAW,mBAAmB,KAAK,SAAS;AAAA,MAC5C,QAAQ,KAAK,UAAU;AAAA,MACvB,WAAW;AAAA,MACX,UAAU;AAAA,MACV,WAAW;AAAA,MACX,eAAe;AAAA,IACjB;AACA,UAAM,MAAM,KAAK,MAAM;AACvB,WAAO,EAAE,IAAI,OAAO,GAAG;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MAAM,OAAqB,CAAC,GAAyB;AACzD,UAAM,QAAQ,KAAK,aAAa;AAChC,UAAM,aAAa,KAAK,cAAc,KAAK;AAC3C,UAAM,cAAc,KAAK,eAAe,KAAK;AAC7C,UAAM,OAAO,MAAM,MAAM,YAAY,UAAU;AAE/C,QAAI,OAAO;AACX,QAAI,SAAS;AACb,eAAW,UAAU,MAAM;AACzB,YAAM,OAAgC;AAAA,QACpC,YAAY,OAAO;AAAA,QACnB,SAAS,OAAO;AAAA,MAClB;AACA,UAAI,OAAO,cAAc,KAAM,MAAK,aAAa,OAAO;AACxD,UAAI,OAAO,WAAW,KAAM,MAAK,SAAS,OAAO;AAEjD,UAAI;AACF,cAAM,KAAK,QAAQ,QAAQ,cAAc;AAAA,UACvC;AAAA,UACA,SAAS,EAAE,mBAAmB,OAAO,GAAG;AAAA,QAC1C,CAAC;AACD,cAAM,MAAM,SAAS,OAAO,EAAE;AAC9B,gBAAQ;AAAA,MACV,SAAS,KAAK;AACZ,cAAM,WAAW,OAAO,WAAW;AACnC,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAI,YAAY,aAAa;AAC3B,gBAAM,MAAM,WAAW,OAAO,IAAI,SAAS,UAAU,oBAAoB;AACzE,eAAK,SAAS;AAAA,YACZ,GAAG;AAAA,YACH;AAAA,YACA,WAAW;AAAA,YACX,eAAe;AAAA,UACjB,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,MAAM,WAAW,OAAO,IAAI,SAAS,UAAU,KAAK,IAAI,IAAI,UAAU,WAAW,CAAC,CAAC;AAAA,QAC3F;AACA,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,QAAQ,WAAW,MAAM,MAAM,KAAK,EAAE;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,iBAA+B;AAC1C,SAAK,aAAa;AAClB,QAAI,KAAK,WAAY;AACrB,UAAM,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,kBAAkB,GAAI,CAAC;AACzD,SAAK,aAAa,YAAY,MAAM,KAAK,KAAK,UAAU,GAAG,EAAE;AAC7D,SAAK,WAAW,QAAQ;AAAA,EAC1B;AAAA;AAAA,EAGA,cAAoB;AAClB,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAC7B,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,YAA2B;AACvC,QAAI,KAAK,SAAU;AACnB,SAAK,WAAW;AAChB,QAAI;AACF,YAAM,KAAK,MAAM;AAAA,IACnB,SAAS,KAAK;AACZ,WAAK,eAAe,GAAG;AAAA,IACzB,UAAE;AACA,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,eAAsB;AAC5B,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,cAAc,iEAA4D;AAAA,IACtF;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,KAAa,OAA8B,CAAC,GAAsB;AACrF,UAAM,OAAgC,EAAE,IAAI;AAE5C,UAAM,YAAY,mBAAmB,KAAK,SAAS;AACnD,QAAI,cAAc,KAAM,MAAK,aAAa;AAC1C,QAAI,KAAK,kBAAkB,OAAW,MAAK,gBAAgB,KAAK;AAChE,QAAI,KAAK,WAAW,OAAW,MAAK,SAAS,KAAK;AAClD,QAAI,KAAK,gBAAgB,OAAW,MAAK,eAAe,KAAK;AAC7D,QAAI,KAAK,kBAAkB,OAAW,MAAK,iBAAiB,KAAK;AACjE,QAAI,KAAK,gBAAgB,OAAW,MAAK,cAAc,KAAK;AAC5D,QAAI,KAAK,kBAAkB,OAAW,MAAK,iBAAiB,KAAK;AACjE,QAAI,KAAK,sBAAsB,OAAW,MAAK,sBAAsB,KAAK;AAC1E,WAAO,KAAK,YAAsB,QAAQ,iBAAiB,EAAE,KAAK,CAAC;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,cAAc,OAA6B,CAAC,GAA4B;AAC5E,UAAM,QAAiC,CAAC;AAExC,UAAM,YAAY,mBAAmB,KAAK,SAAS;AACnD,QAAI,cAAc,KAAM,OAAM,aAAa;AAC3C,QAAI,KAAK,WAAW,OAAW,OAAM,SAAS,KAAK;AACnD,QAAI,KAAK,UAAU,OAAW,OAAM,QAAQ,KAAK;AACjD,WAAO,KAAK,YAA4B,OAAO,iBAAiB,EAAE,MAAM,CAAC;AAAA,EAC3E;AAAA;AAAA,EAGA,MAAM,YAAY,IAA+B;AAC/C,WAAO,KAAK,YAAsB,OAAO,iBAAiB,mBAAmB,EAAE,CAAC,EAAE;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAe,IAAY,OAAyC;AACxE,WAAO,KAAK,YAAsB,SAAS,iBAAiB,mBAAmB,EAAE,CAAC,IAAI;AAAA,MACpF,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,eAAe,IAA2B;AAC9C,UAAM,KAAK,QAAQ,UAAU,iBAAiB,mBAAmB,EAAE,CAAC,EAAE;AAAA,EACxE;AAAA;AAAA,EAGA,MAAM,qBAAqB,IAA+B;AACxD,WAAO,KAAK,YAAsB,QAAQ,iBAAiB,mBAAmB,EAAE,CAAC,gBAAgB;AAAA,EACnG;AAAA;AAAA,EAGA,MAAM,eAAe,IAA+B;AAClD,WAAO,KAAK,YAAsB,QAAQ,iBAAiB,mBAAmB,EAAE,CAAC,SAAS;AAAA,EAC5F;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,OAA4B,CAAC,GAAoB;AAClE,UAAM,OAAgC;AAAA,MACpC,MAAM,KAAK,QAAQ;AAAA,MACnB,OAAO,KAAK,SAAS;AAAA,IACvB;AACA,QAAI,KAAK,kBAAkB,OAAW,MAAK,kBAAkB,KAAK;AAClE,WAAO,KAAK,YAAoB,QAAQ,gBAAgB,EAAE,KAAK,CAAC;AAAA,EAClE;AAAA;AAAA,EAGA,MAAM,aAAa,IAA2B;AAC5C,UAAM,KAAK,QAAQ,UAAU,gBAAgB,mBAAmB,EAAE,CAAC,EAAE;AAAA,EACvE;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,OAA8B,CAAC,GAA4B;AAC9E,UAAM,QAAiC,CAAC;AACxC,QAAI,KAAK,WAAW,OAAW,OAAM,SAAS,KAAK;AACnD,QAAI,KAAK,eAAe,OAAW,OAAM,cAAc,KAAK;AAC5D,QAAI,KAAK,cAAc,OAAW,OAAM,aAAa,KAAK;AAC1D,QAAI,KAAK,UAAU,OAAW,OAAM,QAAQ,KAAK;AACjD,QAAI,KAAK,UAAU,OAAW,OAAM,QAAQ,KAAK;AACjD,QAAI,KAAK,MAAM,OAAW,OAAM,IAAI,KAAK;AACzC,QAAI,KAAK,WAAW,OAAW,OAAM,SAAS,KAAK;AACnD,QAAI,KAAK,UAAU,OAAW,OAAM,QAAQ,KAAK;AACjD,WAAO,KAAK,YAA4B,OAAO,kBAAkB,EAAE,MAAM,CAAC;AAAA,EAC5E;AAAA;AAAA,EAGA,MAAM,UAAU,YAAuC;AACrD,WAAO,KAAK;AAAA,MACV;AAAA,MACA,kBAAkB,mBAAmB,UAAU,CAAC;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAc,YACZ,QACA,MACA,OAAuB,CAAC,GACZ;AACZ,UAAM,OAAO,MAAM,KAAK,QAAQ,QAAQ,MAAM,IAAI;AAClD,WAAQ,MAAM,KAAK,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,QACZ,QACA,MACA,OAAuB,CAAC,GACL;AACnB,QAAI,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAChC,QAAI,KAAK,OAAO;AACd,YAAM,KAAK,IAAI,gBAAgB;AAC/B,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AACrD,YAAI,UAAU,UAAa,UAAU,KAAM,IAAG,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA,MACzE;AACA,YAAM,SAAS,GAAG,SAAS;AAC3B,UAAI,OAAQ,QAAO,IAAI,MAAM;AAAA,IAC/B;AAEA,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,GAAG,KAAK;AAAA,IACV;AACA,QAAI;AACJ,QAAI,KAAK,SAAS,QAAW;AAC3B,aAAO,KAAK,UAAU,KAAK,IAAI;AAC/B,cAAQ,cAAc,IAAI;AAAA,IAC5B;AAEA,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,KAAK,YAAY,WAAW;AAC3D,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,KAAK,UAAU,KAAK;AAAA,UAC/B;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,QAC5C,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,kBAAU;AACV,YAAI,UAAU,KAAK,YAAY;AAC7B,gBAAM,MAAM,UAAU,OAAO,CAAC;AAC9B;AAAA,QACF;AACA,cAAM,IAAI,cAAc,mBAAmB,OAAO,GAAG,CAAC,EAAE;AAAA,MAC1D;AAEA,UAAI,eAAe,IAAI,KAAK,MAAM,KAAK,UAAU,KAAK,YAAY;AAChE,cAAM,MAAM,aAAa,IAAI,KAAK,UAAU,OAAO,CAAC;AACpD;AAAA,MACF;AACA,UAAI,KAAK,UAAU,IAAK,OAAM,MAAM,SAAS,IAAI;AACjD,aAAO;AAAA,IACT;AACA,UAAM,IAAI,cAAc,iCAAiC,OAAO,OAAO,CAAC,EAAE;AAAA,EAC5E;AACF;AAQA,SAAS,mBAAmB,WAA0C;AACpE,SAAO,cAAc,UAAa,cAAc,QAAQ,cAAc,KAAK,OAAO;AACpF;AAEA,SAAS,UAAU,SAAyB;AAC1C,SAAO,KAAK,IAAI,KAAM,MAAM,KAAK,OAAO;AAC1C;AAEA,SAAS,aAAa,MAA+B;AACnD,QAAM,MAAM,KAAK,QAAQ,IAAI,aAAa;AAC1C,MAAI,OAAO,QAAQ,KAAK,GAAG,EAAG,QAAO,OAAO,GAAG,IAAI;AACnD,SAAO;AACT;AAEA,eAAe,SAAS,MAA2C;AAEjE,QAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,MAAI,OAAO;AACX,MAAI,UAAU;AACd,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,WAAO,KAAK,OAAO,QAAQ;AAC3B,cAAU,KAAK,OAAO,WAAW;AAAA,EACnC,QAAQ;AAAA,EAER;AACA,SAAO,IAAI,iBAAiB,KAAK,QAAQ,MAAM,OAAO;AACxD;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;AKtiBO,IAAM,UAAU;","names":["byCreatedAt","rowToRecord"]}
1
+ {"version":3,"sources":["../src/signature.ts","../src/client.ts","../src/errors.ts","../src/outbox.ts","../src/stores/redis.ts","../src/stores/postgres.ts","../src/index.ts"],"sourcesContent":["/**\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 { WebhooksApiError, WebhooksError } 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 WebhooksEvent {\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 WebhooksClient.enqueue} / {@link WebhooksClient.drain}\n * (and the background drainer) become available. Omit to use only the live {@link WebhooksClient.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 WebhooksClient.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 WebhooksClient {\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<WebhooksEvent> {\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 WebhooksError(\"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 WebhooksApiError} 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 WebhooksError(`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 WebhooksError(`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<WebhooksApiError> {\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 WebhooksApiError(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 WebhooksError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhooksError\";\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 WebhooksApiError extends WebhooksError {\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 = \"WebhooksApiError\";\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 WebhooksClient} (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","/**\n * @nimbusnexus/webhooks-sdk — the official TypeScript client for NimbusNexus Webhooks.\n *\n * - `verify` — verify an incoming webhook's HMAC signature (for subscribers).\n * - `WebhooksClient` — publish events to webhookd (for producers).\n */\nexport { DEFAULT_TOLERANCE_SECONDS, sign, verify } from \"./signature\";\nexport type { VerifyOptions } from \"./signature\";\nexport { WebhooksClient } from \"./client\";\nexport type {\n ClientOptions,\n PublishOptions,\n WebhooksEvent,\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 { WebhooksApiError, WebhooksError } 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.5.2\";\n"],"mappings":"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","names":["byCreatedAt","rowToRecord"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbusnexus/webhooks-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "description": "Official TypeScript SDK for NimbusNexus Webhooks — publish events + verify webhook signatures.",
6
6
  "license": "MIT",