@spooled/sdk 1.0.9 → 1.0.10
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 +94 -431
- package/dist/{index-GVY6uW_o.d.cts → index-4Qr6pGB3.d.cts} +2 -2
- package/dist/{index-GVY6uW_o.d.ts → index-4Qr6pGB3.d.ts} +2 -2
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- 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 +1 -1
package/README.md
CHANGED
|
@@ -7,13 +7,15 @@ Official Node.js/TypeScript SDK for [Spooled Cloud](https://spooled.cloud) - a m
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
10
|
+
- **Full TypeScript support** with comprehensive type definitions
|
|
11
|
+
- **ESM and CommonJS** dual package support
|
|
12
|
+
- **Automatic retries** with exponential backoff and jitter
|
|
13
|
+
- **Circuit breaker** for fault tolerance
|
|
14
|
+
- **Realtime events** via WebSocket and SSE
|
|
15
|
+
- **Worker runtime** for processing jobs
|
|
16
|
+
- **Workflow DAGs** for complex job dependencies
|
|
17
|
+
- **gRPC streaming** for high-performance workers
|
|
18
|
+
- **Automatic case conversion** between camelCase (SDK) and snake_case (API)
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -26,7 +28,6 @@ npm install @spooled/sdk
|
|
|
26
28
|
```typescript
|
|
27
29
|
import { SpooledClient } from '@spooled/sdk';
|
|
28
30
|
|
|
29
|
-
// Create a client
|
|
30
31
|
const client = new SpooledClient({
|
|
31
32
|
apiKey: 'sk_live_your_api_key'
|
|
32
33
|
});
|
|
@@ -50,401 +51,147 @@ const job = await client.jobs.get(id);
|
|
|
50
51
|
console.log(`Status: ${job.status}`);
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
##
|
|
54
|
+
## Documentation
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
timeout: 30000, // Request timeout (ms)
|
|
64
|
-
|
|
65
|
-
// Retry settings
|
|
66
|
-
retries: 3, // Max retry attempts
|
|
67
|
-
retryDelay: 1000, // Base delay (ms)
|
|
68
|
-
retry: {
|
|
69
|
-
maxRetries: 3,
|
|
70
|
-
baseDelay: 1000,
|
|
71
|
-
maxDelay: 30000,
|
|
72
|
-
factor: 2, // Exponential factor
|
|
73
|
-
jitter: true // Add randomness
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
// Circuit breaker
|
|
77
|
-
circuitBreaker: {
|
|
78
|
-
enabled: true,
|
|
79
|
-
failureThreshold: 5, // Failures to open
|
|
80
|
-
successThreshold: 3, // Successes to close
|
|
81
|
-
timeout: 30000 // Reset timeout (ms)
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
// Advanced
|
|
85
|
-
headers: { 'X-Custom': 'header' },
|
|
86
|
-
fetch: customFetch, // Custom fetch impl
|
|
87
|
-
debug: true // Enable logging
|
|
88
|
-
});
|
|
89
|
-
```
|
|
56
|
+
| Guide | Description |
|
|
57
|
+
|-------|-------------|
|
|
58
|
+
| [Getting Started](docs/getting-started.md) | Installation, setup, and first job |
|
|
59
|
+
| [Configuration](docs/configuration.md) | All configuration options |
|
|
60
|
+
| [Workers](docs/workers.md) | Job processing with SpooledWorker |
|
|
61
|
+
| [Workflows](docs/workflows.md) | Building DAGs with job dependencies |
|
|
62
|
+
| [gRPC](docs/grpc.md) | High-performance streaming |
|
|
63
|
+
| [Resources](docs/resources.md) | Complete API reference |
|
|
90
64
|
|
|
91
|
-
##
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
See the [`examples/`](examples/) directory for runnable code:
|
|
68
|
+
|
|
69
|
+
| Example | Description |
|
|
70
|
+
|---------|-------------|
|
|
71
|
+
| [`quick-start.ts`](examples/quick-start.ts) | Basic SDK usage |
|
|
72
|
+
| [`worker.ts`](examples/worker.ts) | Processing jobs with SpooledWorker |
|
|
73
|
+
| [`workflow-dag.ts`](examples/workflow-dag.ts) | Complex workflows with dependencies |
|
|
74
|
+
| [`grpc-streaming.ts`](examples/grpc-streaming.ts) | High-performance gRPC streaming |
|
|
75
|
+
| [`realtime.ts`](examples/realtime.ts) | Real-time event streaming |
|
|
76
|
+
| [`schedules.ts`](examples/schedules.ts) | Cron schedules |
|
|
77
|
+
| [`error-handling.ts`](examples/error-handling.ts) | Error handling patterns |
|
|
78
|
+
|
|
79
|
+
## Core Concepts
|
|
92
80
|
|
|
93
81
|
### Jobs
|
|
94
82
|
|
|
83
|
+
Jobs are units of work with payloads, priorities, and retry policies:
|
|
84
|
+
|
|
95
85
|
```typescript
|
|
96
|
-
|
|
97
|
-
const { id, created } = await client.jobs.create({
|
|
86
|
+
const { id } = await client.jobs.create({
|
|
98
87
|
queueName: 'my-queue',
|
|
99
88
|
payload: { data: 'value' },
|
|
100
89
|
priority: 5, // -100 to 100
|
|
101
90
|
maxRetries: 3,
|
|
102
91
|
timeoutSeconds: 300,
|
|
103
|
-
scheduledAt: new Date(Date.now() + 60000),
|
|
104
|
-
idempotencyKey: 'unique-key',
|
|
105
|
-
tags: { env: 'production' }
|
|
92
|
+
scheduledAt: new Date(Date.now() + 60000),
|
|
93
|
+
idempotencyKey: 'unique-key',
|
|
106
94
|
});
|
|
95
|
+
```
|
|
107
96
|
|
|
108
|
-
|
|
109
|
-
const jobs = await client.jobs.list({
|
|
110
|
-
queueName: 'my-queue',
|
|
111
|
-
status: 'pending',
|
|
112
|
-
limit: 10,
|
|
113
|
-
offset: 0
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Get job details
|
|
117
|
-
const job = await client.jobs.get(id);
|
|
118
|
-
|
|
119
|
-
// Cancel a job
|
|
120
|
-
await client.jobs.cancel(id);
|
|
97
|
+
### Workers
|
|
121
98
|
|
|
122
|
-
|
|
123
|
-
await client.jobs.retry(id);
|
|
99
|
+
Process jobs with the built-in worker runtime:
|
|
124
100
|
|
|
125
|
-
|
|
126
|
-
|
|
101
|
+
```typescript
|
|
102
|
+
import { SpooledClient, SpooledWorker } from '@spooled/sdk';
|
|
127
103
|
|
|
128
|
-
|
|
129
|
-
const stats = await client.jobs.getStats();
|
|
104
|
+
const client = new SpooledClient({ apiKey: 'sk_live_...' });
|
|
130
105
|
|
|
131
|
-
|
|
132
|
-
const result = await client.jobs.bulkEnqueue({
|
|
106
|
+
const worker = new SpooledWorker(client, {
|
|
133
107
|
queueName: 'my-queue',
|
|
134
|
-
|
|
135
|
-
{ payload: { n: 1 } },
|
|
136
|
-
{ payload: { n: 2 } },
|
|
137
|
-
{ payload: { n: 3 } }
|
|
138
|
-
]
|
|
108
|
+
concurrency: 10,
|
|
139
109
|
});
|
|
140
110
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const dlqJobs = await client.jobs.dlq.list();
|
|
146
|
-
await client.jobs.dlq.retry({ jobIds: ['id1', 'id2'] });
|
|
147
|
-
await client.jobs.dlq.purge({ confirm: true });
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Queues
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
// List queues
|
|
154
|
-
const queues = await client.queues.list();
|
|
155
|
-
|
|
156
|
-
// Get queue config
|
|
157
|
-
const config = await client.queues.get('my-queue');
|
|
158
|
-
|
|
159
|
-
// Update queue config
|
|
160
|
-
await client.queues.updateConfig('my-queue', {
|
|
161
|
-
maxRetries: 5,
|
|
162
|
-
defaultTimeout: 600,
|
|
163
|
-
rateLimit: 100
|
|
111
|
+
worker.process(async (ctx) => {
|
|
112
|
+
console.log(`Processing job ${ctx.jobId}`);
|
|
113
|
+
await doSomething(ctx.payload);
|
|
114
|
+
return { success: true };
|
|
164
115
|
});
|
|
165
116
|
|
|
166
|
-
|
|
167
|
-
const stats = await client.queues.getStats('my-queue');
|
|
117
|
+
await worker.start();
|
|
168
118
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
await client.queues.resume('my-queue');
|
|
119
|
+
// Graceful shutdown
|
|
120
|
+
process.on('SIGTERM', () => worker.stop());
|
|
172
121
|
```
|
|
173
122
|
|
|
174
|
-
###
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
// Create a schedule
|
|
178
|
-
const schedule = await client.schedules.create({
|
|
179
|
-
name: 'Daily Report',
|
|
180
|
-
cronExpression: '0 0 9 * * *', // 6-field cron
|
|
181
|
-
timezone: 'America/New_York',
|
|
182
|
-
queueName: 'reports',
|
|
183
|
-
payloadTemplate: { type: 'daily' }
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// List schedules
|
|
187
|
-
const schedules = await client.schedules.list();
|
|
188
|
-
|
|
189
|
-
// Get/update/delete
|
|
190
|
-
const s = await client.schedules.get(schedule.id);
|
|
191
|
-
await client.schedules.update(schedule.id, { cronExpression: '0 0 8 * * *' });
|
|
192
|
-
await client.schedules.delete(schedule.id);
|
|
193
|
-
|
|
194
|
-
// Pause/resume
|
|
195
|
-
await client.schedules.pause(schedule.id);
|
|
196
|
-
await client.schedules.resume(schedule.id);
|
|
123
|
+
### Workflows (DAGs)
|
|
197
124
|
|
|
198
|
-
|
|
199
|
-
const { jobId } = await client.schedules.trigger(schedule.id);
|
|
200
|
-
|
|
201
|
-
// Get history
|
|
202
|
-
const runs = await client.schedules.getHistory(schedule.id, 10);
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### Workflows
|
|
125
|
+
Orchestrate multiple jobs with dependencies:
|
|
206
126
|
|
|
207
127
|
```typescript
|
|
208
|
-
// Create a workflow with dependencies
|
|
209
128
|
const workflow = await client.workflows.create({
|
|
210
129
|
name: 'ETL Pipeline',
|
|
211
130
|
jobs: [
|
|
212
131
|
{ key: 'extract', queueName: 'etl', payload: { step: 'extract' } },
|
|
213
132
|
{ key: 'transform', queueName: 'etl', payload: { step: 'transform' }, dependsOn: ['extract'] },
|
|
214
|
-
{ key: 'load', queueName: 'etl', payload: { step: 'load' }, dependsOn: ['transform'] }
|
|
215
|
-
]
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// Get workflow status
|
|
219
|
-
const status = await client.workflows.get(workflow.workflowId);
|
|
220
|
-
|
|
221
|
-
// Cancel workflow
|
|
222
|
-
await client.workflows.cancel(workflow.workflowId);
|
|
223
|
-
|
|
224
|
-
// Job dependencies
|
|
225
|
-
const deps = await client.workflows.jobs.getDependencies(jobId);
|
|
226
|
-
await client.workflows.jobs.addDependencies(jobId, { dependsOnJobIds: ['other-job'] });
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Webhooks
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
// Create outgoing webhook
|
|
233
|
-
const webhook = await client.webhooks.create({
|
|
234
|
-
name: 'Slack Notifications',
|
|
235
|
-
url: 'https://hooks.slack.com/...',
|
|
236
|
-
events: ['job.completed', 'job.failed'],
|
|
237
|
-
secret: 'hmac-secret'
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// List/get/update/delete
|
|
241
|
-
const webhooks = await client.webhooks.list();
|
|
242
|
-
const wh = await client.webhooks.get(webhook.id);
|
|
243
|
-
await client.webhooks.update(webhook.id, { enabled: false });
|
|
244
|
-
await client.webhooks.delete(webhook.id);
|
|
245
|
-
|
|
246
|
-
// Test webhook
|
|
247
|
-
const result = await client.webhooks.test(webhook.id);
|
|
248
|
-
|
|
249
|
-
// Get delivery history
|
|
250
|
-
const deliveries = await client.webhooks.getDeliveries(webhook.id);
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### API Keys
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
// List keys (sensitive data hidden)
|
|
257
|
-
const keys = await client.apiKeys.list();
|
|
258
|
-
|
|
259
|
-
// Create new key (key shown only once!)
|
|
260
|
-
const { id, key } = await client.apiKeys.create({
|
|
261
|
-
name: 'Production API Key',
|
|
262
|
-
queues: ['queue-1', 'queue-2'], // Optional: restrict to queues
|
|
263
|
-
rateLimit: 1000
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// Update/revoke
|
|
267
|
-
await client.apiKeys.update(id, { name: 'Updated Name' });
|
|
268
|
-
await client.apiKeys.revoke(id);
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Organizations
|
|
272
|
-
|
|
273
|
-
```typescript
|
|
274
|
-
// Create organization (returns initial API key)
|
|
275
|
-
const { organization, apiKey } = await client.organizations.create({
|
|
276
|
-
name: 'My Company',
|
|
277
|
-
slug: 'my-company',
|
|
278
|
-
billingEmail: 'billing@company.com'
|
|
133
|
+
{ key: 'load', queueName: 'etl', payload: { step: 'load' }, dependsOn: ['transform'] },
|
|
134
|
+
],
|
|
279
135
|
});
|
|
280
|
-
|
|
281
|
-
// Get usage and limits
|
|
282
|
-
const usage = await client.organizations.getUsage();
|
|
283
|
-
console.log(`Plan: ${usage.plan} (${usage.planDisplayName})`);
|
|
284
|
-
console.log(`Jobs today: ${usage.usage.jobsToday.current}/${usage.usage.jobsToday.limit ?? 'unlimited'}`);
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### Billing
|
|
288
|
-
|
|
289
|
-
```typescript
|
|
290
|
-
// Get billing status (Stripe subscription info)
|
|
291
|
-
const status = await client.billing.getStatus();
|
|
292
|
-
console.log(status.planTier, status.hasStripeCustomer);
|
|
293
|
-
|
|
294
|
-
// Create Stripe billing portal session
|
|
295
|
-
const portal = await client.billing.createPortal({
|
|
296
|
-
returnUrl: 'https://spooled.cloud/dashboard/billing'
|
|
297
|
-
});
|
|
298
|
-
console.log('Portal URL:', portal.url);
|
|
299
136
|
```
|
|
300
137
|
|
|
301
|
-
###
|
|
302
|
-
|
|
303
|
-
```typescript
|
|
304
|
-
const dashboard = await client.dashboard.get();
|
|
305
|
-
console.log('Jobs pending:', dashboard.jobs.pending);
|
|
306
|
-
console.log('Queues:', dashboard.queues.length);
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Health & Metrics
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
const health = await client.health.get();
|
|
313
|
-
console.log(health.status, health.database, health.cache);
|
|
314
|
-
|
|
315
|
-
const ready = await client.health.readiness();
|
|
316
|
-
console.log('Ready:', ready);
|
|
317
|
-
|
|
318
|
-
const metrics = await client.metrics.get();
|
|
319
|
-
console.log(metrics.slice(0, 200));
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Admin API
|
|
323
|
-
|
|
324
|
-
```typescript
|
|
325
|
-
const adminClient = new SpooledClient({
|
|
326
|
-
apiKey: 'sk_live_...',
|
|
327
|
-
adminKey: 'admin_...'
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
const orgs = await adminClient.admin.listOrganizations({ planTier: 'pro', limit: 10 });
|
|
331
|
-
console.log(orgs.total);
|
|
332
|
-
|
|
333
|
-
const stats = await adminClient.admin.getStats();
|
|
334
|
-
console.log(stats.organizations.total);
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### Webhook Ingestion
|
|
338
|
-
|
|
339
|
-
These endpoints are primarily for webhook providers (GitHub/Stripe/custom) and are **signature-based** (not API key based).
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
await client.ingest.custom('org_123', {
|
|
343
|
-
queueName: 'custom_events',
|
|
344
|
-
eventType: 'custom.event',
|
|
345
|
-
payload: { hello: 'world' },
|
|
346
|
-
});
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
## Worker Runtime
|
|
138
|
+
### Schedules
|
|
350
139
|
|
|
351
|
-
|
|
140
|
+
Run jobs on a cron schedule:
|
|
352
141
|
|
|
353
142
|
```typescript
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
concurrency: 10, // Process up to 10 jobs simultaneously
|
|
361
|
-
pollInterval: 1000, // Poll every second
|
|
362
|
-
leaseDuration: 30, // 30 second lease
|
|
363
|
-
shutdownTimeout: 30000 // Wait 30s for graceful shutdown
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// Define job handler
|
|
367
|
-
worker.process(async (ctx) => {
|
|
368
|
-
console.log(`Processing job ${ctx.jobId}`);
|
|
369
|
-
console.log('Payload:', ctx.payload);
|
|
370
|
-
|
|
371
|
-
// Check for shutdown signal
|
|
372
|
-
if (ctx.signal.aborted) {
|
|
373
|
-
throw new Error('Job aborted');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Do work...
|
|
377
|
-
await doSomething(ctx.payload);
|
|
378
|
-
|
|
379
|
-
// Return optional result
|
|
380
|
-
return { processed: true };
|
|
143
|
+
const schedule = await client.schedules.create({
|
|
144
|
+
name: 'Daily Report',
|
|
145
|
+
cronExpression: '0 0 9 * * *', // 6-field cron
|
|
146
|
+
timezone: 'America/New_York',
|
|
147
|
+
queueName: 'reports',
|
|
148
|
+
payloadTemplate: { type: 'daily' },
|
|
381
149
|
});
|
|
382
|
-
|
|
383
|
-
// Event handlers
|
|
384
|
-
worker.on('job:completed', ({ jobId }) => console.log(`Completed: ${jobId}`));
|
|
385
|
-
worker.on('job:failed', ({ jobId, error }) => console.error(`Failed: ${jobId}`, error));
|
|
386
|
-
|
|
387
|
-
// Start worker
|
|
388
|
-
await worker.start();
|
|
389
|
-
|
|
390
|
-
// Graceful shutdown
|
|
391
|
-
process.on('SIGTERM', () => worker.stop());
|
|
392
150
|
```
|
|
393
151
|
|
|
394
|
-
|
|
152
|
+
### Realtime Events
|
|
395
153
|
|
|
396
|
-
Subscribe to real-time events
|
|
154
|
+
Subscribe to real-time job events:
|
|
397
155
|
|
|
398
156
|
```typescript
|
|
399
157
|
const realtime = await client.realtime();
|
|
400
158
|
|
|
401
|
-
// Connection state
|
|
402
|
-
realtime.onStateChange((state) => {
|
|
403
|
-
console.log('State:', state); // 'connecting' | 'connected' | 'reconnecting' | 'disconnected'
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// Event handlers
|
|
407
|
-
realtime.on('job.created', (data) => {
|
|
408
|
-
console.log(`New job: ${data.jobId} in ${data.queueName}`);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
159
|
realtime.on('job.completed', (data) => {
|
|
412
|
-
console.log(`Job completed: ${data.jobId}
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
realtime.on('job.failed', (data) => {
|
|
416
|
-
console.log(`Job failed: ${data.jobId} - ${data.error}`);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// Listen to all events
|
|
420
|
-
realtime.onEvent((event) => {
|
|
421
|
-
console.log(event.type, event);
|
|
160
|
+
console.log(`Job completed: ${data.jobId}`);
|
|
422
161
|
});
|
|
423
162
|
|
|
424
|
-
// Connect and subscribe
|
|
425
163
|
await realtime.connect();
|
|
426
164
|
await realtime.subscribe({ queueName: 'my-queue' });
|
|
427
|
-
|
|
428
|
-
// Cleanup
|
|
429
|
-
realtime.disconnect();
|
|
430
165
|
```
|
|
431
166
|
|
|
432
|
-
|
|
167
|
+
## Error Handling
|
|
433
168
|
|
|
434
|
-
|
|
435
|
-
- **SSE**: `type: 'sse'`. In the current backend implementation, SSE queue/job endpoints periodically query the database, which can be heavier at scale. Use SSE primarily when WebSocket is not possible.
|
|
169
|
+
All errors extend `SpooledError` with specific subclasses:
|
|
436
170
|
|
|
437
171
|
```typescript
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
172
|
+
import {
|
|
173
|
+
NotFoundError,
|
|
174
|
+
RateLimitError,
|
|
175
|
+
ValidationError,
|
|
176
|
+
isSpooledError,
|
|
177
|
+
} from '@spooled/sdk';
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await client.jobs.get('non-existent');
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof NotFoundError) {
|
|
183
|
+
console.log('Job not found');
|
|
184
|
+
} else if (error instanceof RateLimitError) {
|
|
185
|
+
console.log(`Retry after ${error.getRetryAfter()} seconds`);
|
|
186
|
+
} else if (isSpooledError(error)) {
|
|
187
|
+
console.log(`API error: ${error.code} - ${error.message}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
441
190
|
```
|
|
442
191
|
|
|
443
192
|
## gRPC API
|
|
444
193
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
For high-throughput workers or streaming scenarios, consider using the native gRPC client with `@grpc/grpc-js`:
|
|
194
|
+
For high-throughput workers, use the native gRPC API:
|
|
448
195
|
|
|
449
196
|
```typescript
|
|
450
197
|
import * as grpc from '@grpc/grpc-js';
|
|
@@ -454,27 +201,18 @@ const packageDefinition = protoLoader.loadSync('spooled.proto');
|
|
|
454
201
|
const spooled = grpc.loadPackageDefinition(packageDefinition).spooled.v1;
|
|
455
202
|
|
|
456
203
|
const client = new spooled.QueueService(
|
|
457
|
-
'
|
|
204
|
+
'grpc.spooled.cloud:443',
|
|
458
205
|
grpc.credentials.createSsl()
|
|
459
206
|
);
|
|
460
207
|
|
|
461
|
-
// Add API key metadata to calls
|
|
462
208
|
const metadata = new grpc.Metadata();
|
|
463
209
|
metadata.add('x-api-key', 'sk_live_your_key');
|
|
464
210
|
|
|
465
|
-
|
|
466
|
-
queue_name: 'emails',
|
|
467
|
-
payload: { to: 'user@example.com' },
|
|
468
|
-
max_retries: 3
|
|
469
|
-
}, metadata, (err, response) => {
|
|
470
|
-
console.log('Job ID:', response.job_id);
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
// Streaming example
|
|
211
|
+
// Stream jobs
|
|
474
212
|
const stream = client.StreamJobs({
|
|
475
213
|
queue_name: 'emails',
|
|
476
214
|
worker_id: 'worker-1',
|
|
477
|
-
lease_duration_secs: 300
|
|
215
|
+
lease_duration_secs: 300,
|
|
478
216
|
}, metadata);
|
|
479
217
|
|
|
480
218
|
stream.on('data', (job) => {
|
|
@@ -482,72 +220,7 @@ stream.on('data', (job) => {
|
|
|
482
220
|
});
|
|
483
221
|
```
|
|
484
222
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
| Use Case | Recommended |
|
|
488
|
-
|----------|-------------|
|
|
489
|
-
| Web/mobile apps | REST API |
|
|
490
|
-
| Dashboard/admin | REST API |
|
|
491
|
-
| High-throughput workers | gRPC |
|
|
492
|
-
| Streaming job delivery | gRPC |
|
|
493
|
-
|
|
494
|
-
See [spooled-backend/proto/spooled.proto](https://github.com/spooled-cloud/spooled-backend/blob/main/proto/spooled.proto) for the full protocol definition.
|
|
495
|
-
## Error Handling
|
|
496
|
-
|
|
497
|
-
All errors extend `SpooledError` with specific subclasses:
|
|
498
|
-
|
|
499
|
-
```typescript
|
|
500
|
-
import {
|
|
501
|
-
SpooledError,
|
|
502
|
-
AuthenticationError, // 401
|
|
503
|
-
AuthorizationError, // 403
|
|
504
|
-
NotFoundError, // 404
|
|
505
|
-
ValidationError, // 400
|
|
506
|
-
ConflictError, // 409
|
|
507
|
-
RateLimitError, // 429
|
|
508
|
-
ServerError, // 5xx
|
|
509
|
-
NetworkError, // Network failure
|
|
510
|
-
TimeoutError, // Request timeout
|
|
511
|
-
CircuitBreakerOpenError,
|
|
512
|
-
isSpooledError
|
|
513
|
-
} from '@spooled/sdk';
|
|
514
|
-
|
|
515
|
-
try {
|
|
516
|
-
await client.jobs.get('non-existent');
|
|
517
|
-
} catch (error) {
|
|
518
|
-
if (error instanceof NotFoundError) {
|
|
519
|
-
console.log('Job not found');
|
|
520
|
-
} else if (error instanceof RateLimitError) {
|
|
521
|
-
console.log(`Retry after ${error.getRetryAfter()} seconds`);
|
|
522
|
-
} else if (isSpooledError(error)) {
|
|
523
|
-
console.log(`API error: ${error.code} - ${error.message}`);
|
|
524
|
-
console.log('Retryable:', error.isRetryable());
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
## Case Conversion
|
|
530
|
-
|
|
531
|
-
The SDK automatically converts between camelCase (SDK) and snake_case (API):
|
|
532
|
-
|
|
533
|
-
```typescript
|
|
534
|
-
// You write camelCase
|
|
535
|
-
await client.jobs.create({
|
|
536
|
-
queueName: 'my-queue',
|
|
537
|
-
maxRetries: 3,
|
|
538
|
-
timeoutSeconds: 300
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// API receives snake_case
|
|
542
|
-
// { queue_name: 'my-queue', max_retries: 3, timeout_seconds: 300 }
|
|
543
|
-
|
|
544
|
-
// Response is converted back
|
|
545
|
-
const job = await client.jobs.get(id);
|
|
546
|
-
console.log(job.queueName); // camelCase
|
|
547
|
-
console.log(job.maxRetries); // camelCase
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
**Important:** User-defined JSON blobs (`payload`, `result`, `metadata`, `tags`) are preserved as-is and not converted.
|
|
223
|
+
See [gRPC Guide](docs/grpc.md) for complete documentation.
|
|
551
224
|
|
|
552
225
|
## TypeScript Support
|
|
553
226
|
|
|
@@ -560,20 +233,10 @@ import type {
|
|
|
560
233
|
Queue, QueueStats,
|
|
561
234
|
Schedule, CreateScheduleParams,
|
|
562
235
|
Workflow, CreateWorkflowParams,
|
|
563
|
-
Worker, WorkerStatus
|
|
236
|
+
Worker, WorkerStatus,
|
|
564
237
|
} from '@spooled/sdk';
|
|
565
238
|
```
|
|
566
239
|
|
|
567
|
-
## Examples
|
|
568
|
-
|
|
569
|
-
See the [`examples/`](examples/) directory for runnable examples:
|
|
570
|
-
|
|
571
|
-
- [`quick-start.ts`](examples/quick-start.ts) - Basic usage
|
|
572
|
-
- [`worker.ts`](examples/worker.ts) - Processing jobs
|
|
573
|
-
- [`realtime.ts`](examples/realtime.ts) - Event streaming
|
|
574
|
-
- [`schedules.ts`](examples/schedules.ts) - Cron schedules
|
|
575
|
-
- [`error-handling.ts`](examples/error-handling.ts) - Error handling patterns
|
|
576
|
-
|
|
577
240
|
## License
|
|
578
241
|
|
|
579
242
|
Apache-2.0 - see [LICENSE](LICENSE) for details.
|
|
@@ -103,11 +103,11 @@ declare const DEFAULT_CONFIG: {
|
|
|
103
103
|
successThreshold: number;
|
|
104
104
|
timeout: number;
|
|
105
105
|
};
|
|
106
|
-
readonly userAgent: "@spooled/sdk-nodejs/1.0.
|
|
106
|
+
readonly userAgent: "@spooled/sdk-nodejs/1.0.10";
|
|
107
107
|
readonly autoRefreshToken: true;
|
|
108
108
|
};
|
|
109
109
|
/** SDK version */
|
|
110
|
-
declare const SDK_VERSION = "1.0.
|
|
110
|
+
declare const SDK_VERSION = "1.0.10";
|
|
111
111
|
/** API version prefix */
|
|
112
112
|
declare const API_VERSION = "v1";
|
|
113
113
|
/**
|
|
@@ -103,11 +103,11 @@ declare const DEFAULT_CONFIG: {
|
|
|
103
103
|
successThreshold: number;
|
|
104
104
|
timeout: number;
|
|
105
105
|
};
|
|
106
|
-
readonly userAgent: "@spooled/sdk-nodejs/1.0.
|
|
106
|
+
readonly userAgent: "@spooled/sdk-nodejs/1.0.10";
|
|
107
107
|
readonly autoRefreshToken: true;
|
|
108
108
|
};
|
|
109
109
|
/** SDK version */
|
|
110
|
-
declare const SDK_VERSION = "1.0.
|
|
110
|
+
declare const SDK_VERSION = "1.0.10";
|
|
111
111
|
/** API version prefix */
|
|
112
112
|
declare const API_VERSION = "v1";
|
|
113
113
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -55,10 +55,10 @@ var DEFAULT_CONFIG = {
|
|
|
55
55
|
successThreshold: 3,
|
|
56
56
|
timeout: 3e4
|
|
57
57
|
},
|
|
58
|
-
userAgent: "@spooled/sdk-nodejs/1.0.
|
|
58
|
+
userAgent: "@spooled/sdk-nodejs/1.0.10",
|
|
59
59
|
autoRefreshToken: true
|
|
60
60
|
};
|
|
61
|
-
var SDK_VERSION = "1.0.
|
|
61
|
+
var SDK_VERSION = "1.0.10";
|
|
62
62
|
var API_VERSION = "v1";
|
|
63
63
|
var API_BASE_PATH = `/api/${API_VERSION}`;
|
|
64
64
|
function resolveConfig(options) {
|
|
@@ -2933,7 +2933,7 @@ var SpooledWorker = class {
|
|
|
2933
2933
|
...DEFAULT_OPTIONS,
|
|
2934
2934
|
hostname: os.hostname(),
|
|
2935
2935
|
workerType: "nodejs",
|
|
2936
|
-
version: "1.0.
|
|
2936
|
+
version: "1.0.10",
|
|
2937
2937
|
metadata: {},
|
|
2938
2938
|
...options
|
|
2939
2939
|
};
|