@vercel/queue 0.0.0-alpha.34 → 0.0.0-alpha.36

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
@@ -19,6 +19,12 @@ var JsonTransport = class {
19
19
  contentType = "application/json";
20
20
  replacer;
21
21
  reviver;
22
+ /**
23
+ * Create a new JsonTransport.
24
+ * @param options - Optional JSON serialization options
25
+ * @param options.replacer - Custom replacer for JSON.stringify
26
+ * @param options.reviver - Custom reviver for JSON.parse
27
+ */
22
28
  constructor(options = {}) {
23
29
  this.replacer = options.replacer;
24
30
  this.reviver = options.reviver;
@@ -48,6 +54,10 @@ var StreamTransport = class {
48
54
  async deserialize(stream) {
49
55
  return stream;
50
56
  }
57
+ /**
58
+ * Consume any remaining stream data to prevent resource leaks.
59
+ * Called automatically by ConsumerGroup; manual call required for direct client usage.
60
+ */
51
61
  async finalize(payload) {
52
62
  const reader = payload.getReader();
53
63
  try {
@@ -98,6 +108,7 @@ var QueueEmptyError = class extends Error {
98
108
  }
99
109
  };
100
110
  var MessageLockedError = class extends Error {
111
+ /** Suggested retry delay in seconds, if provided by the server. */
101
112
  retryAfter;
102
113
  constructor(messageId, retryAfter) {
103
114
  const retryMessage = retryAfter ? ` Retry after ${retryAfter} seconds.` : " Try again later.";
@@ -143,7 +154,9 @@ var MessageAlreadyProcessedError = class extends Error {
143
154
  }
144
155
  };
145
156
  var ConcurrencyLimitError = class extends Error {
157
+ /** Current number of in-flight messages for this consumer group. */
146
158
  currentInflight;
159
+ /** Maximum allowed concurrent messages (as configured). */
147
160
  maxConcurrency;
148
161
  constructor(message = "Concurrency limit exceeded", currentInflight, maxConcurrency) {
149
162
  super(message);
@@ -530,19 +543,55 @@ var QueueClient = class {
530
543
  }
531
544
  return response;
532
545
  }
546
+ /**
547
+ * Send a message to a topic.
548
+ *
549
+ * @param options - Message options including queue name, payload, and optional settings
550
+ * @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
551
+ * @param options.payload - Message payload
552
+ * @param options.idempotencyKey - Optional deduplication key (dedup window: min(retention, 24h))
553
+ * @param options.retentionSeconds - Message TTL (default: 86400, min: 60, max: 86400)
554
+ * @param options.delaySeconds - Delivery delay (default: 0, max: retentionSeconds)
555
+ * @param transport - Serializer for the payload
556
+ * @returns Promise with the generated messageId
557
+ * @throws {DuplicateMessageError} When idempotency key was already used
558
+ * @throws {ConsumerDiscoveryError} When consumer discovery fails
559
+ * @throws {ConsumerRegistryNotConfiguredError} When registry not configured
560
+ * @throws {BadRequestError} When parameters are invalid
561
+ * @throws {UnauthorizedError} When authentication fails
562
+ * @throws {ForbiddenError} When access is denied
563
+ * @throws {InternalServerError} When server encounters an error
564
+ */
533
565
  async sendMessage(options, transport) {
534
566
  const {
535
567
  queueName,
536
568
  payload,
537
569
  idempotencyKey,
538
570
  retentionSeconds,
539
- delaySeconds
571
+ delaySeconds,
572
+ headers: optionHeaders
540
573
  } = options;
541
- const headers = new Headers({
542
- Authorization: `Bearer ${await this.getToken()}`,
543
- "Content-Type": transport.contentType,
544
- ...this.customHeaders
545
- });
574
+ const headers = new Headers();
575
+ if (this.customHeaders) {
576
+ for (const [name, value] of Object.entries(this.customHeaders)) {
577
+ headers.append(name, value);
578
+ }
579
+ }
580
+ if (optionHeaders) {
581
+ const protectedHeaderNames = /* @__PURE__ */ new Set(["authorization", "content-type"]);
582
+ const isProtectedHeader = (name) => {
583
+ const lower = name.toLowerCase();
584
+ if (protectedHeaderNames.has(lower)) return true;
585
+ return lower.startsWith("vqs-");
586
+ };
587
+ for (const [name, value] of Object.entries(optionHeaders)) {
588
+ if (!isProtectedHeader(name) && value !== void 0) {
589
+ headers.append(name, value);
590
+ }
591
+ }
592
+ }
593
+ headers.set("Authorization", `Bearer ${await this.getToken()}`);
594
+ headers.set("Content-Type", transport.contentType);
546
595
  const deploymentId = this.getSendDeploymentId();
547
596
  if (deploymentId) {
548
597
  headers.set("Vqs-Deployment-Id", deploymentId);
@@ -592,6 +641,25 @@ var QueueClient = class {
592
641
  const responseData = await response.json();
593
642
  return responseData;
594
643
  }
644
+ /**
645
+ * Receive messages from a topic as an async generator.
646
+ *
647
+ * @param options - Receive options
648
+ * @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
649
+ * @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
650
+ * @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
651
+ * @param options.limit - Max messages to retrieve (default: 1, min: 1, max: 10)
652
+ * @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
653
+ * @param transport - Deserializer for message payloads
654
+ * @yields Message objects with payload, messageId, receiptHandle, etc.
655
+ * @throws {QueueEmptyError} When no messages available
656
+ * @throws {InvalidLimitError} When limit is outside 1-10 range
657
+ * @throws {ConcurrencyLimitError} When maxConcurrency exceeded
658
+ * @throws {BadRequestError} When parameters are invalid
659
+ * @throws {UnauthorizedError} When authentication fails
660
+ * @throws {ForbiddenError} When access is denied
661
+ * @throws {InternalServerError} When server encounters an error
662
+ */
595
663
  async *receiveMessages(options, transport) {
596
664
  const {
597
665
  queueName,
@@ -677,6 +745,26 @@ var QueueClient = class {
677
745
  }
678
746
  }
679
747
  }
748
+ /**
749
+ * Receive a specific message by its ID.
750
+ *
751
+ * @param options - Receive options
752
+ * @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
753
+ * @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
754
+ * @param options.messageId - Message ID to retrieve
755
+ * @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
756
+ * @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
757
+ * @param transport - Deserializer for the message payload
758
+ * @returns Promise with the message
759
+ * @throws {MessageNotFoundError} When message doesn't exist
760
+ * @throws {MessageNotAvailableError} When message is in wrong state or was a duplicate
761
+ * @throws {MessageAlreadyProcessedError} When message was already processed
762
+ * @throws {ConcurrencyLimitError} When maxConcurrency exceeded
763
+ * @throws {BadRequestError} When parameters are invalid
764
+ * @throws {UnauthorizedError} When authentication fails
765
+ * @throws {ForbiddenError} When access is denied
766
+ * @throws {InternalServerError} When server encounters an error
767
+ */
680
768
  async receiveMessageById(options, transport) {
681
769
  const {
682
770
  queueName,
@@ -771,6 +859,21 @@ var QueueClient = class {
771
859
  }
772
860
  throw new MessageNotFoundError(messageId);
773
861
  }
862
+ /**
863
+ * Delete (acknowledge) a message after successful processing.
864
+ *
865
+ * @param options - Delete options
866
+ * @param options.queueName - Topic name
867
+ * @param options.consumerGroup - Consumer group name
868
+ * @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
869
+ * @returns Promise indicating deletion success
870
+ * @throws {MessageNotFoundError} When receipt handle not found
871
+ * @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
872
+ * @throws {BadRequestError} When parameters are invalid
873
+ * @throws {UnauthorizedError} When authentication fails
874
+ * @throws {ForbiddenError} When access is denied
875
+ * @throws {InternalServerError} When server encounters an error
876
+ */
774
877
  async deleteMessage(options) {
775
878
  const { queueName, consumerGroup, receiptHandle } = options;
776
879
  const headers = new Headers({
@@ -815,6 +918,23 @@ var QueueClient = class {
815
918
  }
816
919
  return { deleted: true };
817
920
  }
921
+ /**
922
+ * Extend or change the visibility timeout of a message.
923
+ * Used to prevent message redelivery while still processing.
924
+ *
925
+ * @param options - Visibility options
926
+ * @param options.queueName - Topic name
927
+ * @param options.consumerGroup - Consumer group name
928
+ * @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
929
+ * @param options.visibilityTimeoutSeconds - New timeout (min: 0, max: 3600, cannot exceed message expiration)
930
+ * @returns Promise indicating success
931
+ * @throws {MessageNotFoundError} When receipt handle not found
932
+ * @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
933
+ * @throws {BadRequestError} When parameters are invalid
934
+ * @throws {UnauthorizedError} When authentication fails
935
+ * @throws {ForbiddenError} When access is denied
936
+ * @throws {InternalServerError} When server encounters an error
937
+ */
818
938
  async changeVisibility(options) {
819
939
  const {
820
940
  queueName,
@@ -937,36 +1057,26 @@ var ConsumerGroup = class {
937
1057
  refreshInterval;
938
1058
  transport;
939
1059
  /**
940
- * Create a new ConsumerGroup instance
941
- * @param client QueueClient instance to use for API calls
942
- * @param topicName Name of the topic to consume from
943
- * @param consumerGroupName Name of the consumer group
944
- * @param options Optional configuration
1060
+ * Create a new ConsumerGroup instance.
1061
+ *
1062
+ * @param client - QueueClient instance to use for API calls
1063
+ * @param topicName - Name of the topic to consume from (pattern: `[A-Za-z0-9_-]+`)
1064
+ * @param consumerGroupName - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
1065
+ * @param options - Optional configuration
1066
+ * @param options.transport - Payload serializer (default: JsonTransport)
1067
+ * @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
1068
+ * @param options.visibilityRefreshInterval - Lock refresh interval in seconds (default: visibilityTimeout / 3)
945
1069
  */
946
1070
  constructor(client, topicName, consumerGroupName, options = {}) {
947
1071
  this.client = client;
948
1072
  this.topicName = topicName;
949
1073
  this.consumerGroupName = consumerGroupName;
950
- this.visibilityTimeout = options.visibilityTimeoutSeconds || 30;
951
- this.refreshInterval = options.refreshInterval || 10;
1074
+ this.visibilityTimeout = options.visibilityTimeoutSeconds ?? 30;
1075
+ this.refreshInterval = options.visibilityRefreshInterval ?? Math.floor(this.visibilityTimeout / 3);
952
1076
  this.transport = options.transport || new JsonTransport();
953
1077
  }
954
1078
  /**
955
1079
  * Starts a background loop that periodically extends the visibility timeout for a message.
956
- * This prevents the message from becoming visible to other consumers while it's being processed.
957
- *
958
- * The extension loop runs every `refreshInterval` seconds and updates the message's
959
- * visibility timeout to `visibilityTimeout` seconds from the current time.
960
- *
961
- * @param receiptHandle - The receipt handle that proves ownership of the message
962
- * @returns A function that when called will stop the extension loop
963
- *
964
- * @remarks
965
- * - The first extension attempt occurs after `refreshInterval` seconds, not immediately
966
- * - If an extension fails, the loop terminates with an error logged to console
967
- * - The returned stop function is idempotent - calling it multiple times is safe
968
- * - By default, the stop function returns immediately without waiting for in-flight
969
- * - Pass `true` to the stop function to wait for any in-flight extension to complete
970
1080
  */
971
1081
  startVisibilityExtension(receiptHandle) {
972
1082
  let isRunning = true;
@@ -1128,7 +1238,8 @@ var Topic = class {
1128
1238
  payload,
1129
1239
  idempotencyKey: options?.idempotencyKey,
1130
1240
  retentionSeconds: options?.retentionSeconds,
1131
- delaySeconds: options?.delaySeconds
1241
+ delaySeconds: options?.delaySeconds,
1242
+ headers: options?.headers
1132
1243
  },
1133
1244
  this.transport
1134
1245
  );
@@ -1238,7 +1349,7 @@ async function parseCallback(request) {
1238
1349
  messageId
1239
1350
  };
1240
1351
  }
1241
- function createCallbackHandler(handlers, client) {
1352
+ function createCallbackHandler(handlers, client, visibilityTimeoutSeconds) {
1242
1353
  for (const topicPattern in handlers) {
1243
1354
  if (topicPattern.includes("*")) {
1244
1355
  if (!validateWildcardPattern(topicPattern)) {
@@ -1274,7 +1385,10 @@ function createCallbackHandler(handlers, client) {
1274
1385
  );
1275
1386
  }
1276
1387
  const topic = new Topic(client, queueName);
1277
- const cg = topic.consumerGroup(consumerGroup);
1388
+ const cg = topic.consumerGroup(
1389
+ consumerGroup,
1390
+ visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : void 0
1391
+ );
1278
1392
  await cg.consume(consumerGroupHandler, { messageId });
1279
1393
  return Response.json({ status: "success" });
1280
1394
  } catch (error) {
@@ -1290,8 +1404,12 @@ function createCallbackHandler(handlers, client) {
1290
1404
  };
1291
1405
  return routeHandler;
1292
1406
  }
1293
- function handleCallback(handlers, client) {
1294
- return createCallbackHandler(handlers, client || new QueueClient());
1407
+ function handleCallback(handlers, options) {
1408
+ return createCallbackHandler(
1409
+ handlers,
1410
+ options?.client || new QueueClient(),
1411
+ options?.visibilityTimeoutSeconds
1412
+ );
1295
1413
  }
1296
1414
 
1297
1415
  // src/factory.ts
@@ -1304,7 +1422,8 @@ async function send(topicName, payload, options) {
1304
1422
  payload,
1305
1423
  idempotencyKey: options?.idempotencyKey,
1306
1424
  retentionSeconds: options?.retentionSeconds,
1307
- delaySeconds: options?.delaySeconds
1425
+ delaySeconds: options?.delaySeconds,
1426
+ headers: options?.headers
1308
1427
  },
1309
1428
  transport
1310
1429
  );
@@ -1354,23 +1473,39 @@ var Client = class {
1354
1473
  });
1355
1474
  }
1356
1475
  /**
1357
- * Create a callback handler for processing queue messages
1358
- * Returns a Next.js route handler function that routes messages to appropriate handlers
1359
- * @param handlers Object with topic-specific handlers organized by consumer groups
1476
+ * Create a callback handler for processing queue messages.
1477
+ * Returns a Next.js route handler function that routes messages to appropriate handlers.
1478
+ *
1479
+ * @param handlers - Object with topic-specific handlers organized by consumer groups
1480
+ * @param options - Optional configuration
1481
+ * @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
1360
1482
  * @returns A Next.js route handler function
1361
1483
  *
1362
1484
  * @example
1363
1485
  * ```typescript
1486
+ * // Basic usage
1364
1487
  * export const POST = client.handleCallback({
1365
1488
  * "user-events": {
1366
1489
  * "welcome": (user, metadata) => console.log("Welcoming user", user),
1367
1490
  * "analytics": (user, metadata) => console.log("Tracking user", user),
1368
1491
  * },
1369
1492
  * });
1493
+ *
1494
+ * // With custom visibility timeout
1495
+ * export const POST = client.handleCallback({
1496
+ * "video-processing": {
1497
+ * "transcode": async (video) => await transcodeVideo(video),
1498
+ * },
1499
+ * }, {
1500
+ * visibilityTimeoutSeconds: 300, // 5 minutes for long operations
1501
+ * });
1370
1502
  * ```
1371
1503
  */
1372
- handleCallback(handlers) {
1373
- return handleCallback(handlers, this.client);
1504
+ handleCallback(handlers, options) {
1505
+ return handleCallback(handlers, {
1506
+ ...options,
1507
+ client: this.client
1508
+ });
1374
1509
  }
1375
1510
  };
1376
1511
  export {