@spooled/sdk 1.0.40 → 1.1.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
@@ -120,6 +120,7 @@ const client = new SpooledClient({ apiKey: "sp_live_..." });
120
120
 
121
121
  const worker = new SpooledWorker(client, {
122
122
  queueName: "my-queue",
123
+ workerId: "my-queue-worker-01", // stable id — see below
123
124
  concurrency: 10,
124
125
  });
125
126
 
@@ -135,6 +136,11 @@ await worker.start();
135
136
  process.on("SIGTERM", () => worker.stop());
136
137
  ```
137
138
 
139
+ Give any worker that restarts a stable `workerId` (1-128 chars, `[A-Za-z0-9._-]`). Registration then
140
+ upserts a single row instead of minting a new UUID each start, which otherwise leaves the previous
141
+ registration counting against your plan's worker limit until the stale-worker reaper clears it
142
+ (~2 minutes).
143
+
138
144
  ### Workflows (DAGs)
139
145
 
140
146
  Orchestrate multiple jobs with dependencies:
@@ -215,19 +221,32 @@ Configure outgoing webhooks for job events:
215
221
  ```typescript
216
222
  // Create webhook
217
223
  const webhook = await client.webhooks.create({
224
+ name: "App notifications",
218
225
  url: "https://your-app.com/webhooks/spooled",
219
226
  events: ["job.completed", "job.failed"],
220
- queueName: "my-queue",
221
227
  secret: "webhook_secret_key",
222
228
  });
223
229
 
224
230
  // Retry a failed delivery
225
231
  await client.webhooks.retryDelivery(webhookId, deliveryId);
226
232
 
227
- // Get delivery history
233
+ // Get delivery history (retention-bounded: free 1 day, starter 7, pro 30,
234
+ // enterprise 90 — and only the newest 100 deliveries per webhook)
228
235
  const deliveries = await client.webhooks.getDeliveries(webhookId);
229
236
  ```
230
237
 
238
+ After 20 consecutive failed deliveries a webhook is disabled automatically (`enabled: false`,
239
+ `lastStatus: "auto_disabled"`) and stops receiving events until you turn it back on:
240
+
241
+ ```typescript
242
+ await client.webhooks.update(webhookId, { enabled: true });
243
+ ```
244
+
245
+ Updating changes only the fields you send. `secret` is three-state — omit it to keep the current
246
+ secret, pass a string to replace it, or pass `null` to clear it. Clearing is destructive: deliveries
247
+ then go out unsigned, with no `X-Spooled-Signature` header. Do not serialise unchanged fields as
248
+ `null`.
249
+
231
250
  ### Dead Letter Queue (DLQ)
232
251
 
233
252
  Manage jobs that have exhausted all retries:
@@ -329,9 +348,9 @@ All operations automatically enforce tier-based limits:
329
348
  - ✅ Workflow creation (counts all jobs in workflow)
330
349
  - ✅ Schedule triggers
331
350
  - ✅ DLQ retry operations
332
- - ✅ Worker registration
351
+ - ✅ Worker registration (re-registering your own stable `workerId` is not charged again)
333
352
  - ✅ Queue creation
334
- - ✅ Webhook creation
353
+ - ✅ Webhook creation, and re-enabling a disabled webhook
335
354
 
336
355
  When a plan quota or limit is exceeded, you'll receive an `HTTP 429` response with
337
356
  `code: "QUOTA_EXCEEDED"`. The SDK reliably preserves the HTTP status, error code, message,
@@ -107,11 +107,11 @@ declare const DEFAULT_CONFIG: {
107
107
  successThreshold: number;
108
108
  timeout: number;
109
109
  };
110
- readonly userAgent: "@spooled/sdk-nodejs/1.0.40";
110
+ readonly userAgent: "@spooled/sdk-nodejs/1.1.0";
111
111
  readonly autoRefreshToken: true;
112
112
  };
113
113
  /** SDK version */
114
- declare const SDK_VERSION = "1.0.40";
114
+ declare const SDK_VERSION = "1.1.0";
115
115
  /** API version prefix */
116
116
  declare const API_VERSION = "v1";
117
117
  /**
@@ -1006,6 +1006,18 @@ interface WorkerSummary {
1006
1006
  interface RegisterWorkerParams {
1007
1007
  /** Queue name to process */
1008
1008
  queueName: string;
1009
+ /**
1010
+ * Stable worker identifier (1-128 chars, `[A-Za-z0-9._-]`).
1011
+ *
1012
+ * Supplying one makes registration an upsert, so a restarting worker reuses
1013
+ * a single row and re-registering an id you already own is not charged
1014
+ * against the plan worker cap. Omit it and the server mints a UUID, which
1015
+ * means every restart leaves the old row occupying the cap until the
1016
+ * stale-worker reaper clears it (~2 minutes) — enough for a crash-looping
1017
+ * worker on a tight plan to 429 itself out of registering. An id owned by a
1018
+ * different organization is rejected with HTTP 409.
1019
+ */
1020
+ workerId?: string;
1009
1021
  /** Worker hostname */
1010
1022
  hostname: string;
1011
1023
  /** Worker type identifier */
@@ -1485,9 +1497,24 @@ interface OutgoingWebhook {
1485
1497
  url: string;
1486
1498
  events: WebhookEventType[];
1487
1499
  enabled: boolean;
1500
+ /**
1501
+ * Consecutive failed deliveries.
1502
+ *
1503
+ * Counted once per DELIVERY, not once per retry attempt, so for the same
1504
+ * real-world failures this is roughly 5x smaller than a per-attempt count.
1505
+ * A successful delivery resets it to 0, including a successful manual retry
1506
+ * via `retryDelivery()`. At 20 the webhook is disabled automatically.
1507
+ */
1488
1508
  failureCount: number;
1489
1509
  lastTriggeredAt?: string;
1490
- lastStatus?: "success" | "failed";
1510
+ /**
1511
+ * Outcome of the most recent delivery.
1512
+ *
1513
+ * `"auto_disabled"` means 20 consecutive failed deliveries disabled the
1514
+ * webhook: `enabled` is now false and no further events are delivered until
1515
+ * it is re-enabled with `update(id, { enabled: true })`.
1516
+ */
1517
+ lastStatus?: "success" | "failed" | "auto_disabled";
1491
1518
  createdAt: string;
1492
1519
  updatedAt: string;
1493
1520
  }
@@ -1504,12 +1531,34 @@ interface CreateOutgoingWebhookParams {
1504
1531
  /** Whether enabled (default: true) */
1505
1532
  enabled?: boolean;
1506
1533
  }
1507
- /** Parameters for updating an outgoing webhook */
1534
+ /**
1535
+ * Parameters for updating an outgoing webhook.
1536
+ *
1537
+ * Every field is optional and only the fields you send are changed.
1538
+ */
1508
1539
  interface UpdateOutgoingWebhookParams {
1509
1540
  name?: string;
1510
1541
  url?: string;
1511
1542
  events?: WebhookEventType[];
1512
- secret?: string;
1543
+ /**
1544
+ * HMAC secret used to sign deliveries.
1545
+ *
1546
+ * Omit the field to keep the current secret, pass a string to replace it, or
1547
+ * pass `null` to CLEAR it. Clearing is destructive: deliveries then go out
1548
+ * unsigned, with no `X-Spooled-Signature` header, and the old secret cannot
1549
+ * be recovered. Client code that reserialises an unchanged webhook must send
1550
+ * `undefined` (or drop the key) rather than `null`, or it will wipe a live
1551
+ * secret.
1552
+ */
1553
+ secret?: string | null;
1554
+ /**
1555
+ * Whether the webhook receives events.
1556
+ *
1557
+ * Setting this to `true` is the recovery path for a webhook that was
1558
+ * auto-disabled after 20 consecutive failed deliveries. Re-enabling is
1559
+ * charged against the plan webhook cap, so it can fail with HTTP 429
1560
+ * `QUOTA_EXCEEDED` even though no webhook is being created.
1561
+ */
1513
1562
  enabled?: boolean;
1514
1563
  }
1515
1564
  /** Response for webhook test */
@@ -1519,7 +1568,15 @@ interface TestWebhookResponse {
1519
1568
  responseTimeMs: number;
1520
1569
  error?: string;
1521
1570
  }
1522
- /** Webhook delivery record */
1571
+ /**
1572
+ * Webhook delivery record.
1573
+ *
1574
+ * Delivery history is retained, not permanent: rows are removed by the
1575
+ * per-organization retention sweep using the plan's history retention window
1576
+ * (free 1 day, starter 7, pro 30, enterprise 90). Only the newest 100
1577
+ * deliveries per webhook are readable through the API in any case, so copy
1578
+ * anything you need as a durable audit record into your own store.
1579
+ */
1523
1580
  interface OutgoingWebhookDelivery {
1524
1581
  id: string;
1525
1582
  webhookId: string;
@@ -1593,6 +1650,12 @@ interface ApiKeySummary {
1593
1650
  rateLimit?: number;
1594
1651
  isActive: boolean;
1595
1652
  createdAt: string;
1653
+ /**
1654
+ * Coarse last-use timestamp, written at most once per key per 5 minutes
1655
+ * rather than on every request. Treat it as "used recently", not as a live
1656
+ * signal: it can lag actual use by up to 5 minutes, so it cannot answer
1657
+ * "is this key in use right now" and idle-key checks must allow for the lag.
1658
+ */
1596
1659
  lastUsed?: string;
1597
1660
  expiresAt?: string;
1598
1661
  }
@@ -3292,6 +3355,16 @@ declare function createClient(options: SpooledClientConfig): SpooledClient;
3292
3355
  interface SpooledWorkerOptions {
3293
3356
  /** Queue name to process */
3294
3357
  queueName: string;
3358
+ /**
3359
+ * Stable worker identifier (1-128 chars, `[A-Za-z0-9._-]`).
3360
+ *
3361
+ * Set this on any worker that restarts or redeploys: registration becomes an
3362
+ * upsert, so the process reuses one row instead of leaving the previous
3363
+ * registration to occupy the plan worker cap until the stale-worker reaper
3364
+ * clears it (~2 minutes). Omit it and the server mints a UUID on every
3365
+ * start. An id owned by a different organization is rejected with HTTP 409.
3366
+ */
3367
+ workerId?: string;
3295
3368
  /** Worker hostname (default: auto-detected) */
3296
3369
  hostname?: string;
3297
3370
  /** Worker type identifier */
@@ -3422,6 +3495,11 @@ type EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;
3422
3495
  */
3423
3496
  declare class SpooledWorker {
3424
3497
  private readonly client;
3498
+ /**
3499
+ * Options with every defaultable field filled in. `workerId` stays optional:
3500
+ * there is no sensible default for it, and omitting it is what makes the
3501
+ * server mint a UUID.
3502
+ */
3425
3503
  private readonly options;
3426
3504
  private readonly debug;
3427
3505
  private state;
@@ -107,11 +107,11 @@ declare const DEFAULT_CONFIG: {
107
107
  successThreshold: number;
108
108
  timeout: number;
109
109
  };
110
- readonly userAgent: "@spooled/sdk-nodejs/1.0.40";
110
+ readonly userAgent: "@spooled/sdk-nodejs/1.1.0";
111
111
  readonly autoRefreshToken: true;
112
112
  };
113
113
  /** SDK version */
114
- declare const SDK_VERSION = "1.0.40";
114
+ declare const SDK_VERSION = "1.1.0";
115
115
  /** API version prefix */
116
116
  declare const API_VERSION = "v1";
117
117
  /**
@@ -1006,6 +1006,18 @@ interface WorkerSummary {
1006
1006
  interface RegisterWorkerParams {
1007
1007
  /** Queue name to process */
1008
1008
  queueName: string;
1009
+ /**
1010
+ * Stable worker identifier (1-128 chars, `[A-Za-z0-9._-]`).
1011
+ *
1012
+ * Supplying one makes registration an upsert, so a restarting worker reuses
1013
+ * a single row and re-registering an id you already own is not charged
1014
+ * against the plan worker cap. Omit it and the server mints a UUID, which
1015
+ * means every restart leaves the old row occupying the cap until the
1016
+ * stale-worker reaper clears it (~2 minutes) — enough for a crash-looping
1017
+ * worker on a tight plan to 429 itself out of registering. An id owned by a
1018
+ * different organization is rejected with HTTP 409.
1019
+ */
1020
+ workerId?: string;
1009
1021
  /** Worker hostname */
1010
1022
  hostname: string;
1011
1023
  /** Worker type identifier */
@@ -1485,9 +1497,24 @@ interface OutgoingWebhook {
1485
1497
  url: string;
1486
1498
  events: WebhookEventType[];
1487
1499
  enabled: boolean;
1500
+ /**
1501
+ * Consecutive failed deliveries.
1502
+ *
1503
+ * Counted once per DELIVERY, not once per retry attempt, so for the same
1504
+ * real-world failures this is roughly 5x smaller than a per-attempt count.
1505
+ * A successful delivery resets it to 0, including a successful manual retry
1506
+ * via `retryDelivery()`. At 20 the webhook is disabled automatically.
1507
+ */
1488
1508
  failureCount: number;
1489
1509
  lastTriggeredAt?: string;
1490
- lastStatus?: "success" | "failed";
1510
+ /**
1511
+ * Outcome of the most recent delivery.
1512
+ *
1513
+ * `"auto_disabled"` means 20 consecutive failed deliveries disabled the
1514
+ * webhook: `enabled` is now false and no further events are delivered until
1515
+ * it is re-enabled with `update(id, { enabled: true })`.
1516
+ */
1517
+ lastStatus?: "success" | "failed" | "auto_disabled";
1491
1518
  createdAt: string;
1492
1519
  updatedAt: string;
1493
1520
  }
@@ -1504,12 +1531,34 @@ interface CreateOutgoingWebhookParams {
1504
1531
  /** Whether enabled (default: true) */
1505
1532
  enabled?: boolean;
1506
1533
  }
1507
- /** Parameters for updating an outgoing webhook */
1534
+ /**
1535
+ * Parameters for updating an outgoing webhook.
1536
+ *
1537
+ * Every field is optional and only the fields you send are changed.
1538
+ */
1508
1539
  interface UpdateOutgoingWebhookParams {
1509
1540
  name?: string;
1510
1541
  url?: string;
1511
1542
  events?: WebhookEventType[];
1512
- secret?: string;
1543
+ /**
1544
+ * HMAC secret used to sign deliveries.
1545
+ *
1546
+ * Omit the field to keep the current secret, pass a string to replace it, or
1547
+ * pass `null` to CLEAR it. Clearing is destructive: deliveries then go out
1548
+ * unsigned, with no `X-Spooled-Signature` header, and the old secret cannot
1549
+ * be recovered. Client code that reserialises an unchanged webhook must send
1550
+ * `undefined` (or drop the key) rather than `null`, or it will wipe a live
1551
+ * secret.
1552
+ */
1553
+ secret?: string | null;
1554
+ /**
1555
+ * Whether the webhook receives events.
1556
+ *
1557
+ * Setting this to `true` is the recovery path for a webhook that was
1558
+ * auto-disabled after 20 consecutive failed deliveries. Re-enabling is
1559
+ * charged against the plan webhook cap, so it can fail with HTTP 429
1560
+ * `QUOTA_EXCEEDED` even though no webhook is being created.
1561
+ */
1513
1562
  enabled?: boolean;
1514
1563
  }
1515
1564
  /** Response for webhook test */
@@ -1519,7 +1568,15 @@ interface TestWebhookResponse {
1519
1568
  responseTimeMs: number;
1520
1569
  error?: string;
1521
1570
  }
1522
- /** Webhook delivery record */
1571
+ /**
1572
+ * Webhook delivery record.
1573
+ *
1574
+ * Delivery history is retained, not permanent: rows are removed by the
1575
+ * per-organization retention sweep using the plan's history retention window
1576
+ * (free 1 day, starter 7, pro 30, enterprise 90). Only the newest 100
1577
+ * deliveries per webhook are readable through the API in any case, so copy
1578
+ * anything you need as a durable audit record into your own store.
1579
+ */
1523
1580
  interface OutgoingWebhookDelivery {
1524
1581
  id: string;
1525
1582
  webhookId: string;
@@ -1593,6 +1650,12 @@ interface ApiKeySummary {
1593
1650
  rateLimit?: number;
1594
1651
  isActive: boolean;
1595
1652
  createdAt: string;
1653
+ /**
1654
+ * Coarse last-use timestamp, written at most once per key per 5 minutes
1655
+ * rather than on every request. Treat it as "used recently", not as a live
1656
+ * signal: it can lag actual use by up to 5 minutes, so it cannot answer
1657
+ * "is this key in use right now" and idle-key checks must allow for the lag.
1658
+ */
1596
1659
  lastUsed?: string;
1597
1660
  expiresAt?: string;
1598
1661
  }
@@ -3292,6 +3355,16 @@ declare function createClient(options: SpooledClientConfig): SpooledClient;
3292
3355
  interface SpooledWorkerOptions {
3293
3356
  /** Queue name to process */
3294
3357
  queueName: string;
3358
+ /**
3359
+ * Stable worker identifier (1-128 chars, `[A-Za-z0-9._-]`).
3360
+ *
3361
+ * Set this on any worker that restarts or redeploys: registration becomes an
3362
+ * upsert, so the process reuses one row instead of leaving the previous
3363
+ * registration to occupy the plan worker cap until the stale-worker reaper
3364
+ * clears it (~2 minutes). Omit it and the server mints a UUID on every
3365
+ * start. An id owned by a different organization is rejected with HTTP 409.
3366
+ */
3367
+ workerId?: string;
3295
3368
  /** Worker hostname (default: auto-detected) */
3296
3369
  hostname?: string;
3297
3370
  /** Worker type identifier */
@@ -3422,6 +3495,11 @@ type EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;
3422
3495
  */
3423
3496
  declare class SpooledWorker {
3424
3497
  private readonly client;
3498
+ /**
3499
+ * Options with every defaultable field filled in. `workerId` stays optional:
3500
+ * there is no sensible default for it, and omitting it is what makes the
3501
+ * server mint a UUID.
3502
+ */
3425
3503
  private readonly options;
3426
3504
  private readonly debug;
3427
3505
  private state;
package/dist/index.cjs CHANGED
@@ -50,10 +50,10 @@ var DEFAULT_CONFIG = {
50
50
  successThreshold: 3,
51
51
  timeout: 3e4
52
52
  },
53
- userAgent: "@spooled/sdk-nodejs/1.0.40",
53
+ userAgent: "@spooled/sdk-nodejs/1.1.0",
54
54
  autoRefreshToken: true
55
55
  };
56
- var SDK_VERSION = "1.0.40";
56
+ var SDK_VERSION = "1.1.0";
57
57
  var API_VERSION = "v1";
58
58
  var API_BASE_PATH = `/api/${API_VERSION}`;
59
59
  function normalizeCredential(value) {
@@ -3661,6 +3661,11 @@ var DEFAULT_OPTIONS = {
3661
3661
  };
3662
3662
  var SpooledWorker = class {
3663
3663
  client;
3664
+ /**
3665
+ * Options with every defaultable field filled in. `workerId` stays optional:
3666
+ * there is no sensible default for it, and omitting it is what makes the
3667
+ * server mint a UUID.
3668
+ */
3664
3669
  options;
3665
3670
  debug;
3666
3671
  state = "idle";
@@ -3731,6 +3736,7 @@ var SpooledWorker = class {
3731
3736
  try {
3732
3737
  const registration = await this.client.workers.register({
3733
3738
  queueName: this.options.queueName,
3739
+ workerId: this.options.workerId,
3734
3740
  hostname: this.options.hostname,
3735
3741
  workerType: this.options.workerType,
3736
3742
  maxConcurrency: this.options.concurrency,