@spooled/sdk 1.0.12 → 1.0.13
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 +206 -19
- package/dist/{index-ZwiiavfA.d.cts → index-C6RMeNDM.d.cts} +28 -2
- package/dist/{index-ZwiiavfA.d.ts → index-C6RMeNDM.d.ts} +28 -2
- package/dist/index.cjs +31 -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 +31 -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
|
@@ -14,7 +14,13 @@ Official Node.js/TypeScript SDK for [Spooled Cloud](https://spooled.cloud) - a m
|
|
|
14
14
|
- **Realtime events** via WebSocket and SSE
|
|
15
15
|
- **Worker runtime** for processing jobs
|
|
16
16
|
- **Workflow DAGs** for complex job dependencies
|
|
17
|
-
- **gRPC streaming** for high-performance workers
|
|
17
|
+
- **gRPC streaming** for high-performance workers (with automatic limit enforcement)
|
|
18
|
+
- **Tier-based limits** with automatic enforcement across all endpoints
|
|
19
|
+
- **Organization management** with usage tracking
|
|
20
|
+
- **Webhook management** with automatic retries
|
|
21
|
+
- **Dead Letter Queue (DLQ)** operations
|
|
22
|
+
- **API Key management** with Redis caching (~28x faster authentication)
|
|
23
|
+
- **Billing integration** via Stripe
|
|
18
24
|
- **Automatic case conversion** between camelCase (SDK) and snake_case (API)
|
|
19
25
|
|
|
20
26
|
## Installation
|
|
@@ -165,6 +171,161 @@ await realtime.connect();
|
|
|
165
171
|
await realtime.subscribe({ queueName: 'my-queue' });
|
|
166
172
|
```
|
|
167
173
|
|
|
174
|
+
### Organization Management
|
|
175
|
+
|
|
176
|
+
Manage your organization and track usage:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Get current usage and limits
|
|
180
|
+
const usage = await client.organizations.getUsage();
|
|
181
|
+
console.log(`Active jobs: ${usage.active_jobs.current}/${usage.active_jobs.limit}`);
|
|
182
|
+
console.log(`Plan: ${usage.plan.tier}`);
|
|
183
|
+
|
|
184
|
+
// Generate a unique slug for a new organization
|
|
185
|
+
const { slug } = await client.organizations.generateSlug('My Company');
|
|
186
|
+
|
|
187
|
+
// Check if a slug is available
|
|
188
|
+
const { available } = await client.organizations.checkSlug('my-company');
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Webhooks
|
|
192
|
+
|
|
193
|
+
Configure outgoing webhooks for job events:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Create webhook
|
|
197
|
+
const webhook = await client.webhooks.create({
|
|
198
|
+
url: 'https://your-app.com/webhooks/spooled',
|
|
199
|
+
events: ['job.completed', 'job.failed'],
|
|
200
|
+
queueName: 'my-queue',
|
|
201
|
+
secret: 'webhook_secret_key'
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Retry a failed delivery
|
|
205
|
+
await client.webhooks.retryDelivery(webhookId, deliveryId);
|
|
206
|
+
|
|
207
|
+
// Get delivery history
|
|
208
|
+
const deliveries = await client.webhooks.getDeliveries(webhookId);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Dead Letter Queue (DLQ)
|
|
212
|
+
|
|
213
|
+
Manage jobs that have exhausted all retries:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// List DLQ jobs
|
|
217
|
+
const dlqJobs = await client.jobs.dlq.list({ limit: 100 });
|
|
218
|
+
|
|
219
|
+
// Retry specific jobs from DLQ
|
|
220
|
+
await client.jobs.dlq.retry({
|
|
221
|
+
jobIds: ['job-1', 'job-2'],
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Retry jobs by queue
|
|
225
|
+
await client.jobs.dlq.retry({
|
|
226
|
+
queueName: 'my-queue',
|
|
227
|
+
limit: 50
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Purge DLQ (requires confirmation)
|
|
231
|
+
await client.jobs.dlq.purge({
|
|
232
|
+
queueName: 'my-queue',
|
|
233
|
+
confirm: true
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### API Key Management
|
|
238
|
+
|
|
239
|
+
Manage API keys programmatically:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// Create a new API key
|
|
243
|
+
const apiKey = await client.apiKeys.create({
|
|
244
|
+
name: 'Production Worker',
|
|
245
|
+
queues: ['emails', 'notifications'],
|
|
246
|
+
rateLimit: 1000
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
console.log(`Save this key: ${apiKey.key}`); // Only shown once!
|
|
250
|
+
|
|
251
|
+
// List all API keys
|
|
252
|
+
const keys = await client.apiKeys.list();
|
|
253
|
+
|
|
254
|
+
// Revoke a key
|
|
255
|
+
await client.apiKeys.revoke(keyId);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Billing & Subscriptions
|
|
259
|
+
|
|
260
|
+
Manage billing via Stripe integration:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// Get billing status
|
|
264
|
+
const status = await client.billing.getStatus();
|
|
265
|
+
|
|
266
|
+
// Create customer portal session
|
|
267
|
+
const { url } = await client.billing.createPortal({
|
|
268
|
+
returnUrl: 'https://your-app.com/settings'
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Redirect user to: url
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Authentication
|
|
275
|
+
|
|
276
|
+
Email-based passwordless authentication:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Start email login flow
|
|
280
|
+
await client.auth.startEmailLogin('user@example.com');
|
|
281
|
+
// User receives email with login link
|
|
282
|
+
|
|
283
|
+
// Check if email exists
|
|
284
|
+
const { exists } = await client.auth.checkEmail('user@example.com');
|
|
285
|
+
|
|
286
|
+
// Exchange API key for JWT
|
|
287
|
+
const { accessToken, refreshToken } = await client.auth.login({
|
|
288
|
+
apiKey: 'sk_live_...'
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Use JWT for subsequent requests
|
|
292
|
+
const jwtClient = new SpooledClient({ accessToken });
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Plan Limits
|
|
296
|
+
|
|
297
|
+
All operations automatically enforce tier-based limits:
|
|
298
|
+
|
|
299
|
+
| Tier | Active Jobs | Daily Jobs | Queues | Workers | Webhooks |
|
|
300
|
+
|------|-------------|------------|--------|---------|----------|
|
|
301
|
+
| **Free** | 10 | 1,000 | 5 | 3 | 2 |
|
|
302
|
+
| **Starter** | 100 | 100,000 | 25 | 25 | 10 |
|
|
303
|
+
| **Enterprise** | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited |
|
|
304
|
+
|
|
305
|
+
**Limits are enforced on:**
|
|
306
|
+
- ✅ HTTP job creation (`POST /jobs`, `POST /jobs/bulk`)
|
|
307
|
+
- ✅ gRPC job enqueue
|
|
308
|
+
- ✅ Workflow creation (counts all jobs in workflow)
|
|
309
|
+
- ✅ Schedule triggers
|
|
310
|
+
- ✅ DLQ retry operations
|
|
311
|
+
- ✅ Worker registration
|
|
312
|
+
- ✅ Queue creation
|
|
313
|
+
- ✅ Webhook creation
|
|
314
|
+
|
|
315
|
+
When limits are exceeded, you'll receive a `403 Forbidden` response with details:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
try {
|
|
319
|
+
await client.jobs.create({ /* ... */ });
|
|
320
|
+
} catch (error) {
|
|
321
|
+
if (error.statusCode === 403 && error.code === 'limit_exceeded') {
|
|
322
|
+
console.log(`Limit: ${error.message}`);
|
|
323
|
+
console.log(`Current: ${error.current}, Max: ${error.limit}`);
|
|
324
|
+
console.log(`Upgrade to: ${error.upgradeTo}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
168
329
|
## Error Handling
|
|
169
330
|
|
|
170
331
|
All errors extend `SpooledError` with specific subclasses:
|
|
@@ -174,6 +335,7 @@ import {
|
|
|
174
335
|
NotFoundError,
|
|
175
336
|
RateLimitError,
|
|
176
337
|
ValidationError,
|
|
338
|
+
LimitExceededError,
|
|
177
339
|
isSpooledError,
|
|
178
340
|
} from '@spooled/sdk';
|
|
179
341
|
|
|
@@ -182,6 +344,9 @@ try {
|
|
|
182
344
|
} catch (error) {
|
|
183
345
|
if (error instanceof NotFoundError) {
|
|
184
346
|
console.log('Job not found');
|
|
347
|
+
} else if (error instanceof LimitExceededError) {
|
|
348
|
+
console.log(`Plan limit: ${error.message}`);
|
|
349
|
+
console.log(`Upgrade to ${error.upgradeTo} for more capacity`);
|
|
185
350
|
} else if (error instanceof RateLimitError) {
|
|
186
351
|
console.log(`Retry after ${error.getRetryAfter()} seconds`);
|
|
187
352
|
} else if (isSpooledError(error)) {
|
|
@@ -192,35 +357,57 @@ try {
|
|
|
192
357
|
|
|
193
358
|
## gRPC API
|
|
194
359
|
|
|
195
|
-
For high-throughput workers, use the native gRPC API:
|
|
360
|
+
For high-throughput workers, use the native gRPC API with the included gRPC client:
|
|
196
361
|
|
|
197
362
|
```typescript
|
|
198
|
-
import
|
|
199
|
-
import * as protoLoader from '@grpc/proto-loader';
|
|
363
|
+
import { SpooledGrpcClient } from '@spooled/sdk';
|
|
200
364
|
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
const client = new spooled.QueueService(
|
|
365
|
+
// Connect to gRPC server
|
|
366
|
+
const grpcClient = new SpooledGrpcClient(
|
|
205
367
|
'grpc.spooled.cloud:443',
|
|
206
|
-
|
|
368
|
+
'sk_live_your_key'
|
|
207
369
|
);
|
|
208
370
|
|
|
209
|
-
|
|
210
|
-
|
|
371
|
+
// Register worker
|
|
372
|
+
const { workerId } = await grpcClient.workers.register({
|
|
373
|
+
queueName: 'emails',
|
|
374
|
+
hostname: 'worker-1',
|
|
375
|
+
maxConcurrency: 10
|
|
376
|
+
});
|
|
211
377
|
|
|
212
|
-
//
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
378
|
+
// Enqueue job (enforces plan limits automatically)
|
|
379
|
+
const { jobId } = await grpcClient.queue.enqueue({
|
|
380
|
+
queueName: 'emails',
|
|
381
|
+
payload: { to: 'user@example.com' },
|
|
382
|
+
priority: 5
|
|
383
|
+
});
|
|
218
384
|
|
|
219
|
-
|
|
220
|
-
|
|
385
|
+
// Dequeue jobs in batch
|
|
386
|
+
const { jobs } = await grpcClient.queue.dequeue({
|
|
387
|
+
queueName: 'emails',
|
|
388
|
+
workerId,
|
|
389
|
+
batchSize: 10,
|
|
390
|
+
leaseDurationSecs: 300
|
|
221
391
|
});
|
|
392
|
+
|
|
393
|
+
// Process and complete
|
|
394
|
+
for (const job of jobs) {
|
|
395
|
+
await processJob(job);
|
|
396
|
+
await grpcClient.queue.complete({
|
|
397
|
+
jobId: job.id,
|
|
398
|
+
workerId,
|
|
399
|
+
result: { success: true }
|
|
400
|
+
});
|
|
401
|
+
}
|
|
222
402
|
```
|
|
223
403
|
|
|
404
|
+
**gRPC Features:**
|
|
405
|
+
- ⚡ **~28x faster** than HTTP (with Redis cache: ~50ms vs 1400ms)
|
|
406
|
+
- 🛡️ **Automatic plan limit enforcement** on enqueue operations
|
|
407
|
+
- 📦 **Batch operations** for higher throughput
|
|
408
|
+
- 🔄 **Streaming support** for real-time job processing
|
|
409
|
+
- 🔐 **Secure authentication** via API key metadata
|
|
410
|
+
|
|
224
411
|
See [gRPC Guide](docs/grpc.md) for complete documentation.
|
|
225
412
|
|
|
226
413
|
## TypeScript Support
|
|
@@ -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.
|
|
110
|
+
readonly userAgent: "@spooled/sdk-nodejs/1.0.13";
|
|
111
111
|
readonly autoRefreshToken: true;
|
|
112
112
|
};
|
|
113
113
|
/** SDK version */
|
|
114
|
-
declare const SDK_VERSION = "1.0.
|
|
114
|
+
declare const SDK_VERSION = "1.0.13";
|
|
115
115
|
/** API version prefix */
|
|
116
116
|
declare const API_VERSION = "v1";
|
|
117
117
|
/**
|
|
@@ -422,6 +422,19 @@ declare class AuthResource {
|
|
|
422
422
|
* Validate a token
|
|
423
423
|
*/
|
|
424
424
|
validate(params: ValidateTokenParams): Promise<ValidateTokenResponse>;
|
|
425
|
+
/**
|
|
426
|
+
* Start email-based login flow (sends magic link)
|
|
427
|
+
*/
|
|
428
|
+
startEmailLogin(email: string): Promise<{
|
|
429
|
+
success: boolean;
|
|
430
|
+
message: string;
|
|
431
|
+
}>;
|
|
432
|
+
/**
|
|
433
|
+
* Check if an email address exists in the system
|
|
434
|
+
*/
|
|
435
|
+
checkEmail(email: string): Promise<{
|
|
436
|
+
exists: boolean;
|
|
437
|
+
}>;
|
|
425
438
|
}
|
|
426
439
|
|
|
427
440
|
/**
|
|
@@ -1388,6 +1401,13 @@ declare class WebhooksResource {
|
|
|
1388
1401
|
* Get webhook delivery history
|
|
1389
1402
|
*/
|
|
1390
1403
|
getDeliveries(id: string): Promise<OutgoingWebhookDelivery[]>;
|
|
1404
|
+
/**
|
|
1405
|
+
* Retry a specific webhook delivery
|
|
1406
|
+
*/
|
|
1407
|
+
retryDelivery(webhookId: string, deliveryId: string): Promise<{
|
|
1408
|
+
success: boolean;
|
|
1409
|
+
message: string;
|
|
1410
|
+
}>;
|
|
1391
1411
|
}
|
|
1392
1412
|
|
|
1393
1413
|
/**
|
|
@@ -1633,6 +1653,12 @@ declare class OrganizationsResource {
|
|
|
1633
1653
|
* Check slug availability
|
|
1634
1654
|
*/
|
|
1635
1655
|
checkSlug(slug: string): Promise<CheckSlugResponse>;
|
|
1656
|
+
/**
|
|
1657
|
+
* Generate a unique slug from an organization name
|
|
1658
|
+
*/
|
|
1659
|
+
generateSlug(name: string): Promise<{
|
|
1660
|
+
slug: string;
|
|
1661
|
+
}>;
|
|
1636
1662
|
}
|
|
1637
1663
|
|
|
1638
1664
|
/**
|
|
@@ -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.
|
|
110
|
+
readonly userAgent: "@spooled/sdk-nodejs/1.0.13";
|
|
111
111
|
readonly autoRefreshToken: true;
|
|
112
112
|
};
|
|
113
113
|
/** SDK version */
|
|
114
|
-
declare const SDK_VERSION = "1.0.
|
|
114
|
+
declare const SDK_VERSION = "1.0.13";
|
|
115
115
|
/** API version prefix */
|
|
116
116
|
declare const API_VERSION = "v1";
|
|
117
117
|
/**
|
|
@@ -422,6 +422,19 @@ declare class AuthResource {
|
|
|
422
422
|
* Validate a token
|
|
423
423
|
*/
|
|
424
424
|
validate(params: ValidateTokenParams): Promise<ValidateTokenResponse>;
|
|
425
|
+
/**
|
|
426
|
+
* Start email-based login flow (sends magic link)
|
|
427
|
+
*/
|
|
428
|
+
startEmailLogin(email: string): Promise<{
|
|
429
|
+
success: boolean;
|
|
430
|
+
message: string;
|
|
431
|
+
}>;
|
|
432
|
+
/**
|
|
433
|
+
* Check if an email address exists in the system
|
|
434
|
+
*/
|
|
435
|
+
checkEmail(email: string): Promise<{
|
|
436
|
+
exists: boolean;
|
|
437
|
+
}>;
|
|
425
438
|
}
|
|
426
439
|
|
|
427
440
|
/**
|
|
@@ -1388,6 +1401,13 @@ declare class WebhooksResource {
|
|
|
1388
1401
|
* Get webhook delivery history
|
|
1389
1402
|
*/
|
|
1390
1403
|
getDeliveries(id: string): Promise<OutgoingWebhookDelivery[]>;
|
|
1404
|
+
/**
|
|
1405
|
+
* Retry a specific webhook delivery
|
|
1406
|
+
*/
|
|
1407
|
+
retryDelivery(webhookId: string, deliveryId: string): Promise<{
|
|
1408
|
+
success: boolean;
|
|
1409
|
+
message: string;
|
|
1410
|
+
}>;
|
|
1391
1411
|
}
|
|
1392
1412
|
|
|
1393
1413
|
/**
|
|
@@ -1633,6 +1653,12 @@ declare class OrganizationsResource {
|
|
|
1633
1653
|
* Check slug availability
|
|
1634
1654
|
*/
|
|
1635
1655
|
checkSlug(slug: string): Promise<CheckSlugResponse>;
|
|
1656
|
+
/**
|
|
1657
|
+
* Generate a unique slug from an organization name
|
|
1658
|
+
*/
|
|
1659
|
+
generateSlug(name: string): Promise<{
|
|
1660
|
+
slug: string;
|
|
1661
|
+
}>;
|
|
1636
1662
|
}
|
|
1637
1663
|
|
|
1638
1664
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -56,10 +56,10 @@ var DEFAULT_CONFIG = {
|
|
|
56
56
|
successThreshold: 3,
|
|
57
57
|
timeout: 3e4
|
|
58
58
|
},
|
|
59
|
-
userAgent: "@spooled/sdk-nodejs/1.0.
|
|
59
|
+
userAgent: "@spooled/sdk-nodejs/1.0.13",
|
|
60
60
|
autoRefreshToken: true
|
|
61
61
|
};
|
|
62
|
-
var SDK_VERSION = "1.0.
|
|
62
|
+
var SDK_VERSION = "1.0.13";
|
|
63
63
|
var API_VERSION = "v1";
|
|
64
64
|
var API_BASE_PATH = `/api/${API_VERSION}`;
|
|
65
65
|
function resolveConfig(options) {
|
|
@@ -947,6 +947,20 @@ var AuthResource = class {
|
|
|
947
947
|
async validate(params) {
|
|
948
948
|
return this.http.post("/auth/validate", params);
|
|
949
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Start email-based login flow (sends magic link)
|
|
952
|
+
*/
|
|
953
|
+
async startEmailLogin(email) {
|
|
954
|
+
return this.http.post("/auth/email/start", { email });
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Check if an email address exists in the system
|
|
958
|
+
*/
|
|
959
|
+
async checkEmail(email) {
|
|
960
|
+
return this.http.get("/auth/check-email", {
|
|
961
|
+
params: { email }
|
|
962
|
+
});
|
|
963
|
+
}
|
|
950
964
|
};
|
|
951
965
|
|
|
952
966
|
// src/resources/jobs.ts
|
|
@@ -1310,6 +1324,14 @@ var WebhooksResource = class {
|
|
|
1310
1324
|
async getDeliveries(id) {
|
|
1311
1325
|
return this.http.get(`/outgoing-webhooks/${id}/deliveries`);
|
|
1312
1326
|
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Retry a specific webhook delivery
|
|
1329
|
+
*/
|
|
1330
|
+
async retryDelivery(webhookId, deliveryId) {
|
|
1331
|
+
return this.http.post(
|
|
1332
|
+
`/outgoing-webhooks/${webhookId}/retry/${deliveryId}`
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1313
1335
|
};
|
|
1314
1336
|
|
|
1315
1337
|
// src/resources/api-keys.ts
|
|
@@ -1404,6 +1426,12 @@ var OrganizationsResource = class {
|
|
|
1404
1426
|
params: { slug }
|
|
1405
1427
|
});
|
|
1406
1428
|
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Generate a unique slug from an organization name
|
|
1431
|
+
*/
|
|
1432
|
+
async generateSlug(name) {
|
|
1433
|
+
return this.http.post("/organizations/generate-slug", { name });
|
|
1434
|
+
}
|
|
1407
1435
|
};
|
|
1408
1436
|
|
|
1409
1437
|
// src/resources/billing.ts
|
|
@@ -2940,7 +2968,7 @@ var SpooledWorker = class {
|
|
|
2940
2968
|
...DEFAULT_OPTIONS,
|
|
2941
2969
|
hostname: os.hostname(),
|
|
2942
2970
|
workerType: "nodejs",
|
|
2943
|
-
version: "1.0.
|
|
2971
|
+
version: "1.0.13",
|
|
2944
2972
|
metadata: {},
|
|
2945
2973
|
...options
|
|
2946
2974
|
};
|