queuebear 0.1.7 → 0.1.9
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 +17 -37
- package/dist/index.d.ts +59 -3
- package/dist/index.js +91 -7
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -493,33 +493,34 @@ The signing secret is available in your QueueBear project settings. When configu
|
|
|
493
493
|
|
|
494
494
|
## Local Development
|
|
495
495
|
|
|
496
|
-
When developing locally, your webhook endpoints run on `localhost` which isn't accessible from QueueBear's servers. Use
|
|
496
|
+
When developing locally, your webhook endpoints run on `localhost` which isn't accessible from QueueBear's servers. Use [Tunnelmole](https://tunnelmole.com) to expose your local server - it's free and requires no signup.
|
|
497
497
|
|
|
498
|
-
###
|
|
498
|
+
### Installing Tunnelmole
|
|
499
499
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
**1. Install and authenticate:**
|
|
500
|
+
**Linux, macOS, Windows WSL:**
|
|
503
501
|
|
|
504
502
|
```bash
|
|
505
|
-
|
|
506
|
-
|
|
503
|
+
curl -O https://install.tunnelmole.com/t357g/install && sudo bash install
|
|
504
|
+
```
|
|
507
505
|
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
**Node.js (all platforms, requires Node 16+):**
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
npm install -g tunnelmole
|
|
510
510
|
```
|
|
511
511
|
|
|
512
|
-
|
|
512
|
+
### Starting a Tunnel
|
|
513
513
|
|
|
514
514
|
```bash
|
|
515
|
-
|
|
515
|
+
tmole 3000
|
|
516
|
+
# Output: https://xxxx.tunnelmole.com is forwarding to localhost:3000
|
|
516
517
|
```
|
|
517
518
|
|
|
518
|
-
|
|
519
|
+
### Using the Tunnel URL
|
|
519
520
|
|
|
520
521
|
```typescript
|
|
521
|
-
// Use
|
|
522
|
-
await qb.messages.publish("https://
|
|
522
|
+
// Use tunnelmole URL instead of localhost
|
|
523
|
+
await qb.messages.publish("https://xxxx.tunnelmole.com/api/webhooks", {
|
|
523
524
|
event: "user.created",
|
|
524
525
|
userId: "123"
|
|
525
526
|
});
|
|
@@ -527,37 +528,16 @@ await qb.messages.publish("https://abc123.ngrok.io/api/webhooks", {
|
|
|
527
528
|
// Works for workflows too
|
|
528
529
|
await qb.workflows.trigger(
|
|
529
530
|
"onboarding",
|
|
530
|
-
"https://
|
|
531
|
+
"https://xxxx.tunnelmole.com/api/workflows/onboarding",
|
|
531
532
|
{ userId: "123" }
|
|
532
533
|
);
|
|
533
534
|
```
|
|
534
535
|
|
|
535
|
-
**Debugging:** ngrok provides a web inspector at `http://localhost:4040` to view requests, responses, and replay failed deliveries.
|
|
536
|
-
|
|
537
|
-
### Using localtunnel
|
|
538
|
-
|
|
539
|
-
[localtunnel](https://localtunnel.me) is free and requires no signup.
|
|
540
|
-
|
|
541
|
-
```bash
|
|
542
|
-
npx localtunnel --port 3000
|
|
543
|
-
# Output: your url is: https://good-months-leave.loca.lt
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
**Note:** localtunnel shows a reminder page on first visit. Bypass it by adding a header:
|
|
547
|
-
|
|
548
|
-
```typescript
|
|
549
|
-
await qb.messages.publish(
|
|
550
|
-
"https://good-months-leave.loca.lt/api/webhooks",
|
|
551
|
-
{ event: "test" },
|
|
552
|
-
{ headers: { "bypass-tunnel-reminder": "true" } }
|
|
553
|
-
);
|
|
554
|
-
```
|
|
555
|
-
|
|
556
536
|
### Tips
|
|
557
537
|
|
|
558
538
|
- Store your tunnel URL in `.env` for easy switching between local and production
|
|
559
539
|
- Both `callbackUrl` and `failureCallbackUrl` need public URLs for local testing
|
|
560
|
-
-
|
|
540
|
+
- Tunnel URLs change on restart
|
|
561
541
|
|
|
562
542
|
---
|
|
563
543
|
|
package/dist/index.d.ts
CHANGED
|
@@ -468,6 +468,26 @@ interface PurgeDLQResponse {
|
|
|
468
468
|
purged: true;
|
|
469
469
|
count: number;
|
|
470
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Options for serveMessage() function
|
|
473
|
+
*/
|
|
474
|
+
interface ServeMessageOptions {
|
|
475
|
+
/** Signing secret for verifying message authenticity */
|
|
476
|
+
signingSecret?: string;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Context provided to message handlers by serveMessage()
|
|
480
|
+
*/
|
|
481
|
+
interface MessageContext<T> {
|
|
482
|
+
/** The parsed request body */
|
|
483
|
+
body: T;
|
|
484
|
+
/** QueueBear message ID (from X-QueueBear-Message-Id header) */
|
|
485
|
+
messageId?: string;
|
|
486
|
+
/** Current attempt number (0-indexed) */
|
|
487
|
+
attemptNumber: number;
|
|
488
|
+
/** All request headers */
|
|
489
|
+
headers: Record<string, string>;
|
|
490
|
+
}
|
|
471
491
|
|
|
472
492
|
/**
|
|
473
493
|
* Messages API client for publishing and managing webhook messages
|
|
@@ -495,12 +515,12 @@ declare class MessagesAPI extends BaseClient {
|
|
|
495
515
|
/**
|
|
496
516
|
* Get message details and delivery history
|
|
497
517
|
*
|
|
498
|
-
* @param messageId - The message ID (e.g., "
|
|
518
|
+
* @param messageId - The message ID (e.g., "abc123...")
|
|
499
519
|
* @returns Full message details including delivery logs
|
|
500
520
|
*
|
|
501
521
|
* @example
|
|
502
522
|
* ```typescript
|
|
503
|
-
* const message = await qb.messages.get("
|
|
523
|
+
* const message = await qb.messages.get("abc123");
|
|
504
524
|
* console.log(message.status); // "completed" | "pending" | "failed"
|
|
505
525
|
* console.log(message.deliveryLogs); // Delivery attempt history
|
|
506
526
|
* ```
|
|
@@ -1189,4 +1209,40 @@ type WorkflowHandler<TInput = unknown, TOutput = unknown> = (context: WorkflowCo
|
|
|
1189
1209
|
*/
|
|
1190
1210
|
declare function serve<TInput = unknown, TOutput = unknown>(handler: WorkflowHandler<TInput, TOutput>, options?: ServeOptions): (request: Request) => Promise<Response>;
|
|
1191
1211
|
|
|
1192
|
-
|
|
1212
|
+
/**
|
|
1213
|
+
* Message handler function type for serveMessage()
|
|
1214
|
+
*/
|
|
1215
|
+
type MessageHandler<TBody = unknown, TResult = unknown> = (context: MessageContext<TBody>) => Promise<TResult>;
|
|
1216
|
+
/**
|
|
1217
|
+
* Create a message webhook handler for simple message delivery.
|
|
1218
|
+
*
|
|
1219
|
+
* Unlike serve() which is designed for durable workflows with steps and state,
|
|
1220
|
+
* serveMessage() is for simple message webhooks published via messages.publish().
|
|
1221
|
+
*
|
|
1222
|
+
* @param handler - The handler function to process incoming messages
|
|
1223
|
+
* @param options - Optional configuration (e.g., signing secret for verification)
|
|
1224
|
+
* @returns A Request handler function for use with any HTTP framework
|
|
1225
|
+
*
|
|
1226
|
+
* @example
|
|
1227
|
+
* ```typescript
|
|
1228
|
+
* import { serveMessage } from 'queuebear';
|
|
1229
|
+
*
|
|
1230
|
+
* const handler = serveMessage<{ userId: string }>(
|
|
1231
|
+
* async (context) => {
|
|
1232
|
+
* console.log(`Processing message ${context.messageId} for user ${context.body.userId}`);
|
|
1233
|
+
* await processUser(context.body.userId);
|
|
1234
|
+
* return { success: true };
|
|
1235
|
+
* },
|
|
1236
|
+
* { signingSecret: process.env.QB_SIGNING_SECRET }
|
|
1237
|
+
* );
|
|
1238
|
+
*
|
|
1239
|
+
* // With Hono
|
|
1240
|
+
* app.post('/webhook', (c) => handler(c.req.raw));
|
|
1241
|
+
*
|
|
1242
|
+
* // With Express (using raw Request)
|
|
1243
|
+
* app.post('/webhook', (req, res) => handler(req).then(r => res.send(r)));
|
|
1244
|
+
* ```
|
|
1245
|
+
*/
|
|
1246
|
+
declare function serveMessage<TBody = unknown, TResult = unknown>(handler: MessageHandler<TBody, TResult>, options?: ServeMessageOptions): (request: Request) => Promise<Response>;
|
|
1247
|
+
|
|
1248
|
+
export { type CallConfig, type CancelMessageResponse, type CompletedStep, type CreateScheduleOptions, type CreateScheduleResponse, DLQAPI, type DLQEntry, type DLQEntrySummary, type DeleteDLQResponse, type DeleteScheduleResponse, type DeliveryLog, type LastResponse, type ListDLQOptions, type ListDLQResponse, type ListMessagesOptions, type ListMessagesResponse, type ListRunsOptions, type ListRunsResponse, type ListSchedulesOptions, type ListSchedulesResponse, type Message, type MessageContext, type MessageHandler, type MessageMethod, type MessageStatus, type MessageSummary, MessagesAPI, type Pagination, ParallelExecutionError, type ParallelStepDefinition, type PauseScheduleResponse, type PublishOptions, type PublishResponse, type PurgeDLQResponse, QueueBear, QueueBearError, type QueueBearOptions, type ResumeScheduleResponse, type RetryDLQResponse, type Schedule, type ScheduleSummary, SchedulesAPI, type SendEventOptions, type SendEventResponse, type ServeMessageOptions, type ServeOptions, type StatusResponse, type StepRetryOptions, type StepType, type TriggerOptions, type TriggerResponse, type WaitForEventOptions, type WorkflowContext, type WorkflowContextOptions, type WorkflowHandler, WorkflowPausedError, type WorkflowRun, type WorkflowRunStatus, type WorkflowStep, WorkflowsAPI, createWorkflowContext, serve, serveMessage };
|
package/dist/index.js
CHANGED
|
@@ -115,12 +115,12 @@ var MessagesAPI = class extends BaseClient {
|
|
|
115
115
|
/**
|
|
116
116
|
* Get message details and delivery history
|
|
117
117
|
*
|
|
118
|
-
* @param messageId - The message ID (e.g., "
|
|
118
|
+
* @param messageId - The message ID (e.g., "abc123...")
|
|
119
119
|
* @returns Full message details including delivery logs
|
|
120
120
|
*
|
|
121
121
|
* @example
|
|
122
122
|
* ```typescript
|
|
123
|
-
* const message = await qb.messages.get("
|
|
123
|
+
* const message = await qb.messages.get("abc123");
|
|
124
124
|
* console.log(message.status); // "completed" | "pending" | "failed"
|
|
125
125
|
* console.log(message.deliveryLogs); // Delivery attempt history
|
|
126
126
|
* ```
|
|
@@ -164,7 +164,10 @@ var MessagesAPI = class extends BaseClient {
|
|
|
164
164
|
* ```
|
|
165
165
|
*/
|
|
166
166
|
async cancel(messageId) {
|
|
167
|
-
return this.request(
|
|
167
|
+
return this.request(
|
|
168
|
+
"DELETE",
|
|
169
|
+
`/messages/${messageId}`
|
|
170
|
+
);
|
|
168
171
|
}
|
|
169
172
|
/**
|
|
170
173
|
* Publish a message and wait for delivery completion
|
|
@@ -186,9 +189,18 @@ var MessagesAPI = class extends BaseClient {
|
|
|
186
189
|
* ```
|
|
187
190
|
*/
|
|
188
191
|
async publishAndWait(destination, body, options) {
|
|
189
|
-
const {
|
|
192
|
+
const {
|
|
193
|
+
pollIntervalMs = 1e3,
|
|
194
|
+
timeoutMs = 6e4,
|
|
195
|
+
...publishOptions
|
|
196
|
+
} = options || {};
|
|
190
197
|
const { messageId } = await this.publish(destination, body, publishOptions);
|
|
191
|
-
const terminalStates = [
|
|
198
|
+
const terminalStates = [
|
|
199
|
+
"completed",
|
|
200
|
+
"failed",
|
|
201
|
+
"cancelled",
|
|
202
|
+
"dlq"
|
|
203
|
+
];
|
|
192
204
|
const startTime = Date.now();
|
|
193
205
|
while (Date.now() - startTime < timeoutMs) {
|
|
194
206
|
const message = await this.get(messageId);
|
|
@@ -197,7 +209,9 @@ var MessagesAPI = class extends BaseClient {
|
|
|
197
209
|
}
|
|
198
210
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
199
211
|
}
|
|
200
|
-
throw new Error(
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Message ${messageId} did not complete within ${timeoutMs}ms`
|
|
214
|
+
);
|
|
201
215
|
}
|
|
202
216
|
};
|
|
203
217
|
|
|
@@ -1257,6 +1271,75 @@ function verifySignature(body, signature, secret) {
|
|
|
1257
1271
|
return false;
|
|
1258
1272
|
}
|
|
1259
1273
|
}
|
|
1274
|
+
|
|
1275
|
+
// src/serve-message.ts
|
|
1276
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
1277
|
+
function serveMessage(handler, options) {
|
|
1278
|
+
return async (request) => {
|
|
1279
|
+
try {
|
|
1280
|
+
if (options?.signingSecret) {
|
|
1281
|
+
const signature = request.headers.get("X-QueueBear-Signature");
|
|
1282
|
+
const bodyText = await request.clone().text();
|
|
1283
|
+
if (!verifySignature2(bodyText, signature, options.signingSecret)) {
|
|
1284
|
+
return new Response(JSON.stringify({ error: "Invalid signature" }), {
|
|
1285
|
+
status: 401,
|
|
1286
|
+
headers: { "Content-Type": "application/json" }
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const messageId = request.headers.get("X-QueueBear-Message-Id") || void 0;
|
|
1291
|
+
const attemptNumber = parseInt(
|
|
1292
|
+
request.headers.get("X-QueueBear-Retry-Count") || "0",
|
|
1293
|
+
10
|
|
1294
|
+
);
|
|
1295
|
+
const body = await request.json();
|
|
1296
|
+
const context = {
|
|
1297
|
+
body,
|
|
1298
|
+
messageId,
|
|
1299
|
+
attemptNumber,
|
|
1300
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
1301
|
+
};
|
|
1302
|
+
const result = await handler(context);
|
|
1303
|
+
return new Response(JSON.stringify({ success: true, result }), {
|
|
1304
|
+
status: 200,
|
|
1305
|
+
headers: { "Content-Type": "application/json" }
|
|
1306
|
+
});
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
console.error("[serveMessage] Error:", error);
|
|
1309
|
+
return new Response(
|
|
1310
|
+
JSON.stringify({
|
|
1311
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1312
|
+
}),
|
|
1313
|
+
{
|
|
1314
|
+
status: 500,
|
|
1315
|
+
headers: { "Content-Type": "application/json" }
|
|
1316
|
+
}
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
function verifySignature2(body, signature, secret) {
|
|
1322
|
+
if (!signature) return false;
|
|
1323
|
+
const parts = signature.split(",").reduce(
|
|
1324
|
+
(acc, part) => {
|
|
1325
|
+
const [key, value] = part.split("=");
|
|
1326
|
+
acc[key] = value;
|
|
1327
|
+
return acc;
|
|
1328
|
+
},
|
|
1329
|
+
{}
|
|
1330
|
+
);
|
|
1331
|
+
if (!parts.t || !parts.v1) return false;
|
|
1332
|
+
const timestamp = parseInt(parts.t, 10);
|
|
1333
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1334
|
+
if (Math.abs(now - timestamp) > 300) return false;
|
|
1335
|
+
const payload = `${parts.t}.${body}`;
|
|
1336
|
+
const expected = createHmac2("sha256", secret).update(payload).digest("hex");
|
|
1337
|
+
try {
|
|
1338
|
+
return timingSafeEqual2(Buffer.from(parts.v1), Buffer.from(expected));
|
|
1339
|
+
} catch {
|
|
1340
|
+
return false;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1260
1343
|
export {
|
|
1261
1344
|
DLQAPI,
|
|
1262
1345
|
MessagesAPI,
|
|
@@ -1267,5 +1350,6 @@ export {
|
|
|
1267
1350
|
WorkflowPausedError,
|
|
1268
1351
|
WorkflowsAPI,
|
|
1269
1352
|
createWorkflowContext,
|
|
1270
|
-
serve
|
|
1353
|
+
serve,
|
|
1354
|
+
serveMessage
|
|
1271
1355
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "queuebear",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "QueueBear SDK for message queues, scheduled jobs, and durable workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,13 +18,12 @@
|
|
|
18
18
|
"type-check": "tsc --noEmit",
|
|
19
19
|
"clean": "rm -rf dist"
|
|
20
20
|
},
|
|
21
|
-
"dependencies": {},
|
|
22
21
|
"devDependencies": {
|
|
23
22
|
"@types/node": "^22.10.2",
|
|
24
23
|
"tsup": "^8.3.5",
|
|
25
|
-
"typescript": "^5.7.2"
|
|
24
|
+
"typescript": "^5.7.2",
|
|
25
|
+
"vitest": "^4.0.16"
|
|
26
26
|
},
|
|
27
|
-
"peerDependencies": {},
|
|
28
27
|
"files": [
|
|
29
28
|
"dist"
|
|
30
29
|
],
|