queuebear 0.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/LICENSE +21 -0
- package/README.md +484 -0
- package/dist/index.d.ts +1209 -0
- package/dist/index.js +1313 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Robert Marshall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# queuebear
|
|
2
|
+
|
|
3
|
+
QueueBear SDK for building durable workflows and managing message queues.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install queuebear
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { QueueBear, serve } from "queuebear";
|
|
15
|
+
|
|
16
|
+
// Create client
|
|
17
|
+
const qb = new QueueBear({
|
|
18
|
+
baseUrl: "https://your-queuebear-instance.com",
|
|
19
|
+
apiKey: "qb_live_xxx",
|
|
20
|
+
projectId: "proj_xxx",
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API Overview
|
|
25
|
+
|
|
26
|
+
The SDK provides access to all QueueBear APIs:
|
|
27
|
+
|
|
28
|
+
| API | Description |
|
|
29
|
+
|-----|-------------|
|
|
30
|
+
| `qb.messages` | Publish and manage webhook messages |
|
|
31
|
+
| `qb.schedules` | Create and manage cron-based recurring jobs |
|
|
32
|
+
| `qb.dlq` | Manage failed messages in the dead letter queue |
|
|
33
|
+
| `qb.workflows` | Trigger and manage durable workflows |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Messages API
|
|
38
|
+
|
|
39
|
+
Publish messages to be delivered to webhook destinations with automatic retries.
|
|
40
|
+
|
|
41
|
+
### Publish a Message
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const { messageId } = await qb.messages.publish(
|
|
45
|
+
"https://api.example.com/webhook",
|
|
46
|
+
{ event: "user.created", userId: "123" },
|
|
47
|
+
{
|
|
48
|
+
delay: "30s", // Delay before delivery
|
|
49
|
+
retries: 5, // Number of retry attempts
|
|
50
|
+
method: "POST", // HTTP method
|
|
51
|
+
headers: { "X-API-Key": "secret" }, // Headers to forward
|
|
52
|
+
callbackUrl: "https://...", // Success callback
|
|
53
|
+
failureCallbackUrl: "https://...", // Failure callback
|
|
54
|
+
deduplicationId: "unique-id", // Prevent duplicate messages
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Get Message Status
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const message = await qb.messages.get(messageId);
|
|
63
|
+
console.log(message.status); // "pending" | "completed" | "failed"
|
|
64
|
+
console.log(message.deliveryLogs); // Delivery attempt history
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### List Messages
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const { messages, pagination } = await qb.messages.list({
|
|
71
|
+
status: "pending",
|
|
72
|
+
limit: 20,
|
|
73
|
+
offset: 0,
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Cancel a Message
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
await qb.messages.cancel(messageId);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Publish and Wait
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const message = await qb.messages.publishAndWait(
|
|
87
|
+
"https://api.example.com/webhook",
|
|
88
|
+
{ event: "user.created" },
|
|
89
|
+
{ timeoutMs: 30000 }
|
|
90
|
+
);
|
|
91
|
+
console.log(message.status); // "completed"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Schedules API
|
|
97
|
+
|
|
98
|
+
Create cron-based recurring jobs.
|
|
99
|
+
|
|
100
|
+
### Create a Schedule
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const schedule = await qb.schedules.create({
|
|
104
|
+
destination: "https://api.example.com/cron-job",
|
|
105
|
+
cron: "0 9 * * *", // Daily at 9 AM
|
|
106
|
+
timezone: "America/New_York",
|
|
107
|
+
method: "POST",
|
|
108
|
+
body: JSON.stringify({ type: "daily-report" }),
|
|
109
|
+
headers: { "Content-Type": "application/json" },
|
|
110
|
+
retries: 3,
|
|
111
|
+
metadata: { jobName: "daily-report" },
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Common Cron Expressions
|
|
116
|
+
|
|
117
|
+
| Expression | Description |
|
|
118
|
+
|------------|-------------|
|
|
119
|
+
| `* * * * *` | Every minute |
|
|
120
|
+
| `0 * * * *` | Every hour |
|
|
121
|
+
| `0 9 * * *` | Daily at 9:00 AM |
|
|
122
|
+
| `0 9 * * 1-5` | Weekdays at 9:00 AM |
|
|
123
|
+
| `0 0 1 * *` | First day of each month |
|
|
124
|
+
| `0 */6 * * *` | Every 6 hours |
|
|
125
|
+
|
|
126
|
+
### List Schedules
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const { schedules } = await qb.schedules.list();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Pause / Resume
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
await qb.schedules.pause(scheduleId);
|
|
136
|
+
await qb.schedules.resume(scheduleId);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Delete a Schedule
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
await qb.schedules.delete(scheduleId);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Dead Letter Queue (DLQ) API
|
|
148
|
+
|
|
149
|
+
Manage messages that failed all retry attempts.
|
|
150
|
+
|
|
151
|
+
### List DLQ Entries
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const { entries } = await qb.dlq.list();
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
console.log(`${entry.id}: ${entry.failureReason}`);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Get Entry Details
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const entry = await qb.dlq.get(dlqId);
|
|
164
|
+
console.log(entry.body); // Original message body
|
|
165
|
+
console.log(entry.totalAttempts); // Number of failed attempts
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Retry a Failed Message
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const result = await qb.dlq.retry(dlqId);
|
|
172
|
+
console.log(result.newMessageId); // New message created
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Delete Entry / Purge All
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
await qb.dlq.delete(dlqId);
|
|
179
|
+
await qb.dlq.purge(); // Delete all entries
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Retry All Failed Messages
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const results = await qb.dlq.retryAll();
|
|
186
|
+
console.log(`Retried ${results.length} entries`);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Workflows API
|
|
192
|
+
|
|
193
|
+
Build durable, fault-tolerant workflows with automatic step caching.
|
|
194
|
+
|
|
195
|
+
Workflows consist of two parts:
|
|
196
|
+
1. **Workflow endpoint** - Created with `serve()`, handles workflow execution
|
|
197
|
+
2. **Client** - Uses `qb.workflows` to trigger and manage workflow runs
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### `serve()` - Create a Workflow Endpoint
|
|
202
|
+
|
|
203
|
+
The `serve()` function creates an HTTP handler for your workflow. It receives requests from QueueBear, executes your workflow code, and manages step caching automatically.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { serve } from "queuebear";
|
|
207
|
+
|
|
208
|
+
export const POST = serve<InputType>(async (context) => {
|
|
209
|
+
// Your workflow logic here
|
|
210
|
+
return result;
|
|
211
|
+
}, options);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Parameters:**
|
|
215
|
+
|
|
216
|
+
| Parameter | Type | Description |
|
|
217
|
+
|-----------|------|-------------|
|
|
218
|
+
| `handler` | `(context: WorkflowContext<T>) => Promise<R>` | Your workflow function |
|
|
219
|
+
| `options` | `ServeOptions` | Optional configuration |
|
|
220
|
+
|
|
221
|
+
**Options:**
|
|
222
|
+
|
|
223
|
+
| Option | Type | Description |
|
|
224
|
+
|--------|------|-------------|
|
|
225
|
+
| `signingSecret` | `string` | Secret to verify requests come from QueueBear |
|
|
226
|
+
| `baseUrl` | `string` | Override QueueBear base URL (auto-detected if not set) |
|
|
227
|
+
|
|
228
|
+
### Framework Integration
|
|
229
|
+
|
|
230
|
+
**Next.js (App Router)**
|
|
231
|
+
```typescript
|
|
232
|
+
// app/api/workflows/my-workflow/route.ts
|
|
233
|
+
import { serve } from "queuebear";
|
|
234
|
+
|
|
235
|
+
export const POST = serve(async (context) => {
|
|
236
|
+
await context.run("step-1", async () => { /* ... */ });
|
|
237
|
+
return { success: true };
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Express**
|
|
242
|
+
```typescript
|
|
243
|
+
import express from "express";
|
|
244
|
+
import { serve } from "queuebear";
|
|
245
|
+
|
|
246
|
+
const app = express();
|
|
247
|
+
app.use(express.json());
|
|
248
|
+
|
|
249
|
+
const handler = serve(async (context) => {
|
|
250
|
+
await context.run("step-1", async () => { /* ... */ });
|
|
251
|
+
return { success: true };
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
app.post("/api/workflows/my-workflow", async (req, res) => {
|
|
255
|
+
const response = await handler(new Request(req.url, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: req.headers as HeadersInit,
|
|
258
|
+
body: JSON.stringify(req.body),
|
|
259
|
+
}));
|
|
260
|
+
res.status(response.status).json(await response.json());
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Hono**
|
|
265
|
+
```typescript
|
|
266
|
+
import { Hono } from "hono";
|
|
267
|
+
import { serve } from "queuebear";
|
|
268
|
+
|
|
269
|
+
const app = new Hono();
|
|
270
|
+
|
|
271
|
+
const handler = serve(async (context) => {
|
|
272
|
+
await context.run("step-1", async () => { /* ... */ });
|
|
273
|
+
return { success: true };
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
app.post("/api/workflows/my-workflow", async (c) => {
|
|
277
|
+
const response = await handler(c.req.raw);
|
|
278
|
+
return response;
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### Complete Workflow Example
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// app/api/workflows/onboarding/route.ts
|
|
288
|
+
import { serve } from "queuebear";
|
|
289
|
+
|
|
290
|
+
interface OnboardingInput {
|
|
291
|
+
userId: string;
|
|
292
|
+
email: string;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export const POST = serve<OnboardingInput>(async (context) => {
|
|
296
|
+
const { userId, email } = context.input;
|
|
297
|
+
|
|
298
|
+
// Step 1: Send welcome email (cached if already done)
|
|
299
|
+
await context.run("send-welcome", async () => {
|
|
300
|
+
await sendEmail(email, "welcome");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Step 2: Wait 3 days
|
|
304
|
+
await context.sleep("wait-3-days", 60 * 60 * 24 * 3);
|
|
305
|
+
|
|
306
|
+
// Step 3: Send tips email
|
|
307
|
+
await context.run("send-tips", async () => {
|
|
308
|
+
await sendEmail(email, "tips");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return { completed: true };
|
|
312
|
+
}, {
|
|
313
|
+
signingSecret: process.env.QUEUEBEAR_SIGNING_SECRET,
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Trigger a Workflow
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const { runId } = await qb.workflows.trigger(
|
|
321
|
+
"user-onboarding",
|
|
322
|
+
"https://your-app.com/api/workflows/onboarding",
|
|
323
|
+
{ userId: "123", email: "user@example.com" },
|
|
324
|
+
{
|
|
325
|
+
idempotencyKey: "onboarding-user-123",
|
|
326
|
+
maxDuration: 60 * 60 * 24 * 7, // 7 day timeout
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Check Workflow Status
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const status = await qb.workflows.getStatus(runId);
|
|
335
|
+
console.log(status.status); // "running" | "sleeping" | "completed"
|
|
336
|
+
console.log(status.steps); // Array of step details
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Wait for Completion
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const result = await qb.workflows.waitForCompletion(runId, {
|
|
343
|
+
pollIntervalMs: 2000,
|
|
344
|
+
timeoutMs: 60000,
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Trigger and Wait
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
const result = await qb.triggerAndWait(
|
|
352
|
+
"user-onboarding",
|
|
353
|
+
"https://your-app.com/api/workflows/onboarding",
|
|
354
|
+
{ userId: "123" },
|
|
355
|
+
{ timeoutMs: 120000 }
|
|
356
|
+
);
|
|
357
|
+
console.log(result.result); // Workflow output
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Cancel / Retry
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
await qb.workflows.cancel(runId);
|
|
364
|
+
await qb.workflows.retry(runId); // Resume from last completed step
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Send Events
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// In workflow: await context.waitForEvent("order-approved", "order.approved")
|
|
371
|
+
|
|
372
|
+
// From external code:
|
|
373
|
+
await qb.workflows.sendEvent("order.approved", {
|
|
374
|
+
eventKey: "order-123",
|
|
375
|
+
payload: { status: "approved" },
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Context Methods
|
|
382
|
+
|
|
383
|
+
Available in `serve()` handlers:
|
|
384
|
+
|
|
385
|
+
### `context.run(stepName, fn, options?)`
|
|
386
|
+
|
|
387
|
+
Execute a step with automatic caching.
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
const result = await context.run("fetch-user", async () => {
|
|
391
|
+
return await db.users.findById(userId);
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### `context.sleep(stepName, seconds)`
|
|
396
|
+
|
|
397
|
+
Pause workflow for specified duration.
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
await context.sleep("wait-1-hour", 3600);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### `context.sleepUntil(stepName, date)`
|
|
404
|
+
|
|
405
|
+
Pause until a specific date/time.
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
await context.sleepUntil("wait-until-tomorrow", new Date("2024-01-15"));
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### `context.call(stepName, config)`
|
|
412
|
+
|
|
413
|
+
Make an HTTP call as a cached step.
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
const data = await context.call("fetch-api", {
|
|
417
|
+
url: "https://api.example.com/data",
|
|
418
|
+
method: "POST",
|
|
419
|
+
headers: { "Authorization": "Bearer xxx" },
|
|
420
|
+
body: { key: "value" },
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### `context.waitForEvent(stepName, eventName, options?)`
|
|
425
|
+
|
|
426
|
+
Wait for an external event.
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
const payload = await context.waitForEvent("wait-approval", "order.approved", {
|
|
430
|
+
eventKey: "order-123",
|
|
431
|
+
timeoutSeconds: 86400, // 1 day
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### `context.notify(eventName, payload?)`
|
|
436
|
+
|
|
437
|
+
Send fire-and-forget event.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
await context.notify("user.onboarded", { userId: "123" });
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### `context.parallel(steps)`
|
|
444
|
+
|
|
445
|
+
Execute steps in parallel.
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
const [user, orders, preferences] = await context.parallel([
|
|
449
|
+
{ name: "fetch-user", fn: () => fetchUser(userId) },
|
|
450
|
+
{ name: "fetch-orders", fn: () => fetchOrders(userId) },
|
|
451
|
+
{ name: "fetch-preferences", fn: () => fetchPreferences(userId) },
|
|
452
|
+
]);
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### `context.getCompletedSteps()`
|
|
456
|
+
|
|
457
|
+
Get all completed steps for debugging.
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
const steps = await context.getCompletedSteps();
|
|
461
|
+
console.log(`Completed ${steps.length} steps`);
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Security
|
|
467
|
+
|
|
468
|
+
### Signature Verification
|
|
469
|
+
|
|
470
|
+
Verify that workflow requests come from your QueueBear instance:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
export const POST = serve(handler, {
|
|
474
|
+
signingSecret: process.env.QUEUEBEAR_SIGNING_SECRET,
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
The signing secret is available in your QueueBear project settings. When configured, requests without a valid signature will be rejected with a 401 error.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## License
|
|
483
|
+
|
|
484
|
+
MIT
|