queuebear 0.1.6 → 0.1.8

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/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., "msg_abc123...")
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("msg_abc123");
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
- 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 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 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 };
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., "msg_abc123...")
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("msg_abc123");
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("DELETE", `/messages/${messageId}`);
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 { pollIntervalMs = 1e3, timeoutMs = 6e4, ...publishOptions } = options || {};
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 = ["completed", "failed", "cancelled", "dlq"];
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(`Message ${messageId} did not complete within ${timeoutMs}ms`);
212
+ throw new Error(
213
+ `Message ${messageId} did not complete within ${timeoutMs}ms`
214
+ );
201
215
  }
202
216
  };
203
217
 
@@ -1149,7 +1163,7 @@ function serve(handler, options) {
1149
1163
  }
1150
1164
  }
1151
1165
  const body = await request.json();
1152
- runId = body.runId || request.headers.get("X-QueueBear-Workflow-Run") || "";
1166
+ runId = body.id || request.headers.get("X-QueueBear-Workflow-Run") || "";
1153
1167
  runToken = request.headers.get("X-QueueBear-Run-Token") || "";
1154
1168
  if (!runId) {
1155
1169
  return new Response(JSON.stringify({ error: "Missing runId" }), {
@@ -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.6",
3
+ "version": "0.1.8",
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
  ],
@@ -43,7 +42,7 @@
43
42
  "author": {
44
43
  "name": "Robert Marshall",
45
44
  "email": "hello@robertmarshall.dev",
46
- "url": "https://robertmarshall.dev"
45
+ "url": "https://queuebear.com"
47
46
  },
48
47
  "license": "MIT",
49
48
  "repository": {