@nimbusnexus/webhooks-sdk 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,12 +35,24 @@ try {
35
35
  { orderId: "ord_123", total: 4200 },
36
36
  { idempotencyKey: "order-123" }, // makes the publish safe to retry
37
37
  );
38
- console.log(event.eventUid, event.deliveriesCreated);
38
+ console.log(event.eventUid, event.projectId, event.deliveriesCreated);
39
39
  } catch (e) {
40
40
  if (e instanceof WebhookdApiError) console.error(e.statusCode, e.code, e.message);
41
41
  }
42
42
  ```
43
43
 
44
+ **Projects.** A project is addressed by its **id** (`prj_…`), not a slug. Omit `projectId` — as
45
+ above — to publish into your workspace's **default** project; the server resolves it. Pass one only to
46
+ target a specific project:
47
+
48
+ ```ts
49
+ await wh.publish("order.created", { orderId: "ord_123" }, { projectId: "prj_3f9a…" });
50
+ ```
51
+
52
+ The id is opaque and per-workspace, so there is no client-side sentinel for "the default project":
53
+ leaving `projectId` unset omits the field entirely. The response (`event.projectId`) always carries
54
+ the id the event actually landed in.
55
+
44
56
  Transient failures (network errors, `429`, `5xx`) are retried with backoff (a `429` honours
45
57
  `Retry-After`); other `4xx` throw `WebhookdApiError` carrying the `{error:{code,message}}` envelope.
46
58
 
@@ -84,6 +96,10 @@ webhookd returns the original event without re-fanning-out. A record that keeps
84
96
  with capped exponential backoff up to `maxAttempts` (default 10), then parked **dead** (never retried
85
97
  again, retrievable via `store.listDead()`) and passed to the optional `onDead` callback.
86
98
 
99
+ `enqueue` takes the same optional `projectId` as `publish` (omit it for the default project). It is
100
+ persisted on the buffered record as a nullable `project_id` — `null` means "the workspace's default
101
+ project", and `drain` then omits the field from the publish body.
102
+
87
103
  **Built-in stores** — pass one as `store` in `ClientOptions`:
88
104
 
89
105
  | Store | Durable? | Extra needed |
@@ -111,13 +127,15 @@ const wh = new WebhookdClient({ baseUrl: "https://webhooks.example.com", apiKey:
111
127
 
112
128
  // --- Endpoints ---------------------------------------------------------------
113
129
  // Create a receiver — its signing secret is in the response exactly once, so persist it now.
130
+ // Omit `projectId` (here and on listEndpoints) for the workspace's default project.
114
131
  const ep = await wh.createEndpoint("https://your-app.example/webhooks", {
115
132
  subscriptions: [{ match_kind: "prefix", pattern: "order." }],
116
133
  description: "orders service",
117
134
  });
118
135
  const { id: endpointId, secret: signingSecret } = ep;
119
136
 
120
- await wh.listEndpoints({ environment: "prod" }); // { items, next_offset }
137
+ await wh.listEndpoints(); // default project — { items, next_offset }
138
+ await wh.listEndpoints({ projectId: "prj_3f9a…" }); // a specific project, by id
121
139
  await wh.getEndpoint(endpointId);
122
140
 
123
141
  // PATCH — send only the keys you want to change (omitted = unchanged, null = cleared):
package/dist/index.cjs CHANGED
@@ -38,9 +38,9 @@ __export(src_exports, {
38
38
  RedisStore: () => RedisStore,
39
39
  SqliteStore: () => SqliteStore,
40
40
  VERSION: () => VERSION,
41
- WebhookdApiError: () => WebhookdApiError,
42
- WebhookdClient: () => WebhookdClient,
43
- WebhookdError: () => WebhookdError,
41
+ WebhooksApiError: () => WebhooksApiError,
42
+ WebhooksClient: () => WebhooksClient,
43
+ WebhooksError: () => WebhooksError,
44
44
  isDead: () => isDead,
45
45
  sign: () => sign,
46
46
  verify: () => verify
@@ -83,18 +83,18 @@ function verify(secret, rawBody, signature, opts = {}) {
83
83
  var import_node_crypto3 = require("crypto");
84
84
 
85
85
  // src/errors.ts
86
- var WebhookdError = class extends Error {
86
+ var WebhooksError = class extends Error {
87
87
  constructor(message) {
88
88
  super(message);
89
- this.name = "WebhookdError";
89
+ this.name = "WebhooksError";
90
90
  }
91
91
  };
92
- var WebhookdApiError = class extends WebhookdError {
92
+ var WebhooksApiError = class extends WebhooksError {
93
93
  statusCode;
94
94
  code;
95
95
  constructor(statusCode, code, message) {
96
96
  super(`[${statusCode} ${code}] ${message}`);
97
- this.name = "WebhookdApiError";
97
+ this.name = "WebhooksApiError";
98
98
  this.statusCode = statusCode;
99
99
  this.code = code;
100
100
  }
@@ -201,8 +201,7 @@ function rowToRecord(row) {
201
201
  id: row.id,
202
202
  eventType: row.event_type,
203
203
  payload: row.payload,
204
- environment: row.environment,
205
- application: row.application,
204
+ projectId: row.project_id,
206
205
  source: row.source,
207
206
  createdAt: Number(row.created_at),
208
207
  attempts: row.attempts,
@@ -239,8 +238,7 @@ var PostgresStore = class {
239
238
  id TEXT PRIMARY KEY,
240
239
  event_type TEXT NOT NULL,
241
240
  payload JSONB NOT NULL,
242
- environment TEXT NOT NULL,
243
- application TEXT NOT NULL,
241
+ project_id TEXT,
244
242
  source TEXT,
245
243
  sent BOOLEAN NOT NULL DEFAULT FALSE,
246
244
  attempts INTEGER NOT NULL,
@@ -257,13 +255,12 @@ var PostgresStore = class {
257
255
  const pool = await this.ensure();
258
256
  await pool.query(
259
257
  `INSERT INTO ${this.table}
260
- (id, event_type, payload, environment, application, source, sent, attempts, last_error, next_attempt_at, created_at)
261
- VALUES ($1, $2, $3, $4, $5, $6, FALSE, $7, $8, $9, $10)
258
+ (id, event_type, payload, project_id, source, sent, attempts, last_error, next_attempt_at, created_at)
259
+ VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7, $8, $9)
262
260
  ON CONFLICT (id) DO UPDATE SET
263
261
  event_type = EXCLUDED.event_type,
264
262
  payload = EXCLUDED.payload,
265
- environment = EXCLUDED.environment,
266
- application = EXCLUDED.application,
263
+ project_id = EXCLUDED.project_id,
267
264
  source = EXCLUDED.source,
268
265
  sent = EXCLUDED.sent,
269
266
  attempts = EXCLUDED.attempts,
@@ -274,8 +271,7 @@ var PostgresStore = class {
274
271
  record.id,
275
272
  record.eventType,
276
273
  JSON.stringify(record.payload),
277
- record.environment,
278
- record.application,
274
+ record.projectId,
279
275
  record.source,
280
276
  record.attempts,
281
277
  record.lastError,
@@ -432,8 +428,7 @@ function rowToRecord2(row) {
432
428
  id: row.id,
433
429
  eventType: row.event_type,
434
430
  payload: JSON.parse(row.payload),
435
- environment: row.environment,
436
- application: row.application,
431
+ projectId: row.project_id,
437
432
  source: row.source,
438
433
  createdAt: row.created_at,
439
434
  attempts: row.attempts,
@@ -460,12 +455,14 @@ var SqliteStore = class {
460
455
  }
461
456
  this.db = new DatabaseSync(path);
462
457
  this.db.exec(
458
+ // `project_id` is NULLABLE: NULL = the workspace's default project (the publish body simply
459
+ // omits the field). Renamed from the legacy `project` slug column — this store has no
460
+ // schema-versioning mechanism, so a table created by an older SDK build is not upgraded.
463
461
  `CREATE TABLE IF NOT EXISTS webhookd_outbox (
464
462
  id TEXT PRIMARY KEY,
465
463
  event_type TEXT NOT NULL,
466
464
  payload TEXT NOT NULL,
467
- environment TEXT NOT NULL,
468
- application TEXT NOT NULL,
465
+ project_id TEXT,
469
466
  source TEXT,
470
467
  created_at INTEGER NOT NULL,
471
468
  attempts INTEGER NOT NULL,
@@ -477,13 +474,12 @@ var SqliteStore = class {
477
474
  save(record) {
478
475
  this.db.prepare(
479
476
  `INSERT INTO webhookd_outbox
480
- (id, event_type, payload, environment, application, source, created_at, attempts, last_error, next_attempt_at)
481
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
477
+ (id, event_type, payload, project_id, source, created_at, attempts, last_error, next_attempt_at)
478
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
482
479
  ON CONFLICT(id) DO UPDATE SET
483
480
  event_type = excluded.event_type,
484
481
  payload = excluded.payload,
485
- environment = excluded.environment,
486
- application = excluded.application,
482
+ project_id = excluded.project_id,
487
483
  source = excluded.source,
488
484
  created_at = excluded.created_at,
489
485
  attempts = excluded.attempts,
@@ -493,8 +489,7 @@ var SqliteStore = class {
493
489
  record.id,
494
490
  record.eventType,
495
491
  JSON.stringify(record.payload),
496
- record.environment,
497
- record.application,
492
+ record.projectId,
498
493
  record.source,
499
494
  record.createdAt,
500
495
  record.attempts,
@@ -539,7 +534,7 @@ var SqliteStore = class {
539
534
 
540
535
  // src/client.ts
541
536
  var RETRY_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
542
- var WebhookdClient = class {
537
+ var WebhooksClient = class {
543
538
  baseUrl;
544
539
  apiKey;
545
540
  timeoutMs;
@@ -567,10 +562,10 @@ var WebhookdClient = class {
567
562
  async publish(eventType, payload, opts = {}) {
568
563
  const body = {
569
564
  event_type: eventType,
570
- payload,
571
- environment: opts.environment ?? "prod",
572
- application: opts.application ?? "default"
565
+ payload
573
566
  };
567
+ const projectId = normalizeProjectId(opts.projectId);
568
+ if (projectId !== null) body.project_id = projectId;
574
569
  if (opts.source !== void 0) body.source = opts.source;
575
570
  const headers = {};
576
571
  if (opts.idempotencyKey !== void 0) headers["Idempotency-Key"] = opts.idempotencyKey;
@@ -580,8 +575,7 @@ var WebhookdClient = class {
580
575
  id: String(data.id),
581
576
  eventUid: String(data.event_uid),
582
577
  eventType: String(data.event_type),
583
- application: String(data.application ?? "default"),
584
- environment: String(data.environment ?? "prod"),
578
+ projectId: String(data.project_id),
585
579
  deliveriesCreated: Number(data.deliveries_created ?? 0),
586
580
  source: data.source ?? null
587
581
  };
@@ -599,8 +593,8 @@ var WebhookdClient = class {
599
593
  id: opts.idempotencyKey ?? (0, import_node_crypto3.randomUUID)(),
600
594
  eventType,
601
595
  payload,
602
- environment: opts.environment ?? "prod",
603
- application: opts.application ?? "default",
596
+ // null = the workspace's default project (the field is omitted from the publish body on drain).
597
+ projectId: normalizeProjectId(opts.projectId),
604
598
  source: opts.source ?? null,
605
599
  createdAt: now,
606
600
  attempts: 0,
@@ -627,10 +621,9 @@ var WebhookdClient = class {
627
621
  for (const record of rows) {
628
622
  const body = {
629
623
  event_type: record.eventType,
630
- payload: record.payload,
631
- environment: record.environment,
632
- application: record.application
624
+ payload: record.payload
633
625
  };
626
+ if (record.projectId !== null) body.project_id = record.projectId;
634
627
  if (record.source !== null) body.source = record.source;
635
628
  try {
636
629
  await this.request("POST", "/v1/events", {
@@ -690,18 +683,16 @@ var WebhookdClient = class {
690
683
  }
691
684
  requireStore() {
692
685
  if (!this.store) {
693
- throw new WebhookdError("no outbox store configured \u2014 pass `store` in ClientOptions");
686
+ throw new WebhooksError("no outbox store configured \u2014 pass `store` in ClientOptions");
694
687
  }
695
688
  return this.store;
696
689
  }
697
690
  // ── Endpoints ──────────────────────────────────────────────────────────────
698
691
  /** Create an endpoint. The response includes the signing `secret` exactly once — persist it. */
699
692
  async createEndpoint(url, opts = {}) {
700
- const body = {
701
- url,
702
- environment: opts.environment ?? "prod",
703
- application: opts.application ?? "default"
704
- };
693
+ const body = { url };
694
+ const projectId = normalizeProjectId(opts.projectId);
695
+ if (projectId !== null) body.project_id = projectId;
705
696
  if (opts.subscriptions !== void 0) body.subscriptions = opts.subscriptions;
706
697
  if (opts.secret !== void 0) body.secret = opts.secret;
707
698
  if (opts.maxAttempts !== void 0) body.max_attempts = opts.maxAttempts;
@@ -711,9 +702,11 @@ var WebhookdClient = class {
711
702
  if (opts.deliveryTimeoutMs !== void 0) body.delivery_timeout_ms = opts.deliveryTimeoutMs;
712
703
  return this.requestJson("POST", "/v1/endpoints", { body });
713
704
  }
714
- /** List endpoints for an environment (defaults to `prod`). */
705
+ /** List endpoints for a project. Omit `projectId` for the workspace's default project. */
715
706
  async listEndpoints(opts = {}) {
716
- const query = { environment: opts.environment ?? "prod" };
707
+ const query = {};
708
+ const projectId = normalizeProjectId(opts.projectId);
709
+ if (projectId !== null) query.project_id = projectId;
717
710
  if (opts.offset !== void 0) query.offset = opts.offset;
718
711
  if (opts.limit !== void 0) query.limit = opts.limit;
719
712
  return this.requestJson("GET", "/v1/endpoints", { query });
@@ -787,7 +780,7 @@ var WebhookdClient = class {
787
780
  /**
788
781
  * The single request path used by every method: builds the URL (+ query), attaches auth, and runs
789
782
  * the shared retry loop (connection errors + 429 honoring Retry-After + 5xx, capped exponential
790
- * backoff), raising a typed {@link WebhookdApiError} on other 4xx/5xx. The caller reads the body.
783
+ * backoff), raising a typed {@link WebhooksApiError} on other 4xx/5xx. The caller reads the body.
791
784
  */
792
785
  async request(method, path, opts = {}) {
793
786
  let url = `${this.baseUrl}${path}`;
@@ -824,7 +817,7 @@ var WebhookdClient = class {
824
817
  await sleep(backoffMs(attempt));
825
818
  continue;
826
819
  }
827
- throw new WebhookdError(`request failed: ${String(err)}`);
820
+ throw new WebhooksError(`request failed: ${String(err)}`);
828
821
  }
829
822
  if (RETRY_STATUSES.has(resp.status) && attempt < this.maxRetries) {
830
823
  await sleep(retryAfterMs(resp) ?? backoffMs(attempt));
@@ -833,9 +826,12 @@ var WebhookdClient = class {
833
826
  if (resp.status >= 400) throw await apiError(resp);
834
827
  return resp;
835
828
  }
836
- throw new WebhookdError(`request failed after retries: ${String(lastErr)}`);
829
+ throw new WebhooksError(`request failed after retries: ${String(lastErr)}`);
837
830
  }
838
831
  };
832
+ function normalizeProjectId(projectId) {
833
+ return projectId === void 0 || projectId === null || projectId === "" ? null : projectId;
834
+ }
839
835
  function backoffMs(attempt) {
840
836
  return Math.min(2e3, 200 * 2 ** attempt);
841
837
  }
@@ -854,14 +850,14 @@ async function apiError(resp) {
854
850
  message = data.error?.message ?? message;
855
851
  } catch {
856
852
  }
857
- return new WebhookdApiError(resp.status, code, message);
853
+ return new WebhooksApiError(resp.status, code, message);
858
854
  }
859
855
  function sleep(ms) {
860
856
  return new Promise((resolve) => setTimeout(resolve, ms));
861
857
  }
862
858
 
863
859
  // src/index.ts
864
- var VERSION = "0.3.0";
860
+ var VERSION = "0.5.0";
865
861
  // Annotate the CommonJS export names for ESM import in node:
866
862
  0 && (module.exports = {
867
863
  DEAD_NEXT_ATTEMPT_MS,
@@ -872,9 +868,9 @@ var VERSION = "0.3.0";
872
868
  RedisStore,
873
869
  SqliteStore,
874
870
  VERSION,
875
- WebhookdApiError,
876
- WebhookdClient,
877
- WebhookdError,
871
+ WebhooksApiError,
872
+ WebhooksClient,
873
+ WebhooksError,
878
874
  isDead,
879
875
  sign,
880
876
  verify