@nimbusnexus/webhooks-sdk 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # @nimbusnexus/webhooks-sdk (TypeScript)
2
2
 
3
- Official TypeScript SDK for **NimbusNexus Webhooks** — publish events, and verify the webhooks you
4
- receive. Zero runtime dependencies (uses the built-in `fetch` and `node:crypto`); Node ≥ 20.
3
+ Official TypeScript SDK for **NimbusNexus Webhooks** — publish events, manage your endpoints / keys /
4
+ deliveries, and verify the webhooks you receive. Zero runtime dependencies (uses the built-in `fetch`
5
+ and `node:crypto`); Node ≥ 20.
5
6
 
6
7
  ```sh
7
8
  npm install @nimbusnexus/webhooks-sdk
@@ -43,6 +44,99 @@ try {
43
44
  Transient failures (network errors, `429`, `5xx`) are retried with backoff (a `429` honours
44
45
  `Retry-After`); other `4xx` throw `WebhookdApiError` carrying the `{error:{code,message}}` envelope.
45
46
 
47
+ ## Outbox / durable buffering (producers)
48
+
49
+ `publish()` calls webhookd synchronously — if webhookd is unreachable it rejects and the event is
50
+ lost. The **write-first outbox** decouples the two: `enqueue()` durably persists the event to a
51
+ pluggable `Store` and resolves IMMEDIATELY (no network); `drain()` (or a background drainer) ships the
52
+ buffered events later. Every send carries `Idempotency-Key = record.id`, so a re-drain after a crash
53
+ or a lost response never double-publishes — webhookd dedupes. Delivery is **at-least-once**: nothing
54
+ is lost while webhookd is down.
55
+
56
+ ```ts
57
+ import { WebhookdClient, SqliteStore } from "@nimbusnexus/webhooks-sdk";
58
+
59
+ // 1. Configure a durable store (survives process restarts; needs Node >= 22.5 for node:sqlite).
60
+ const store = new SqliteStore("outbox.db");
61
+
62
+ const wh = new WebhookdClient({
63
+ baseUrl: "https://webhooks.example.com",
64
+ apiKey: "whsk_…",
65
+ store,
66
+ });
67
+
68
+ // 2. enqueue() instead of publish() — writes to the store and resolves at once, NO network call.
69
+ const { id } = await wh.enqueue("order.created", { orderId: "ord_123", total: 4200 });
70
+
71
+ // 3a. Drain on demand (resolves to { sent, failed, remaining }):
72
+ await wh.drain();
73
+
74
+ // 3b. …or run a background drainer that calls drain() every 5s until you stop it.
75
+ wh.startDrainer(5);
76
+ // ... your app keeps enqueuing; the drainer ships in the background ...
77
+ wh.stopDrainer();
78
+ ```
79
+
80
+ **Idempotency guarantee.** `id` is the `idempotencyKey` you pass (or a generated UUID v4) and becomes
81
+ the `Idempotency-Key` header on every delivery attempt for that record. If the process crashes after
82
+ a send but before the response is recorded, the next `drain()` re-sends with the *same* key and
83
+ webhookd returns the original event without re-fanning-out. A record that keeps failing is retried
84
+ with capped exponential backoff up to `maxAttempts` (default 10), then parked **dead** (never retried
85
+ again, retrievable via `store.listDead()`) and passed to the optional `onDead` callback.
86
+
87
+ **Built-in stores** — pass one as `store` in `ClientOptions`:
88
+
89
+ | Store | Durable? | Extra needed |
90
+ | --- | --- | --- |
91
+ | `MemoryStore` | No (in-process) | — (built-in) |
92
+ | `FileStore(dir)` | Yes (per-record JSON files) | — (built-in) |
93
+ | `SqliteStore(path)` | Yes (transactional) | — (built-in `node:sqlite`, Node ≥ 22.5) |
94
+ | `RedisStore({ url })` | Yes | `npm install redis` |
95
+ | `PostgresStore({ connectionString })` | Yes | `npm install pg` |
96
+
97
+ The core SDK stays zero-dependency; `redis` / `pg` are `optionalDependencies`, imported lazily only
98
+ when you construct `RedisStore` / `PostgresStore`.
99
+
100
+ ## Manage endpoints, keys & deliveries (operators)
101
+
102
+ The same client wraps the control-plane API — register receivers, mint keys, and drain the
103
+ dead-letter queue from code (needs an **admin**-scoped key). Management methods return the API's
104
+ snake_case JSON through typed interfaces (`Endpoint`, `ApiKey`, `Delivery`, `Page<T>`); list methods
105
+ return `{ items, next_offset }`; `deleteEndpoint` / `revokeApiKey` resolve to `void` (a `204`).
106
+
107
+ ```ts
108
+ import { WebhookdClient } from "@nimbusnexus/webhooks-sdk";
109
+
110
+ const wh = new WebhookdClient({ baseUrl: "https://webhooks.example.com", apiKey: "whsk_admin_…" });
111
+
112
+ // --- Endpoints ---------------------------------------------------------------
113
+ // Create a receiver — its signing secret is in the response exactly once, so persist it now.
114
+ const ep = await wh.createEndpoint("https://your-app.example/webhooks", {
115
+ subscriptions: [{ match_kind: "prefix", pattern: "order." }],
116
+ description: "orders service",
117
+ });
118
+ const { id: endpointId, secret: signingSecret } = ep;
119
+
120
+ await wh.listEndpoints({ environment: "prod" }); // { items, next_offset }
121
+ await wh.getEndpoint(endpointId);
122
+
123
+ // PATCH — send only the keys you want to change (omitted = unchanged, null = cleared):
124
+ await wh.updateEndpoint(endpointId, { max_attempts: 10, status: "disabled" });
125
+
126
+ await wh.rotateEndpointSecret(endpointId); // returns the new secret, once
127
+ await wh.enableEndpoint(endpointId); // recover an auto-disabled endpoint
128
+ await wh.deleteEndpoint(endpointId); // -> void (204)
129
+
130
+ // --- API keys ----------------------------------------------------------------
131
+ const key = await wh.createApiKey({ name: "ci-publisher", scope: "publish", expiresInDays: 90 });
132
+ console.log(key.key); // shown once
133
+ await wh.revokeApiKey(key.id); // -> void (204)
134
+
135
+ // --- Deliveries / dead-letter recovery ---------------------------------------
136
+ const dead = await wh.listDeliveries({ status: "dead" });
137
+ for (const d of dead.items) await wh.redeliver(d.id);
138
+ ```
139
+
46
140
  ## Develop
47
141
 
48
142
  ```sh