@vercel/queue 0.0.0-alpha.32 → 0.0.0-alpha.33

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.mjs CHANGED
@@ -137,6 +137,9 @@ var InvalidLimitError = class extends Error {
137
137
  };
138
138
 
139
139
  // src/client.ts
140
+ function isDebugEnabled() {
141
+ return process.env.VERCEL_QUEUE_DEBUG === "1" || process.env.VERCEL_QUEUE_DEBUG === "true";
142
+ }
140
143
  async function consumeStream(stream) {
141
144
  const reader = stream.getReader();
142
145
  try {
@@ -148,6 +151,31 @@ async function consumeStream(stream) {
148
151
  reader.releaseLock();
149
152
  }
150
153
  }
154
+ function parseRetryAfter(headers) {
155
+ const retryAfterHeader = headers.get("Retry-After");
156
+ if (retryAfterHeader) {
157
+ const parsed = parseInt(retryAfterHeader, 10);
158
+ return Number.isNaN(parsed) ? void 0 : parsed;
159
+ }
160
+ return void 0;
161
+ }
162
+ function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters") {
163
+ if (status === 400) {
164
+ throw new BadRequestError(errorText || badRequestDefault);
165
+ }
166
+ if (status === 401) {
167
+ throw new UnauthorizedError(errorText || void 0);
168
+ }
169
+ if (status === 403) {
170
+ throw new ForbiddenError(errorText || void 0);
171
+ }
172
+ if (status >= 500) {
173
+ throw new InternalServerError(
174
+ errorText || `Server error: ${status} ${statusText}`
175
+ );
176
+ }
177
+ throw new Error(`Failed to ${operation}: ${status} ${statusText}`);
178
+ }
151
179
  function parseQueueHeaders(headers) {
152
180
  const messageId = headers.get("Vqs-Message-Id");
153
181
  const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
@@ -158,7 +186,7 @@ function parseQueueHeaders(headers) {
158
186
  return null;
159
187
  }
160
188
  const deliveryCount = parseInt(deliveryCountStr, 10);
161
- if (isNaN(deliveryCount)) {
189
+ if (Number.isNaN(deliveryCount)) {
162
190
  return null;
163
191
  }
164
192
  return {
@@ -172,24 +200,22 @@ function parseQueueHeaders(headers) {
172
200
  var QueueClient = class {
173
201
  baseUrl;
174
202
  basePath;
175
- customHeaders = {};
203
+ customHeaders;
204
+ providedToken;
176
205
  /**
177
206
  * Create a new Vercel Queue Service client
178
- * @param options Client configuration options
207
+ * @param options QueueClient configuration options
179
208
  */
180
209
  constructor(options = {}) {
181
210
  this.baseUrl = options.baseUrl || process.env.VERCEL_QUEUE_BASE_URL || "https://vercel-queue.com";
182
211
  this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v2/messages";
183
- const VERCEL_QUEUE_HEADER_PREFIX = "VERCEL_QUEUE_HEADER_";
184
- this.customHeaders = Object.fromEntries(
185
- Object.entries(process.env).filter(([key]) => key.startsWith(VERCEL_QUEUE_HEADER_PREFIX)).map(([key, value]) => [
186
- // This allows headers to use dashes independent of shell used
187
- key.replace(VERCEL_QUEUE_HEADER_PREFIX, "").replaceAll("__", "-"),
188
- value || ""
189
- ])
190
- );
212
+ this.customHeaders = options.headers || {};
213
+ this.providedToken = options.token;
191
214
  }
192
215
  async getToken() {
216
+ if (this.providedToken) {
217
+ return this.providedToken;
218
+ }
193
219
  const token = await getVercelOidcToken();
194
220
  if (!token) {
195
221
  throw new Error(
@@ -198,6 +224,45 @@ var QueueClient = class {
198
224
  }
199
225
  return token;
200
226
  }
227
+ /**
228
+ * Internal fetch wrapper that automatically handles debug logging
229
+ * when VERCEL_QUEUE_DEBUG is enabled
230
+ */
231
+ async fetch(url, init) {
232
+ const method = init.method || "GET";
233
+ if (isDebugEnabled()) {
234
+ const logData = {
235
+ method,
236
+ url,
237
+ headers: init.headers
238
+ };
239
+ const body = init.body;
240
+ if (body !== void 0 && body !== null) {
241
+ if (body instanceof ArrayBuffer) {
242
+ logData.bodySize = body.byteLength;
243
+ } else if (body instanceof Uint8Array) {
244
+ logData.bodySize = body.byteLength;
245
+ } else if (typeof body === "string") {
246
+ logData.bodySize = body.length;
247
+ } else {
248
+ logData.bodyType = typeof body;
249
+ }
250
+ }
251
+ console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
252
+ }
253
+ const response = await fetch(url, init);
254
+ if (isDebugEnabled()) {
255
+ const logData = {
256
+ method,
257
+ url,
258
+ status: response.status,
259
+ statusText: response.statusText,
260
+ headers: response.headers
261
+ };
262
+ console.debug("[VQS Debug] Response:", JSON.stringify(logData, null, 2));
263
+ }
264
+ return response;
265
+ }
201
266
  /**
202
267
  * Send a message to a queue
203
268
  * @param options Send message options
@@ -227,32 +292,21 @@ var QueueClient = class {
227
292
  headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
228
293
  }
229
294
  const body = transport.serialize(payload);
230
- const response = await fetch(`${this.baseUrl}${this.basePath}`, {
295
+ const response = await this.fetch(`${this.baseUrl}${this.basePath}`, {
231
296
  method: "POST",
232
297
  body,
233
298
  headers
234
299
  });
235
300
  if (!response.ok) {
236
- if (response.status === 400) {
237
- const errorText = await response.text();
238
- throw new BadRequestError(errorText || "Invalid parameters");
239
- }
240
- if (response.status === 401) {
241
- throw new UnauthorizedError();
242
- }
243
- if (response.status === 403) {
244
- throw new ForbiddenError();
245
- }
301
+ const errorText = await response.text();
246
302
  if (response.status === 409) {
247
303
  throw new Error("Duplicate idempotency key detected");
248
304
  }
249
- if (response.status >= 500) {
250
- throw new InternalServerError(
251
- `Server error: ${response.status} ${response.statusText}`
252
- );
253
- }
254
- throw new Error(
255
- `Failed to send message: ${response.status} ${response.statusText}`
305
+ throwCommonHttpError(
306
+ response.status,
307
+ response.statusText,
308
+ errorText,
309
+ "send message"
256
310
  );
257
311
  }
258
312
  const responseData = await response.json();
@@ -292,7 +346,7 @@ var QueueClient = class {
292
346
  if (limit !== void 0) {
293
347
  headers.set("Vqs-Limit", limit.toString());
294
348
  }
295
- const response = await fetch(`${this.baseUrl}${this.basePath}`, {
349
+ const response = await this.fetch(`${this.baseUrl}${this.basePath}`, {
296
350
  method: "GET",
297
351
  headers
298
352
  });
@@ -300,32 +354,18 @@ var QueueClient = class {
300
354
  throw new QueueEmptyError(queueName, consumerGroup);
301
355
  }
302
356
  if (!response.ok) {
303
- if (response.status === 400) {
304
- const errorText = await response.text();
305
- throw new BadRequestError(errorText || "Invalid parameters");
306
- }
307
- if (response.status === 401) {
308
- throw new UnauthorizedError();
309
- }
310
- if (response.status === 403) {
311
- throw new ForbiddenError();
312
- }
357
+ const errorText = await response.text();
313
358
  if (response.status === 423) {
314
- const retryAfterHeader = response.headers.get("Retry-After");
315
- let retryAfter;
316
- if (retryAfterHeader) {
317
- const parsed = parseInt(retryAfterHeader, 10);
318
- retryAfter = isNaN(parsed) ? void 0 : parsed;
319
- }
320
- throw new MessageLockedError("next message", retryAfter);
321
- }
322
- if (response.status >= 500) {
323
- throw new InternalServerError(
324
- `Server error: ${response.status} ${response.statusText}`
359
+ throw new MessageLockedError(
360
+ "next message",
361
+ parseRetryAfter(response.headers)
325
362
  );
326
363
  }
327
- throw new Error(
328
- `Failed to receive messages: ${response.status} ${response.statusText}`
364
+ throwCommonHttpError(
365
+ response.status,
366
+ response.statusText,
367
+ errorText,
368
+ "receive messages"
329
369
  );
330
370
  }
331
371
  for await (const multipartMessage of parseMultipartStream(response)) {
@@ -374,7 +414,7 @@ var QueueClient = class {
374
414
  if (skipPayload) {
375
415
  headers.set("Vqs-Skip-Payload", "1");
376
416
  }
377
- const response = await fetch(
417
+ const response = await this.fetch(
378
418
  `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
379
419
  {
380
420
  method: "GET",
@@ -382,38 +422,24 @@ var QueueClient = class {
382
422
  }
383
423
  );
384
424
  if (!response.ok) {
385
- if (response.status === 400) {
386
- const errorText = await response.text();
387
- throw new BadRequestError(errorText || "Invalid parameters");
388
- }
389
- if (response.status === 401) {
390
- throw new UnauthorizedError();
391
- }
392
- if (response.status === 403) {
393
- throw new ForbiddenError();
394
- }
425
+ const errorText = await response.text();
395
426
  if (response.status === 404) {
396
427
  throw new MessageNotFoundError(messageId);
397
428
  }
398
- if (response.status === 423) {
399
- const retryAfterHeader = response.headers.get("Retry-After");
400
- let retryAfter;
401
- if (retryAfterHeader) {
402
- const parsed = parseInt(retryAfterHeader, 10);
403
- retryAfter = isNaN(parsed) ? void 0 : parsed;
404
- }
405
- throw new MessageLockedError(messageId, retryAfter);
406
- }
407
429
  if (response.status === 409) {
408
430
  throw new MessageNotAvailableError(messageId);
409
431
  }
410
- if (response.status >= 500) {
411
- throw new InternalServerError(
412
- `Server error: ${response.status} ${response.statusText}`
432
+ if (response.status === 423) {
433
+ throw new MessageLockedError(
434
+ messageId,
435
+ parseRetryAfter(response.headers)
413
436
  );
414
437
  }
415
- throw new Error(
416
- `Failed to receive message by ID: ${response.status} ${response.statusText}`
438
+ throwCommonHttpError(
439
+ response.status,
440
+ response.statusText,
441
+ errorText,
442
+ "receive message by ID"
417
443
  );
418
444
  }
419
445
  if (skipPayload && response.status === 204) {
@@ -483,7 +509,7 @@ var QueueClient = class {
483
509
  */
484
510
  async deleteMessage(options) {
485
511
  const { queueName, consumerGroup, messageId, ticket } = options;
486
- const response = await fetch(
512
+ const response = await this.fetch(
487
513
  `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
488
514
  {
489
515
  method: "DELETE",
@@ -497,31 +523,22 @@ var QueueClient = class {
497
523
  }
498
524
  );
499
525
  if (!response.ok) {
500
- if (response.status === 400) {
501
- throw new BadRequestError("Missing or invalid ticket");
502
- }
503
- if (response.status === 401) {
504
- throw new UnauthorizedError();
505
- }
506
- if (response.status === 403) {
507
- throw new ForbiddenError();
508
- }
526
+ const errorText = await response.text();
509
527
  if (response.status === 404) {
510
528
  throw new MessageNotFoundError(messageId);
511
529
  }
512
530
  if (response.status === 409) {
513
531
  throw new MessageNotAvailableError(
514
532
  messageId,
515
- "Invalid ticket, message not in correct state, or already processed"
516
- );
517
- }
518
- if (response.status >= 500) {
519
- throw new InternalServerError(
520
- `Server error: ${response.status} ${response.statusText}`
533
+ errorText || "Invalid ticket, message not in correct state, or already processed"
521
534
  );
522
535
  }
523
- throw new Error(
524
- `Failed to delete message: ${response.status} ${response.statusText}`
536
+ throwCommonHttpError(
537
+ response.status,
538
+ response.statusText,
539
+ errorText,
540
+ "delete message",
541
+ "Missing or invalid ticket"
525
542
  );
526
543
  }
527
544
  return { deleted: true };
@@ -545,7 +562,7 @@ var QueueClient = class {
545
562
  ticket,
546
563
  visibilityTimeoutSeconds
547
564
  } = options;
548
- const response = await fetch(
565
+ const response = await this.fetch(
549
566
  `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
550
567
  {
551
568
  method: "PATCH",
@@ -560,165 +577,28 @@ var QueueClient = class {
560
577
  }
561
578
  );
562
579
  if (!response.ok) {
563
- if (response.status === 400) {
564
- throw new BadRequestError(
565
- "Missing ticket or invalid visibility timeout"
566
- );
567
- }
568
- if (response.status === 401) {
569
- throw new UnauthorizedError();
570
- }
571
- if (response.status === 403) {
572
- throw new ForbiddenError();
573
- }
580
+ const errorText = await response.text();
574
581
  if (response.status === 404) {
575
582
  throw new MessageNotFoundError(messageId);
576
583
  }
577
584
  if (response.status === 409) {
578
585
  throw new MessageNotAvailableError(
579
586
  messageId,
580
- "Invalid ticket, message not in correct state, or already processed"
587
+ errorText || "Invalid ticket, message not in correct state, or already processed"
581
588
  );
582
589
  }
583
- if (response.status >= 500) {
584
- throw new InternalServerError(
585
- `Server error: ${response.status} ${response.statusText}`
586
- );
587
- }
588
- throw new Error(
589
- `Failed to change visibility: ${response.status} ${response.statusText}`
590
+ throwCommonHttpError(
591
+ response.status,
592
+ response.statusText,
593
+ errorText,
594
+ "change visibility",
595
+ "Missing ticket or invalid visibility timeout"
590
596
  );
591
597
  }
592
598
  return { updated: true };
593
599
  }
594
600
  };
595
601
 
596
- // src/callback.ts
597
- function validateWildcardPattern(pattern) {
598
- const firstIndex = pattern.indexOf("*");
599
- const lastIndex = pattern.lastIndexOf("*");
600
- if (firstIndex !== lastIndex) {
601
- return false;
602
- }
603
- if (firstIndex === -1) {
604
- return false;
605
- }
606
- if (firstIndex !== pattern.length - 1) {
607
- return false;
608
- }
609
- return true;
610
- }
611
- function matchesWildcardPattern(topicName, pattern) {
612
- const prefix = pattern.slice(0, -1);
613
- return topicName.startsWith(prefix);
614
- }
615
- function findTopicHandler(queueName, handlers) {
616
- const exactHandler = handlers[queueName];
617
- if (exactHandler) {
618
- return exactHandler;
619
- }
620
- for (const pattern in handlers) {
621
- if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
622
- return handlers[pattern];
623
- }
624
- }
625
- return null;
626
- }
627
- async function parseCallback(request) {
628
- const contentType = request.headers.get("content-type");
629
- if (!contentType || !contentType.includes("application/cloudevents+json")) {
630
- throw new Error(
631
- "Invalid content type: expected 'application/cloudevents+json'"
632
- );
633
- }
634
- let cloudEvent;
635
- try {
636
- cloudEvent = await request.json();
637
- } catch (error) {
638
- throw new Error("Failed to parse CloudEvent from request body");
639
- }
640
- if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
641
- throw new Error("Invalid CloudEvent: missing required fields");
642
- }
643
- if (cloudEvent.type !== "com.vercel.queue.v1beta") {
644
- throw new Error(
645
- `Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
646
- );
647
- }
648
- const missingFields = [];
649
- if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
650
- if (!("consumerGroup" in cloudEvent.data))
651
- missingFields.push("consumerGroup");
652
- if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
653
- if (missingFields.length > 0) {
654
- throw new Error(
655
- `Missing required CloudEvent data fields: ${missingFields.join(", ")}`
656
- );
657
- }
658
- const { messageId, queueName, consumerGroup } = cloudEvent.data;
659
- return {
660
- queueName,
661
- consumerGroup,
662
- messageId
663
- };
664
- }
665
- function handleCallback(handlers) {
666
- for (const topicPattern in handlers) {
667
- if (topicPattern.includes("*")) {
668
- if (!validateWildcardPattern(topicPattern)) {
669
- throw new Error(
670
- `Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
671
- );
672
- }
673
- }
674
- }
675
- const routeHandler = async (request) => {
676
- try {
677
- const { queueName, consumerGroup, messageId } = await parseCallback(request);
678
- const topicHandler = findTopicHandler(queueName, handlers);
679
- if (!topicHandler) {
680
- const availableTopics = Object.keys(handlers).join(", ");
681
- return Response.json(
682
- {
683
- error: `No handler found for topic: ${queueName}`,
684
- availableTopics
685
- },
686
- { status: 404 }
687
- );
688
- }
689
- const consumerGroupHandler = topicHandler[consumerGroup];
690
- if (!consumerGroupHandler) {
691
- const availableGroups = Object.keys(topicHandler).join(", ");
692
- return Response.json(
693
- {
694
- error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
695
- availableGroups
696
- },
697
- { status: 404 }
698
- );
699
- }
700
- const client = new QueueClient();
701
- const topic = new Topic(client, queueName);
702
- const cg = topic.consumerGroup(consumerGroup);
703
- await cg.consume(consumerGroupHandler, { messageId });
704
- return Response.json({ status: "success" });
705
- } catch (error) {
706
- console.error("Queue callback error:", error);
707
- if (error instanceof Error && (error.message.includes("Missing required CloudEvent data fields") || error.message.includes("Invalid CloudEvent") || error.message.includes("Invalid CloudEvent type") || error.message.includes("Invalid content type") || error.message.includes("Failed to parse CloudEvent"))) {
708
- return Response.json({ error: error.message }, { status: 400 });
709
- }
710
- return Response.json(
711
- { error: "Failed to process queue message" },
712
- { status: 500 }
713
- );
714
- }
715
- };
716
- if (isDevMode()) {
717
- registerDevRouteHandler(routeHandler, handlers);
718
- }
719
- return routeHandler;
720
- }
721
-
722
602
  // src/dev.ts
723
603
  var GLOBAL_KEY = Symbol.for("@vercel/queue.devHandlers");
724
604
  function getDevHandlerState() {
@@ -1183,10 +1063,138 @@ var Topic = class {
1183
1063
  }
1184
1064
  };
1185
1065
 
1066
+ // src/callback.ts
1067
+ function validateWildcardPattern(pattern) {
1068
+ const firstIndex = pattern.indexOf("*");
1069
+ const lastIndex = pattern.lastIndexOf("*");
1070
+ if (firstIndex !== lastIndex) {
1071
+ return false;
1072
+ }
1073
+ if (firstIndex === -1) {
1074
+ return false;
1075
+ }
1076
+ if (firstIndex !== pattern.length - 1) {
1077
+ return false;
1078
+ }
1079
+ return true;
1080
+ }
1081
+ function matchesWildcardPattern(topicName, pattern) {
1082
+ const prefix = pattern.slice(0, -1);
1083
+ return topicName.startsWith(prefix);
1084
+ }
1085
+ function findTopicHandler(queueName, handlers) {
1086
+ const exactHandler = handlers[queueName];
1087
+ if (exactHandler) {
1088
+ return exactHandler;
1089
+ }
1090
+ for (const pattern in handlers) {
1091
+ if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
1092
+ return handlers[pattern];
1093
+ }
1094
+ }
1095
+ return null;
1096
+ }
1097
+ async function parseCallback(request) {
1098
+ const contentType = request.headers.get("content-type");
1099
+ if (!contentType || !contentType.includes("application/cloudevents+json")) {
1100
+ throw new Error(
1101
+ "Invalid content type: expected 'application/cloudevents+json'"
1102
+ );
1103
+ }
1104
+ let cloudEvent;
1105
+ try {
1106
+ cloudEvent = await request.json();
1107
+ } catch (error) {
1108
+ throw new Error("Failed to parse CloudEvent from request body");
1109
+ }
1110
+ if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
1111
+ throw new Error("Invalid CloudEvent: missing required fields");
1112
+ }
1113
+ if (cloudEvent.type !== "com.vercel.queue.v1beta") {
1114
+ throw new Error(
1115
+ `Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
1116
+ );
1117
+ }
1118
+ const missingFields = [];
1119
+ if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
1120
+ if (!("consumerGroup" in cloudEvent.data))
1121
+ missingFields.push("consumerGroup");
1122
+ if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
1123
+ if (missingFields.length > 0) {
1124
+ throw new Error(
1125
+ `Missing required CloudEvent data fields: ${missingFields.join(", ")}`
1126
+ );
1127
+ }
1128
+ const { messageId, queueName, consumerGroup } = cloudEvent.data;
1129
+ return {
1130
+ queueName,
1131
+ consumerGroup,
1132
+ messageId
1133
+ };
1134
+ }
1135
+ function createCallbackHandler(handlers, client) {
1136
+ for (const topicPattern in handlers) {
1137
+ if (topicPattern.includes("*")) {
1138
+ if (!validateWildcardPattern(topicPattern)) {
1139
+ throw new Error(
1140
+ `Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
1141
+ );
1142
+ }
1143
+ }
1144
+ }
1145
+ const routeHandler = async (request) => {
1146
+ try {
1147
+ const { queueName, consumerGroup, messageId } = await parseCallback(request);
1148
+ const topicHandler = findTopicHandler(queueName, handlers);
1149
+ if (!topicHandler) {
1150
+ const availableTopics = Object.keys(handlers).join(", ");
1151
+ return Response.json(
1152
+ {
1153
+ error: `No handler found for topic: ${queueName}`,
1154
+ availableTopics
1155
+ },
1156
+ { status: 404 }
1157
+ );
1158
+ }
1159
+ const consumerGroupHandler = topicHandler[consumerGroup];
1160
+ if (!consumerGroupHandler) {
1161
+ const availableGroups = Object.keys(topicHandler).join(", ");
1162
+ return Response.json(
1163
+ {
1164
+ error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
1165
+ availableGroups
1166
+ },
1167
+ { status: 404 }
1168
+ );
1169
+ }
1170
+ const topic = new Topic(client, queueName);
1171
+ const cg = topic.consumerGroup(consumerGroup);
1172
+ await cg.consume(consumerGroupHandler, { messageId });
1173
+ return Response.json({ status: "success" });
1174
+ } catch (error) {
1175
+ console.error("Queue callback error:", error);
1176
+ if (error instanceof Error && (error.message.includes("Missing required CloudEvent data fields") || error.message.includes("Invalid CloudEvent") || error.message.includes("Invalid CloudEvent type") || error.message.includes("Invalid content type") || error.message.includes("Failed to parse CloudEvent"))) {
1177
+ return Response.json({ error: error.message }, { status: 400 });
1178
+ }
1179
+ return Response.json(
1180
+ { error: "Failed to process queue message" },
1181
+ { status: 500 }
1182
+ );
1183
+ }
1184
+ };
1185
+ if (isDevMode()) {
1186
+ registerDevRouteHandler(routeHandler, handlers);
1187
+ }
1188
+ return routeHandler;
1189
+ }
1190
+ function handleCallback(handlers, client) {
1191
+ return createCallbackHandler(handlers, client || new QueueClient());
1192
+ }
1193
+
1186
1194
  // src/factory.ts
1187
1195
  async function send(topicName, payload, options) {
1188
1196
  const transport = options?.transport || new JsonTransport();
1189
- const client = new QueueClient();
1197
+ const client = options?.client || new QueueClient();
1190
1198
  const result = await client.sendMessage(
1191
1199
  {
1192
1200
  queueName: topicName,
@@ -1204,9 +1212,14 @@ async function send(topicName, payload, options) {
1204
1212
  }
1205
1213
  async function receive(topicName, consumerGroup, handler, options) {
1206
1214
  const transport = options?.transport || new JsonTransport();
1207
- const client = new QueueClient();
1215
+ const client = options?.client || new QueueClient();
1208
1216
  const topic = new Topic(client, topicName, transport);
1209
- const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
1217
+ const {
1218
+ messageId,
1219
+ skipPayload,
1220
+ client: _,
1221
+ ...consumerGroupOptions
1222
+ } = options || {};
1210
1223
  const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
1211
1224
  if (messageId) {
1212
1225
  if (skipPayload) {
@@ -1221,9 +1234,58 @@ async function receive(topicName, consumerGroup, handler, options) {
1221
1234
  return consumer.consume(handler);
1222
1235
  }
1223
1236
  }
1237
+
1238
+ // src/queue-client.ts
1239
+ var Client = class {
1240
+ client;
1241
+ /**
1242
+ * Create a new Client
1243
+ * @param options QueueClient configuration options
1244
+ */
1245
+ constructor(options = {}) {
1246
+ this.client = new QueueClient(options);
1247
+ }
1248
+ /**
1249
+ * Send a message to a topic
1250
+ * @param topicName Name of the topic to send to
1251
+ * @param payload The data to send
1252
+ * @param options Optional publish options and transport
1253
+ * @returns Promise with the message ID
1254
+ * @throws {BadRequestError} When request parameters are invalid
1255
+ * @throws {UnauthorizedError} When authentication fails
1256
+ * @throws {ForbiddenError} When access is denied (environment mismatch)
1257
+ * @throws {InternalServerError} When server encounters an error
1258
+ */
1259
+ async send(topicName, payload, options) {
1260
+ return send(topicName, payload, {
1261
+ ...options,
1262
+ client: this.client
1263
+ });
1264
+ }
1265
+ /**
1266
+ * Create a callback handler for processing queue messages
1267
+ * Returns a Next.js route handler function that routes messages to appropriate handlers
1268
+ * @param handlers Object with topic-specific handlers organized by consumer groups
1269
+ * @returns A Next.js route handler function
1270
+ *
1271
+ * @example
1272
+ * ```typescript
1273
+ * export const POST = client.handleCallback({
1274
+ * "user-events": {
1275
+ * "welcome": (user, metadata) => console.log("Welcoming user", user),
1276
+ * "analytics": (user, metadata) => console.log("Tracking user", user),
1277
+ * },
1278
+ * });
1279
+ * ```
1280
+ */
1281
+ handleCallback(handlers) {
1282
+ return handleCallback(handlers, this.client);
1283
+ }
1284
+ };
1224
1285
  export {
1225
1286
  BadRequestError,
1226
1287
  BufferTransport,
1288
+ Client,
1227
1289
  ForbiddenError,
1228
1290
  InternalServerError,
1229
1291
  InvalidLimitError,