@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 +96 -2
- package/dist/index.cjs +706 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +370 -3
- package/dist/index.d.ts +370 -3
- package/dist/index.js +695 -13
- package/dist/index.js.map +1 -1
- package/package.json +22 -3
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,
|
|
4
|
-
receive. Zero runtime dependencies (uses the built-in `fetch`
|
|
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
|