@spooled/sdk 1.0.39 → 1.0.40

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
@@ -34,23 +34,23 @@ npm install @spooled/sdk
34
34
  ## Quick Start
35
35
 
36
36
  ```typescript
37
- import { SpooledClient } from '@spooled/sdk';
37
+ import { SpooledClient } from "@spooled/sdk";
38
38
 
39
39
  const client = new SpooledClient({
40
- apiKey: 'sp_live_your_api_key',
40
+ apiKey: "sp_live_your_api_key",
41
41
  // For self-hosted: baseUrl: 'https://your-spooled-server.com'
42
42
  });
43
43
 
44
44
  // Create a job
45
45
  const { id } = await client.jobs.create({
46
- queueName: 'email-notifications',
46
+ queueName: "email-notifications",
47
47
  payload: {
48
- to: 'user@example.com',
49
- subject: 'Welcome!',
50
- body: 'Thanks for signing up.'
48
+ to: "user@example.com",
49
+ subject: "Welcome!",
50
+ body: "Thanks for signing up.",
51
51
  },
52
52
  priority: 5,
53
- maxRetries: 3
53
+ maxRetries: 3,
54
54
  });
55
55
 
56
56
  console.log(`Created job: ${id}`);
@@ -62,28 +62,28 @@ console.log(`Status: ${job.status}`);
62
62
 
63
63
  ## Documentation
64
64
 
65
- | Guide | Description |
66
- |-------|-------------|
67
- | [Getting Started](docs/getting-started.md) | Installation, setup, and first job |
68
- | [Configuration](docs/configuration.md) | All configuration options |
69
- | [Workers](docs/workers.md) | Job processing with SpooledWorker |
70
- | [Workflows](docs/workflows.md) | Building DAGs with job dependencies |
71
- | [gRPC](docs/grpc.md) | High-performance streaming |
72
- | [Resources](docs/resources.md) | Complete API reference |
65
+ | Guide | Description |
66
+ | ------------------------------------------ | ----------------------------------- |
67
+ | [Getting Started](docs/getting-started.md) | Installation, setup, and first job |
68
+ | [Configuration](docs/configuration.md) | All configuration options |
69
+ | [Workers](docs/workers.md) | Job processing with SpooledWorker |
70
+ | [Workflows](docs/workflows.md) | Building DAGs with job dependencies |
71
+ | [gRPC](docs/grpc.md) | High-performance streaming |
72
+ | [Resources](docs/resources.md) | Complete API reference |
73
73
 
74
74
  ## Examples
75
75
 
76
76
  See the [`examples/`](examples/) directory for runnable code:
77
77
 
78
- | Example | Description |
79
- |---------|-------------|
80
- | [`quick-start.ts`](examples/quick-start.ts) | Basic SDK usage |
81
- | [`worker.ts`](examples/worker.ts) | Processing jobs with SpooledWorker |
82
- | [`workflow-dag.ts`](examples/workflow-dag.ts) | Complex workflows with dependencies |
83
- | [`grpc-streaming.ts`](examples/grpc-streaming.ts) | High-performance gRPC streaming |
84
- | [`realtime.ts`](examples/realtime.ts) | Real-time event streaming |
85
- | [`schedules.ts`](examples/schedules.ts) | Cron schedules |
86
- | [`error-handling.ts`](examples/error-handling.ts) | Error handling patterns |
78
+ | Example | Description |
79
+ | ------------------------------------------------- | ----------------------------------- |
80
+ | [`quick-start.ts`](examples/quick-start.ts) | Basic SDK usage |
81
+ | [`worker.ts`](examples/worker.ts) | Processing jobs with SpooledWorker |
82
+ | [`workflow-dag.ts`](examples/workflow-dag.ts) | Complex workflows with dependencies |
83
+ | [`grpc-streaming.ts`](examples/grpc-streaming.ts) | High-performance gRPC streaming |
84
+ | [`realtime.ts`](examples/realtime.ts) | Real-time event streaming |
85
+ | [`schedules.ts`](examples/schedules.ts) | Cron schedules |
86
+ | [`error-handling.ts`](examples/error-handling.ts) | Error handling patterns |
87
87
 
88
88
  ## Real-world examples (beginner friendly)
89
89
 
@@ -99,13 +99,13 @@ Jobs are units of work with payloads, priorities, and retry policies:
99
99
 
100
100
  ```typescript
101
101
  const { id } = await client.jobs.create({
102
- queueName: 'my-queue',
103
- payload: { data: 'value' },
104
- priority: 5, // -100 to 100
102
+ queueName: "my-queue",
103
+ payload: { data: "value" },
104
+ priority: 5, // -100 to 100
105
105
  maxRetries: 3,
106
106
  timeoutSeconds: 300,
107
107
  scheduledAt: new Date(Date.now() + 60000),
108
- idempotencyKey: 'unique-key',
108
+ idempotencyKey: "unique-key",
109
109
  });
110
110
  ```
111
111
 
@@ -114,12 +114,12 @@ const { id } = await client.jobs.create({
114
114
  Process jobs with the built-in worker runtime:
115
115
 
116
116
  ```typescript
117
- import { SpooledClient, SpooledWorker } from '@spooled/sdk';
117
+ import { SpooledClient, SpooledWorker } from "@spooled/sdk";
118
118
 
119
- const client = new SpooledClient({ apiKey: 'sp_live_...' });
119
+ const client = new SpooledClient({ apiKey: "sp_live_..." });
120
120
 
121
121
  const worker = new SpooledWorker(client, {
122
- queueName: 'my-queue',
122
+ queueName: "my-queue",
123
123
  concurrency: 10,
124
124
  });
125
125
 
@@ -132,7 +132,7 @@ worker.process(async (ctx) => {
132
132
  await worker.start();
133
133
 
134
134
  // Graceful shutdown
135
- process.on('SIGTERM', () => worker.stop());
135
+ process.on("SIGTERM", () => worker.stop());
136
136
  ```
137
137
 
138
138
  ### Workflows (DAGs)
@@ -141,11 +141,21 @@ Orchestrate multiple jobs with dependencies:
141
141
 
142
142
  ```typescript
143
143
  const workflow = await client.workflows.create({
144
- name: 'ETL Pipeline',
144
+ name: "ETL Pipeline",
145
145
  jobs: [
146
- { key: 'extract', queueName: 'etl', payload: { step: 'extract' } },
147
- { key: 'transform', queueName: 'etl', payload: { step: 'transform' }, dependsOn: ['extract'] },
148
- { key: 'load', queueName: 'etl', payload: { step: 'load' }, dependsOn: ['transform'] },
146
+ { key: "extract", queueName: "etl", payload: { step: "extract" } },
147
+ {
148
+ key: "transform",
149
+ queueName: "etl",
150
+ payload: { step: "transform" },
151
+ dependsOn: ["extract"],
152
+ },
153
+ {
154
+ key: "load",
155
+ queueName: "etl",
156
+ payload: { step: "load" },
157
+ dependsOn: ["transform"],
158
+ },
149
159
  ],
150
160
  });
151
161
  ```
@@ -156,11 +166,11 @@ Run jobs on a cron schedule:
156
166
 
157
167
  ```typescript
158
168
  const schedule = await client.schedules.create({
159
- name: 'Daily Report',
160
- cronExpression: '0 9 * * *', // 9 AM daily; 5-field (min hour dom mon dow) or 6-field (leading seconds) both work
161
- timezone: 'America/New_York',
162
- queueName: 'reports',
163
- payloadTemplate: { type: 'daily' },
169
+ name: "Daily Report",
170
+ cronExpression: "0 9 * * *", // 9 AM daily; 5-field (min hour dom mon dow) or 6-field (leading seconds) both work
171
+ timezone: "America/New_York",
172
+ queueName: "reports",
173
+ payloadTemplate: { type: "daily" },
164
174
  });
165
175
  ```
166
176
 
@@ -171,12 +181,12 @@ Subscribe to real-time job events:
171
181
  ```typescript
172
182
  const realtime = await client.realtime();
173
183
 
174
- realtime.on('job.completed', (data) => {
184
+ realtime.on("job.completed", (data) => {
175
185
  console.log(`Job completed: ${data.jobId}`);
176
186
  });
177
187
 
178
188
  await realtime.connect();
179
- await realtime.subscribe({ queueName: 'my-queue' });
189
+ await realtime.subscribe({ queueName: "my-queue" });
180
190
  ```
181
191
 
182
192
  ### Organization Management
@@ -186,14 +196,16 @@ Manage your organization and track usage:
186
196
  ```typescript
187
197
  // Get current usage and limits
188
198
  const usage = await client.organizations.getUsage();
189
- console.log(`Active jobs: ${usage.active_jobs.current}/${usage.active_jobs.limit}`);
199
+ console.log(
200
+ `Active jobs: ${usage.active_jobs.current}/${usage.active_jobs.limit}`,
201
+ );
190
202
  console.log(`Plan: ${usage.plan.tier}`);
191
203
 
192
204
  // Generate a unique slug for a new organization
193
- const { slug } = await client.organizations.generateSlug('My Company');
205
+ const { slug } = await client.organizations.generateSlug("My Company");
194
206
 
195
207
  // Check if a slug is available
196
- const { available } = await client.organizations.checkSlug('my-company');
208
+ const { available } = await client.organizations.checkSlug("my-company");
197
209
  ```
198
210
 
199
211
  ### Webhooks
@@ -203,10 +215,10 @@ Configure outgoing webhooks for job events:
203
215
  ```typescript
204
216
  // Create webhook
205
217
  const webhook = await client.webhooks.create({
206
- url: 'https://your-app.com/webhooks/spooled',
207
- events: ['job.completed', 'job.failed'],
208
- queueName: 'my-queue',
209
- secret: 'webhook_secret_key'
218
+ url: "https://your-app.com/webhooks/spooled",
219
+ events: ["job.completed", "job.failed"],
220
+ queueName: "my-queue",
221
+ secret: "webhook_secret_key",
210
222
  });
211
223
 
212
224
  // Retry a failed delivery
@@ -226,19 +238,19 @@ const dlqJobs = await client.jobs.dlq.list({ limit: 100 });
226
238
 
227
239
  // Retry specific jobs from DLQ
228
240
  await client.jobs.dlq.retry({
229
- jobIds: ['job-1', 'job-2'],
241
+ jobIds: ["job-1", "job-2"],
230
242
  });
231
243
 
232
244
  // Retry jobs by queue
233
245
  await client.jobs.dlq.retry({
234
- queueName: 'my-queue',
235
- limit: 50
246
+ queueName: "my-queue",
247
+ limit: 50,
236
248
  });
237
249
 
238
250
  // Purge DLQ (requires confirmation)
239
251
  await client.jobs.dlq.purge({
240
- queueName: 'my-queue',
241
- confirm: true
252
+ queueName: "my-queue",
253
+ confirm: true,
242
254
  });
243
255
  ```
244
256
 
@@ -249,9 +261,9 @@ Manage API keys programmatically:
249
261
  ```typescript
250
262
  // Create a new API key
251
263
  const apiKey = await client.apiKeys.create({
252
- name: 'Production Worker',
253
- queues: ['emails', 'notifications'],
254
- rateLimit: 1000
264
+ name: "Production Worker",
265
+ queues: ["emails", "notifications"],
266
+ rateLimit: 1000,
255
267
  });
256
268
 
257
269
  console.log(`Save this key: ${apiKey.key}`); // Only shown once!
@@ -273,7 +285,7 @@ const status = await client.billing.getStatus();
273
285
 
274
286
  // Create customer portal session
275
287
  const { url } = await client.billing.createPortal({
276
- returnUrl: 'https://your-app.com/settings'
288
+ returnUrl: "https://your-app.com/settings",
277
289
  });
278
290
 
279
291
  // Redirect user to: url
@@ -285,15 +297,15 @@ Email-based passwordless authentication:
285
297
 
286
298
  ```typescript
287
299
  // Start email login flow
288
- await client.auth.startEmailLogin('user@example.com');
300
+ await client.auth.startEmailLogin("user@example.com");
289
301
  // User receives email with login link
290
302
 
291
303
  // Check if email exists
292
- const { exists } = await client.auth.checkEmail('user@example.com');
304
+ const { exists } = await client.auth.checkEmail("user@example.com");
293
305
 
294
306
  // Exchange API key for JWT
295
307
  const { accessToken, refreshToken } = await client.auth.login({
296
- apiKey: 'sp_live_...'
308
+ apiKey: "sp_live_...",
297
309
  });
298
310
 
299
311
  // Use JWT for subsequent requests
@@ -304,13 +316,14 @@ const jwtClient = new SpooledClient({ accessToken });
304
316
 
305
317
  All operations automatically enforce tier-based limits:
306
318
 
307
- | Tier | Active Jobs | Daily Jobs | Queues | Workers | Webhooks |
308
- |------|-------------|------------|--------|---------|----------|
309
- | **Free** | 10 | 1,000 | 5 | 3 | 2 |
310
- | **Starter** | 100 | 100,000 | 25 | 25 | 10 |
311
- | **Enterprise** | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited |
319
+ | Tier | Active Jobs | Daily Jobs | Queues | Workers | Webhooks |
320
+ | -------------- | ----------- | ---------- | --------- | --------- | --------- |
321
+ | **Free** | 10 | 1,000 | 5 | 3 | 2 |
322
+ | **Starter** | 100 | 100,000 | 25 | 25 | 10 |
323
+ | **Enterprise** | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited |
312
324
 
313
325
  **Limits are enforced on:**
326
+
314
327
  - ✅ HTTP job creation (`POST /jobs`, `POST /jobs/bulk`)
315
328
  - ✅ gRPC job enqueue
316
329
  - ✅ Workflow creation (counts all jobs in workflow)
@@ -327,16 +340,18 @@ metadata under a JSON `details` field; quota fields sent at the top level of the
327
340
  (such as `resource`, `current`, `limit`, and `plan`) are not currently exposed by the SDK.
328
341
 
329
342
  ```typescript
330
- import { RateLimitError } from '@spooled/sdk';
343
+ import { RateLimitError } from "@spooled/sdk";
331
344
 
332
345
  try {
333
- await client.jobs.create({ /* ... */ });
346
+ await client.jobs.create({
347
+ /* ... */
348
+ });
334
349
  } catch (error) {
335
- if (error instanceof RateLimitError && error.code === 'QUOTA_EXCEEDED') {
350
+ if (error instanceof RateLimitError && error.code === "QUOTA_EXCEEDED") {
336
351
  console.log(`Plan limit: ${error.message}`);
337
- console.log(`Request ID: ${error.requestId ?? 'not provided'}`);
352
+ console.log(`Request ID: ${error.requestId ?? "not provided"}`);
338
353
  // Present only if this server response included a nested `details` object.
339
- if (error.details) console.log('Details:', error.details);
354
+ if (error.details) console.log("Details:", error.details);
340
355
  }
341
356
  }
342
357
  ```
@@ -354,13 +369,13 @@ import {
354
369
  ValidationError,
355
370
  AuthorizationError,
356
371
  isSpooledError,
357
- } from '@spooled/sdk';
372
+ } from "@spooled/sdk";
358
373
 
359
374
  try {
360
- await client.jobs.get('non-existent');
375
+ await client.jobs.get("non-existent");
361
376
  } catch (error) {
362
377
  if (error instanceof NotFoundError) {
363
- console.log('Job not found');
378
+ console.log("Job not found");
364
379
  } else if (error instanceof AuthorizationError) {
365
380
  console.log(`Forbidden: ${error.message}`);
366
381
  } else if (error instanceof RateLimitError) {
@@ -377,37 +392,37 @@ try {
377
392
  For high-throughput workers, use the native gRPC API with the included gRPC client:
378
393
 
379
394
  ```typescript
380
- import { SpooledGrpcClient } from '@spooled/sdk';
395
+ import { SpooledGrpcClient } from "@spooled/sdk";
381
396
 
382
397
  // Connect to gRPC server (Optimized for Cloudflare Tunnel)
383
398
  // useTls: true if connecting to grpc.spooled.cloud:443 (Cloudflare terminates TLS)
384
399
  // useTls: false if connecting to localhost or direct backend
385
400
  const grpcClient = new SpooledGrpcClient({
386
- address: 'grpc.spooled.cloud:443',
387
- apiKey: 'sp_live_your_key',
388
- useTls: true
401
+ address: "grpc.spooled.cloud:443",
402
+ apiKey: "sp_live_your_key",
403
+ useTls: true,
389
404
  });
390
405
 
391
406
  // Register worker
392
407
  const { workerId } = await grpcClient.workers.register({
393
- queueName: 'emails',
394
- hostname: 'worker-1',
395
- maxConcurrency: 10
408
+ queueName: "emails",
409
+ hostname: "worker-1",
410
+ maxConcurrency: 10,
396
411
  });
397
412
 
398
413
  // Enqueue job (enforces plan limits automatically)
399
414
  const { jobId } = await grpcClient.queue.enqueue({
400
- queueName: 'emails',
401
- payload: { to: 'user@example.com' },
402
- priority: 5
415
+ queueName: "emails",
416
+ payload: { to: "user@example.com" },
417
+ priority: 5,
403
418
  });
404
419
 
405
420
  // Dequeue jobs in batch
406
421
  const { jobs } = await grpcClient.queue.dequeue({
407
- queueName: 'emails',
422
+ queueName: "emails",
408
423
  workerId,
409
424
  batchSize: 10,
410
- leaseDurationSecs: 300
425
+ leaseDurationSecs: 300,
411
426
  });
412
427
 
413
428
  // Process and complete. Echo leaseId when present so current servers can fence stale executions; legacy servers may omit it and then use worker-ID-only fencing.
@@ -417,12 +432,13 @@ for (const job of jobs) {
417
432
  jobId: job.id,
418
433
  workerId,
419
434
  leaseId: job.leaseId ?? undefined,
420
- result: { success: true }
435
+ result: { success: true },
421
436
  });
422
437
  }
423
438
  ```
424
439
 
425
440
  **gRPC Features:**
441
+
426
442
  - **~28x faster** than HTTP (with Redis cache: ~50ms vs 1400ms)
427
443
  - **Automatic plan limit enforcement** on enqueue operations
428
444
  - **Batch operations** for higher throughput
@@ -438,12 +454,18 @@ Full TypeScript support with comprehensive type definitions:
438
454
  ```typescript
439
455
  import type {
440
456
  SpooledClientConfig,
441
- Job, JobStatus, CreateJobParams,
442
- Queue, QueueStats,
443
- Schedule, CreateScheduleParams,
444
- Workflow, CreateWorkflowParams,
445
- Worker, WorkerStatus,
446
- } from '@spooled/sdk';
457
+ Job,
458
+ JobStatus,
459
+ CreateJobParams,
460
+ Queue,
461
+ QueueStats,
462
+ Schedule,
463
+ CreateScheduleParams,
464
+ Workflow,
465
+ CreateWorkflowParams,
466
+ Worker,
467
+ WorkerStatus,
468
+ } from "@spooled/sdk";
447
469
  ```
448
470
 
449
471
  ## Development and releases