@spooled/sdk 1.3.0 → 1.4.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 +79 -8
- package/dist/{index-BVWKEr6p.d.cts → index-DzTC8ld3.d.cts} +569 -95
- package/dist/{index-BVWKEr6p.d.ts → index-DzTC8ld3.d.ts} +569 -95
- package/dist/index.cjs +341 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -186
- package/dist/index.d.ts +3 -186
- package/dist/index.js +341 -126
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +1 -1
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +1 -1
- package/dist/worker/index.d.ts +1 -1
- package/dist/worker/index.js +1 -1
- package/dist/worker/index.js.map +1 -1
- package/package.json +4 -3
- package/proto/spooled.proto +19 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ Official Node.js/TypeScript SDK for [Spooled Cloud](https://spooled.cloud) - a m
|
|
|
21
21
|
- **Organization management** with usage tracking
|
|
22
22
|
- **Webhook management** with automatic retries
|
|
23
23
|
- **Dead Letter Queue (DLQ)** operations
|
|
24
|
+
- **Server-side job search** (status lists, text, job type, creation time), paging with `hasMore`, and **job history**
|
|
24
25
|
- **API Key management** with Redis caching (~28x faster authentication)
|
|
25
26
|
- **Billing integration** via Stripe
|
|
26
27
|
- **Automatic case conversion** between camelCase (SDK) and snake_case (API)
|
|
@@ -109,6 +110,24 @@ const { id } = await client.jobs.create({
|
|
|
109
110
|
});
|
|
110
111
|
```
|
|
111
112
|
|
|
113
|
+
Find jobs on the server and page through them (API 0.1.122+):
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const page = await client.jobs.listPage({
|
|
117
|
+
status: ["failed", "deadletter"], // one status or several
|
|
118
|
+
q: "invoice", // id prefix, or text in the queue name or payload.job_type
|
|
119
|
+
jobType: "send-invoice", // exact payload.job_type
|
|
120
|
+
createdAfter: new Date(Date.now() - 86_400_000), // Date or RFC 3339 string
|
|
121
|
+
limit: 100,
|
|
122
|
+
});
|
|
123
|
+
console.log(page.data.length, page.hasMore); // hasMore comes from X-Has-More
|
|
124
|
+
|
|
125
|
+
// What happened to a job: claims, completions, retries, dead-lettering
|
|
126
|
+
const history = await client.jobs.history(id, { order: "desc" });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`jobs.list()` takes the same filters and still returns a plain array.
|
|
130
|
+
|
|
112
131
|
### Workers
|
|
113
132
|
|
|
114
133
|
Process jobs with the built-in worker runtime:
|
|
@@ -153,7 +172,13 @@ the server makes them idempotent. If an ack still fails after the retries the wo
|
|
|
153
172
|
Give any worker that restarts a stable `workerId` (1-128 chars, `[A-Za-z0-9._-]`). Registration then
|
|
154
173
|
upserts a single row instead of minting a new UUID each start, which otherwise leaves the previous
|
|
155
174
|
registration counting against your plan's worker limit until the stale-worker reaper clears it
|
|
156
|
-
(~2 minutes).
|
|
175
|
+
(~2 minutes). `workers.get()` and `workers.list()` report `jobsProcessed` and `jobsFailed` per worker
|
|
176
|
+
(API 0.1.122+).
|
|
177
|
+
|
|
178
|
+
If you process jobs without `SpooledWorker` (`jobs.claim()` and then `jobs.complete()`,
|
|
179
|
+
`jobs.fail()` or `jobs.heartbeat()`), pass the claimed job's `leaseId` on every one of those calls.
|
|
180
|
+
The API requires it: a call without it fails with a `ValidationError` (HTTP 422), and a stale lease
|
|
181
|
+
with HTTP 409. `SpooledWorker` does this for you.
|
|
157
182
|
|
|
158
183
|
### Workflows (DAGs)
|
|
159
184
|
|
|
@@ -241,6 +266,14 @@ const webhook = await client.webhooks.create({
|
|
|
241
266
|
secret: "webhook_secret_key",
|
|
242
267
|
});
|
|
243
268
|
|
|
269
|
+
// Post dead-lettered jobs and workflow outcomes straight to a Slack channel
|
|
270
|
+
await client.webhooks.create({
|
|
271
|
+
name: "On-call",
|
|
272
|
+
url: "https://hooks.slack.com/services/...",
|
|
273
|
+
events: ["job.deadlettered", "workflow.failed"],
|
|
274
|
+
format: "slack", // "json" (default) | "slack" | "discord" | "teams"
|
|
275
|
+
});
|
|
276
|
+
|
|
244
277
|
// Retry a failed delivery
|
|
245
278
|
await client.webhooks.retryDelivery(webhookId, deliveryId);
|
|
246
279
|
|
|
@@ -249,6 +282,13 @@ await client.webhooks.retryDelivery(webhookId, deliveryId);
|
|
|
249
282
|
const deliveries = await client.webhooks.getDeliveries(webhookId);
|
|
250
283
|
```
|
|
251
284
|
|
|
285
|
+
API 0.1.122 added the events `job.deadlettered`, `job.retried`, `workflow.completed` and
|
|
286
|
+
`workflow.failed`, and `format`, which sends each delivery as a chat message that a Slack, Discord
|
|
287
|
+
or Teams (legacy Office 365 connector) incoming-webhook URL accepts as it is.
|
|
288
|
+
|
|
289
|
+
The inbound side has a log too: `client.ingest.listDeliveries({ status, since, queueName })` lists
|
|
290
|
+
the accepted requests to your custom webhook URL (`GET /webhooks/deliveries`, no payloads).
|
|
291
|
+
|
|
252
292
|
After 20 consecutive failed deliveries a webhook is disabled automatically (`enabled: false`,
|
|
253
293
|
`lastStatus: "auto_disabled"`) and stops receiving events until you turn it back on:
|
|
254
294
|
|
|
@@ -296,7 +336,7 @@ Manage API keys programmatically:
|
|
|
296
336
|
const apiKey = await client.apiKeys.create({
|
|
297
337
|
name: "Production Worker",
|
|
298
338
|
queues: ["emails", "notifications"],
|
|
299
|
-
rateLimit: 1000,
|
|
339
|
+
rateLimit: 1000, // optional, requests per MINUTE for this key (can only lower the plan limit)
|
|
300
340
|
});
|
|
301
341
|
|
|
302
342
|
console.log(`Save this key: ${apiKey.key}`); // Only shown once!
|
|
@@ -351,10 +391,13 @@ All operations automatically enforce tier-based limits:
|
|
|
351
391
|
|
|
352
392
|
| Tier | Active Jobs | Daily Jobs | Queues | Workers | Webhooks |
|
|
353
393
|
| -------------- | ----------- | ---------- | --------- | --------- | --------- |
|
|
354
|
-
| **Free** | 10 | 1,000 |
|
|
355
|
-
| **Starter** |
|
|
394
|
+
| **Free** | 10 | 1,000 | 2 | 1 | 1 |
|
|
395
|
+
| **Starter** | 500 | 10,000 | 10 | 5 | 5 |
|
|
396
|
+
| **Pro** | 5,000 | 100,000 | 50 | 25 | 20 |
|
|
356
397
|
| **Enterprise** | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited |
|
|
357
398
|
|
|
399
|
+
(Source: the backend's `config/plans.rs`; Free has no workflows, Starter 5, Pro 25.)
|
|
400
|
+
|
|
358
401
|
**Limits are enforced on:**
|
|
359
402
|
|
|
360
403
|
- ✅ HTTP job creation (`POST /jobs`, `POST /jobs/bulk`)
|
|
@@ -389,10 +432,23 @@ try {
|
|
|
389
432
|
}
|
|
390
433
|
```
|
|
391
434
|
|
|
392
|
-
(
|
|
435
|
+
(Request-rate limiting also returns `429`, as a `RateLimitError` with the default `RATE_LIMIT_EXCEEDED` code. The plan limit is per organization in requests per second; an API key's own `rateLimit` is requests per minute.)
|
|
436
|
+
|
|
437
|
+
Since API 0.1.122, responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
|
|
438
|
+
(a Unix time in seconds) and `X-RateLimit-Scope` (`organization` or `api_key`), and a 429 adds
|
|
439
|
+
`Retry-After`. `client.getRateLimitInfo()` returns the values from the most recent response that had
|
|
440
|
+
them, and `RateLimitError.rateLimitInfo` those of a 429. The headers are optional: public routes and
|
|
441
|
+
servers without Redis do not send them.
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
await client.jobs.getStats();
|
|
445
|
+
const rate = client.getRateLimitInfo();
|
|
446
|
+
// { limit: 200, remaining: 199, reset: Date, scope: "organization" }
|
|
447
|
+
```
|
|
393
448
|
|
|
394
|
-
A plan quota does not clear for hours, so `error.isQuota` is `true` for it and the SDK never retries it
|
|
395
|
-
|
|
449
|
+
A plan quota does not clear for hours, so `error.isQuota` is `true` for it and the SDK never retries it.
|
|
450
|
+
Rate-limit 429s are retried after `Retry-After`. No 429 counts toward the circuit breaker, so a burst
|
|
451
|
+
above the plan limit slows the client down instead of opening the breaker for every call.
|
|
396
452
|
|
|
397
453
|
## Error Handling
|
|
398
454
|
|
|
@@ -440,11 +496,12 @@ const grpcClient = new SpooledGrpcClient({
|
|
|
440
496
|
useTls: true,
|
|
441
497
|
});
|
|
442
498
|
|
|
443
|
-
// Register worker
|
|
499
|
+
// Register worker (a stable workerId is reused across restarts)
|
|
444
500
|
const { workerId } = await grpcClient.workers.register({
|
|
445
501
|
queueName: "emails",
|
|
446
502
|
hostname: "worker-1",
|
|
447
503
|
maxConcurrency: 10,
|
|
504
|
+
workerId: "emails-worker-1",
|
|
448
505
|
});
|
|
449
506
|
|
|
450
507
|
// Enqueue job (enforces plan limits automatically)
|
|
@@ -486,6 +543,20 @@ for (const job of jobs) {
|
|
|
486
543
|
}
|
|
487
544
|
```
|
|
488
545
|
|
|
546
|
+
`fail` sends `retry: true` unless you pass `retry: false`; `false` is sent on the wire (proto field 4
|
|
547
|
+
has presence) and dead-letters the job at once, whatever its `maxRetries`.
|
|
548
|
+
|
|
549
|
+
Over gRPC `payload` and `result` are `google.protobuf.Struct`, so two things differ from REST: only a
|
|
550
|
+
JSON object travels (a job whose payload is an array, string, number or `null` arrives as an empty
|
|
551
|
+
object), and integers at or above 2^53 lose precision (send them as strings). Use REST for such jobs.
|
|
552
|
+
|
|
553
|
+
`dequeue` hands out up to `batchSize` jobs, each with a lease of `leaseDurationSecs` (5-3600 s; when
|
|
554
|
+
omitted the server default applies, 300 s on Spooled Cloud). For a job that can run longer than its
|
|
555
|
+
lease, pass a longer lease or renew it before it expires (`renewLease` with `jobId`, `workerId`,
|
|
556
|
+
`leaseId`, `extensionSecs`). A gRPC `StreamJobs` stream delivers **one job at a time**: the next
|
|
557
|
+
job is sent only after the previous one on that stream is completed, failed or released, so open
|
|
558
|
+
one stream per job slot you want to run in parallel.
|
|
559
|
+
|
|
489
560
|
**gRPC Features:**
|
|
490
561
|
|
|
491
562
|
- **~28x faster** than HTTP (with Redis cache: ~50ms vs 1400ms)
|