@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.js CHANGED
@@ -77,6 +77,12 @@ var JsonTransport = class {
77
77
  contentType = "application/json";
78
78
  replacer;
79
79
  reviver;
80
+ /**
81
+ * Create a new JsonTransport.
82
+ * @param options - Optional JSON serialization options
83
+ * @param options.replacer - Custom replacer for JSON.stringify
84
+ * @param options.reviver - Custom reviver for JSON.parse
85
+ */
80
86
  constructor(options = {}) {
81
87
  this.replacer = options.replacer;
82
88
  this.reviver = options.reviver;
@@ -106,6 +112,10 @@ var StreamTransport = class {
106
112
  async deserialize(stream) {
107
113
  return stream;
108
114
  }
115
+ /**
116
+ * Consume any remaining stream data to prevent resource leaks.
117
+ * Called automatically by ConsumerGroup; manual call required for direct client usage.
118
+ */
109
119
  async finalize(payload) {
110
120
  const reader = payload.getReader();
111
121
  try {
@@ -156,6 +166,7 @@ var QueueEmptyError = class extends Error {
156
166
  }
157
167
  };
158
168
  var MessageLockedError = class extends Error {
169
+ /** Suggested retry delay in seconds, if provided by the server. */
159
170
  retryAfter;
160
171
  constructor(messageId, retryAfter) {
161
172
  const retryMessage = retryAfter ? ` Retry after ${retryAfter} seconds.` : " Try again later.";
@@ -201,7 +212,9 @@ var MessageAlreadyProcessedError = class extends Error {
201
212
  }
202
213
  };
203
214
  var ConcurrencyLimitError = class extends Error {
215
+ /** Current number of in-flight messages for this consumer group. */
204
216
  currentInflight;
217
+ /** Maximum allowed concurrent messages (as configured). */
205
218
  maxConcurrency;
206
219
  constructor(message = "Concurrency limit exceeded", currentInflight, maxConcurrency) {
207
220
  super(message);
@@ -588,19 +601,55 @@ var QueueClient = class {
588
601
  }
589
602
  return response;
590
603
  }
604
+ /**
605
+ * Send a message to a topic.
606
+ *
607
+ * @param options - Message options including queue name, payload, and optional settings
608
+ * @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
609
+ * @param options.payload - Message payload
610
+ * @param options.idempotencyKey - Optional deduplication key (dedup window: min(retention, 24h))
611
+ * @param options.retentionSeconds - Message TTL (default: 86400, min: 60, max: 86400)
612
+ * @param options.delaySeconds - Delivery delay (default: 0, max: retentionSeconds)
613
+ * @param transport - Serializer for the payload
614
+ * @returns Promise with the generated messageId
615
+ * @throws {DuplicateMessageError} When idempotency key was already used
616
+ * @throws {ConsumerDiscoveryError} When consumer discovery fails
617
+ * @throws {ConsumerRegistryNotConfiguredError} When registry not configured
618
+ * @throws {BadRequestError} When parameters are invalid
619
+ * @throws {UnauthorizedError} When authentication fails
620
+ * @throws {ForbiddenError} When access is denied
621
+ * @throws {InternalServerError} When server encounters an error
622
+ */
591
623
  async sendMessage(options, transport) {
592
624
  const {
593
625
  queueName,
594
626
  payload,
595
627
  idempotencyKey,
596
628
  retentionSeconds,
597
- delaySeconds
629
+ delaySeconds,
630
+ headers: optionHeaders
598
631
  } = options;
599
- const headers = new Headers({
600
- Authorization: `Bearer ${await this.getToken()}`,
601
- "Content-Type": transport.contentType,
602
- ...this.customHeaders
603
- });
632
+ const headers = new Headers();
633
+ if (this.customHeaders) {
634
+ for (const [name, value] of Object.entries(this.customHeaders)) {
635
+ headers.append(name, value);
636
+ }
637
+ }
638
+ if (optionHeaders) {
639
+ const protectedHeaderNames = /* @__PURE__ */ new Set(["authorization", "content-type"]);
640
+ const isProtectedHeader = (name) => {
641
+ const lower = name.toLowerCase();
642
+ if (protectedHeaderNames.has(lower)) return true;
643
+ return lower.startsWith("vqs-");
644
+ };
645
+ for (const [name, value] of Object.entries(optionHeaders)) {
646
+ if (!isProtectedHeader(name) && value !== void 0) {
647
+ headers.append(name, value);
648
+ }
649
+ }
650
+ }
651
+ headers.set("Authorization", `Bearer ${await this.getToken()}`);
652
+ headers.set("Content-Type", transport.contentType);
604
653
  const deploymentId = this.getSendDeploymentId();
605
654
  if (deploymentId) {
606
655
  headers.set("Vqs-Deployment-Id", deploymentId);
@@ -650,6 +699,25 @@ var QueueClient = class {
650
699
  const responseData = await response.json();
651
700
  return responseData;
652
701
  }
702
+ /**
703
+ * Receive messages from a topic as an async generator.
704
+ *
705
+ * @param options - Receive options
706
+ * @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
707
+ * @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
708
+ * @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
709
+ * @param options.limit - Max messages to retrieve (default: 1, min: 1, max: 10)
710
+ * @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
711
+ * @param transport - Deserializer for message payloads
712
+ * @yields Message objects with payload, messageId, receiptHandle, etc.
713
+ * @throws {QueueEmptyError} When no messages available
714
+ * @throws {InvalidLimitError} When limit is outside 1-10 range
715
+ * @throws {ConcurrencyLimitError} When maxConcurrency exceeded
716
+ * @throws {BadRequestError} When parameters are invalid
717
+ * @throws {UnauthorizedError} When authentication fails
718
+ * @throws {ForbiddenError} When access is denied
719
+ * @throws {InternalServerError} When server encounters an error
720
+ */
653
721
  async *receiveMessages(options, transport) {
654
722
  const {
655
723
  queueName,
@@ -735,6 +803,26 @@ var QueueClient = class {
735
803
  }
736
804
  }
737
805
  }
806
+ /**
807
+ * Receive a specific message by its ID.
808
+ *
809
+ * @param options - Receive options
810
+ * @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
811
+ * @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
812
+ * @param options.messageId - Message ID to retrieve
813
+ * @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
814
+ * @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
815
+ * @param transport - Deserializer for the message payload
816
+ * @returns Promise with the message
817
+ * @throws {MessageNotFoundError} When message doesn't exist
818
+ * @throws {MessageNotAvailableError} When message is in wrong state or was a duplicate
819
+ * @throws {MessageAlreadyProcessedError} When message was already processed
820
+ * @throws {ConcurrencyLimitError} When maxConcurrency exceeded
821
+ * @throws {BadRequestError} When parameters are invalid
822
+ * @throws {UnauthorizedError} When authentication fails
823
+ * @throws {ForbiddenError} When access is denied
824
+ * @throws {InternalServerError} When server encounters an error
825
+ */
738
826
  async receiveMessageById(options, transport) {
739
827
  const {
740
828
  queueName,
@@ -829,6 +917,21 @@ var QueueClient = class {
829
917
  }
830
918
  throw new MessageNotFoundError(messageId);
831
919
  }
920
+ /**
921
+ * Delete (acknowledge) a message after successful processing.
922
+ *
923
+ * @param options - Delete options
924
+ * @param options.queueName - Topic name
925
+ * @param options.consumerGroup - Consumer group name
926
+ * @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
927
+ * @returns Promise indicating deletion success
928
+ * @throws {MessageNotFoundError} When receipt handle not found
929
+ * @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
930
+ * @throws {BadRequestError} When parameters are invalid
931
+ * @throws {UnauthorizedError} When authentication fails
932
+ * @throws {ForbiddenError} When access is denied
933
+ * @throws {InternalServerError} When server encounters an error
934
+ */
832
935
  async deleteMessage(options) {
833
936
  const { queueName, consumerGroup, receiptHandle } = options;
834
937
  const headers = new Headers({
@@ -873,6 +976,23 @@ var QueueClient = class {
873
976
  }
874
977
  return { deleted: true };
875
978
  }
979
+ /**
980
+ * Extend or change the visibility timeout of a message.
981
+ * Used to prevent message redelivery while still processing.
982
+ *
983
+ * @param options - Visibility options
984
+ * @param options.queueName - Topic name
985
+ * @param options.consumerGroup - Consumer group name
986
+ * @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
987
+ * @param options.visibilityTimeoutSeconds - New timeout (min: 0, max: 3600, cannot exceed message expiration)
988
+ * @returns Promise indicating success
989
+ * @throws {MessageNotFoundError} When receipt handle not found
990
+ * @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
991
+ * @throws {BadRequestError} When parameters are invalid
992
+ * @throws {UnauthorizedError} When authentication fails
993
+ * @throws {ForbiddenError} When access is denied
994
+ * @throws {InternalServerError} When server encounters an error
995
+ */
876
996
  async changeVisibility(options) {
877
997
  const {
878
998
  queueName,
@@ -995,36 +1115,26 @@ var ConsumerGroup = class {
995
1115
  refreshInterval;
996
1116
  transport;
997
1117
  /**
998
- * Create a new ConsumerGroup instance
999
- * @param client QueueClient instance to use for API calls
1000
- * @param topicName Name of the topic to consume from
1001
- * @param consumerGroupName Name of the consumer group
1002
- * @param options Optional configuration
1118
+ * Create a new ConsumerGroup instance.
1119
+ *
1120
+ * @param client - QueueClient instance to use for API calls
1121
+ * @param topicName - Name of the topic to consume from (pattern: `[A-Za-z0-9_-]+`)
1122
+ * @param consumerGroupName - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
1123
+ * @param options - Optional configuration
1124
+ * @param options.transport - Payload serializer (default: JsonTransport)
1125
+ * @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
1126
+ * @param options.visibilityRefreshInterval - Lock refresh interval in seconds (default: visibilityTimeout / 3)
1003
1127
  */
1004
1128
  constructor(client, topicName, consumerGroupName, options = {}) {
1005
1129
  this.client = client;
1006
1130
  this.topicName = topicName;
1007
1131
  this.consumerGroupName = consumerGroupName;
1008
- this.visibilityTimeout = options.visibilityTimeoutSeconds || 30;
1009
- this.refreshInterval = options.refreshInterval || 10;
1132
+ this.visibilityTimeout = options.visibilityTimeoutSeconds ?? 30;
1133
+ this.refreshInterval = options.visibilityRefreshInterval ?? Math.floor(this.visibilityTimeout / 3);
1010
1134
  this.transport = options.transport || new JsonTransport();
1011
1135
  }
1012
1136
  /**
1013
1137
  * Starts a background loop that periodically extends the visibility timeout for a message.
1014
- * This prevents the message from becoming visible to other consumers while it's being processed.
1015
- *
1016
- * The extension loop runs every `refreshInterval` seconds and updates the message's
1017
- * visibility timeout to `visibilityTimeout` seconds from the current time.
1018
- *
1019
- * @param receiptHandle - The receipt handle that proves ownership of the message
1020
- * @returns A function that when called will stop the extension loop
1021
- *
1022
- * @remarks
1023
- * - The first extension attempt occurs after `refreshInterval` seconds, not immediately
1024
- * - If an extension fails, the loop terminates with an error logged to console
1025
- * - The returned stop function is idempotent - calling it multiple times is safe
1026
- * - By default, the stop function returns immediately without waiting for in-flight
1027
- * - Pass `true` to the stop function to wait for any in-flight extension to complete
1028
1138
  */
1029
1139
  startVisibilityExtension(receiptHandle) {
1030
1140
  let isRunning = true;
@@ -1186,7 +1296,8 @@ var Topic = class {
1186
1296
  payload,
1187
1297
  idempotencyKey: options?.idempotencyKey,
1188
1298
  retentionSeconds: options?.retentionSeconds,
1189
- delaySeconds: options?.delaySeconds
1299
+ delaySeconds: options?.delaySeconds,
1300
+ headers: options?.headers
1190
1301
  },
1191
1302
  this.transport
1192
1303
  );
@@ -1296,7 +1407,7 @@ async function parseCallback(request) {
1296
1407
  messageId
1297
1408
  };
1298
1409
  }
1299
- function createCallbackHandler(handlers, client) {
1410
+ function createCallbackHandler(handlers, client, visibilityTimeoutSeconds) {
1300
1411
  for (const topicPattern in handlers) {
1301
1412
  if (topicPattern.includes("*")) {
1302
1413
  if (!validateWildcardPattern(topicPattern)) {
@@ -1332,7 +1443,10 @@ function createCallbackHandler(handlers, client) {
1332
1443
  );
1333
1444
  }
1334
1445
  const topic = new Topic(client, queueName);
1335
- const cg = topic.consumerGroup(consumerGroup);
1446
+ const cg = topic.consumerGroup(
1447
+ consumerGroup,
1448
+ visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : void 0
1449
+ );
1336
1450
  await cg.consume(consumerGroupHandler, { messageId });
1337
1451
  return Response.json({ status: "success" });
1338
1452
  } catch (error) {
@@ -1348,8 +1462,12 @@ function createCallbackHandler(handlers, client) {
1348
1462
  };
1349
1463
  return routeHandler;
1350
1464
  }
1351
- function handleCallback(handlers, client) {
1352
- return createCallbackHandler(handlers, client || new QueueClient());
1465
+ function handleCallback(handlers, options) {
1466
+ return createCallbackHandler(
1467
+ handlers,
1468
+ options?.client || new QueueClient(),
1469
+ options?.visibilityTimeoutSeconds
1470
+ );
1353
1471
  }
1354
1472
 
1355
1473
  // src/factory.ts
@@ -1362,7 +1480,8 @@ async function send(topicName, payload, options) {
1362
1480
  payload,
1363
1481
  idempotencyKey: options?.idempotencyKey,
1364
1482
  retentionSeconds: options?.retentionSeconds,
1365
- delaySeconds: options?.delaySeconds
1483
+ delaySeconds: options?.delaySeconds,
1484
+ headers: options?.headers
1366
1485
  },
1367
1486
  transport
1368
1487
  );
@@ -1412,23 +1531,39 @@ var Client = class {
1412
1531
  });
1413
1532
  }
1414
1533
  /**
1415
- * Create a callback handler for processing queue messages
1416
- * Returns a Next.js route handler function that routes messages to appropriate handlers
1417
- * @param handlers Object with topic-specific handlers organized by consumer groups
1534
+ * Create a callback handler for processing queue messages.
1535
+ * Returns a Next.js route handler function that routes messages to appropriate handlers.
1536
+ *
1537
+ * @param handlers - Object with topic-specific handlers organized by consumer groups
1538
+ * @param options - Optional configuration
1539
+ * @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
1418
1540
  * @returns A Next.js route handler function
1419
1541
  *
1420
1542
  * @example
1421
1543
  * ```typescript
1544
+ * // Basic usage
1422
1545
  * export const POST = client.handleCallback({
1423
1546
  * "user-events": {
1424
1547
  * "welcome": (user, metadata) => console.log("Welcoming user", user),
1425
1548
  * "analytics": (user, metadata) => console.log("Tracking user", user),
1426
1549
  * },
1427
1550
  * });
1551
+ *
1552
+ * // With custom visibility timeout
1553
+ * export const POST = client.handleCallback({
1554
+ * "video-processing": {
1555
+ * "transcode": async (video) => await transcodeVideo(video),
1556
+ * },
1557
+ * }, {
1558
+ * visibilityTimeoutSeconds: 300, // 5 minutes for long operations
1559
+ * });
1428
1560
  * ```
1429
1561
  */
1430
- handleCallback(handlers) {
1431
- return handleCallback(handlers, this.client);
1562
+ handleCallback(handlers, options) {
1563
+ return handleCallback(handlers, {
1564
+ ...options,
1565
+ client: this.client
1566
+ });
1432
1567
  }
1433
1568
  };
1434
1569
  // Annotate the CommonJS export names for ESM import in node: