@vercel/queue 0.3.0 → 0.3.1

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.mts CHANGED
@@ -477,6 +477,14 @@ declare class MessageLockedError extends Error {
477
477
  readonly retryAfter?: number;
478
478
  constructor(messageId: string, retryAfter?: number);
479
479
  }
480
+ /**
481
+ * Error thrown when the server rate-limits a request (HTTP 429).
482
+ */
483
+ declare class TooManyRequestsError extends Error {
484
+ /** Suggested retry delay in seconds, from the Retry-After header, if sent. */
485
+ readonly retryAfter?: number;
486
+ constructor(message?: string, retryAfter?: number);
487
+ }
480
488
  /**
481
489
  * Error thrown when authentication fails.
482
490
  * This typically means the token is missing, invalid, or expired.
@@ -895,4 +903,4 @@ declare function parseRawCallback(body: unknown, headers: Record<string, string
895
903
  */
896
904
  declare function parseCallback(request: Request): Promise<ParsedCallbackRequest>;
897
905
 
898
- export { BadRequestError, type BaseUrlResolver, BufferTransport, CLOUD_EVENT_TYPE_V1BETA, CLOUD_EVENT_TYPE_V2BETA, ConsumerDiscoveryError, ConsumerRegistryNotConfiguredError, DuplicateMessageError, ForbiddenError, InternalServerError, InvalidLimitError, JsonTransport, type Message, MessageAlreadyProcessedError, MessageCorruptedError, type MessageHandler, MessageLockedError, type MessageMetadata, MessageNotAvailableError, MessageNotFoundError, type ParsedCallbackRequest, type ParsedCallbackV1, type ParsedCallbackV2, PollingQueueClient, type PollingQueueClientOptions, QueueClient, type QueueClientOptions, QueueEmptyError, type ReceiveBatchOptions, type ReceiveByIdOptions, type ReceiveOptions, type ReceiveResult, type RegisterDevConsumerOptions, type RetryDirective, type RetryHandler, type SendOptions, type SendResult, StreamTransport, type Transport, UnauthorizedError, type VercelRegion, handleCallback, parseCallback, parseRawCallback, registerDevConsumer, send };
906
+ export { BadRequestError, type BaseUrlResolver, BufferTransport, CLOUD_EVENT_TYPE_V1BETA, CLOUD_EVENT_TYPE_V2BETA, ConsumerDiscoveryError, ConsumerRegistryNotConfiguredError, DuplicateMessageError, ForbiddenError, InternalServerError, InvalidLimitError, JsonTransport, type Message, MessageAlreadyProcessedError, MessageCorruptedError, type MessageHandler, MessageLockedError, type MessageMetadata, MessageNotAvailableError, MessageNotFoundError, type ParsedCallbackRequest, type ParsedCallbackV1, type ParsedCallbackV2, PollingQueueClient, type PollingQueueClientOptions, QueueClient, type QueueClientOptions, QueueEmptyError, type ReceiveBatchOptions, type ReceiveByIdOptions, type ReceiveOptions, type ReceiveResult, type RegisterDevConsumerOptions, type RetryDirective, type RetryHandler, type SendOptions, type SendResult, StreamTransport, TooManyRequestsError, type Transport, UnauthorizedError, type VercelRegion, handleCallback, parseCallback, parseRawCallback, registerDevConsumer, send };
package/dist/index.d.ts CHANGED
@@ -477,6 +477,14 @@ declare class MessageLockedError extends Error {
477
477
  readonly retryAfter?: number;
478
478
  constructor(messageId: string, retryAfter?: number);
479
479
  }
480
+ /**
481
+ * Error thrown when the server rate-limits a request (HTTP 429).
482
+ */
483
+ declare class TooManyRequestsError extends Error {
484
+ /** Suggested retry delay in seconds, from the Retry-After header, if sent. */
485
+ readonly retryAfter?: number;
486
+ constructor(message?: string, retryAfter?: number);
487
+ }
480
488
  /**
481
489
  * Error thrown when authentication fails.
482
490
  * This typically means the token is missing, invalid, or expired.
@@ -895,4 +903,4 @@ declare function parseRawCallback(body: unknown, headers: Record<string, string
895
903
  */
896
904
  declare function parseCallback(request: Request): Promise<ParsedCallbackRequest>;
897
905
 
898
- export { BadRequestError, type BaseUrlResolver, BufferTransport, CLOUD_EVENT_TYPE_V1BETA, CLOUD_EVENT_TYPE_V2BETA, ConsumerDiscoveryError, ConsumerRegistryNotConfiguredError, DuplicateMessageError, ForbiddenError, InternalServerError, InvalidLimitError, JsonTransport, type Message, MessageAlreadyProcessedError, MessageCorruptedError, type MessageHandler, MessageLockedError, type MessageMetadata, MessageNotAvailableError, MessageNotFoundError, type ParsedCallbackRequest, type ParsedCallbackV1, type ParsedCallbackV2, PollingQueueClient, type PollingQueueClientOptions, QueueClient, type QueueClientOptions, QueueEmptyError, type ReceiveBatchOptions, type ReceiveByIdOptions, type ReceiveOptions, type ReceiveResult, type RegisterDevConsumerOptions, type RetryDirective, type RetryHandler, type SendOptions, type SendResult, StreamTransport, type Transport, UnauthorizedError, type VercelRegion, handleCallback, parseCallback, parseRawCallback, registerDevConsumer, send };
906
+ export { BadRequestError, type BaseUrlResolver, BufferTransport, CLOUD_EVENT_TYPE_V1BETA, CLOUD_EVENT_TYPE_V2BETA, ConsumerDiscoveryError, ConsumerRegistryNotConfiguredError, DuplicateMessageError, ForbiddenError, InternalServerError, InvalidLimitError, JsonTransport, type Message, MessageAlreadyProcessedError, MessageCorruptedError, type MessageHandler, MessageLockedError, type MessageMetadata, MessageNotAvailableError, MessageNotFoundError, type ParsedCallbackRequest, type ParsedCallbackV1, type ParsedCallbackV2, PollingQueueClient, type PollingQueueClientOptions, QueueClient, type QueueClientOptions, QueueEmptyError, type ReceiveBatchOptions, type ReceiveByIdOptions, type ReceiveOptions, type ReceiveResult, type RegisterDevConsumerOptions, type RetryDirective, type RetryHandler, type SendOptions, type SendResult, StreamTransport, TooManyRequestsError, type Transport, UnauthorizedError, type VercelRegion, handleCallback, parseCallback, parseRawCallback, registerDevConsumer, send };
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  QueueClient: () => QueueClient,
51
51
  QueueEmptyError: () => QueueEmptyError,
52
52
  StreamTransport: () => StreamTransport,
53
+ TooManyRequestsError: () => TooManyRequestsError,
53
54
  UnauthorizedError: () => UnauthorizedError,
54
55
  handleCallback: () => handleCallback2,
55
56
  parseCallback: () => parseCallback,
@@ -181,6 +182,15 @@ var MessageLockedError = class extends Error {
181
182
  this.retryAfter = retryAfter;
182
183
  }
183
184
  };
185
+ var TooManyRequestsError = class extends Error {
186
+ /** Suggested retry delay in seconds, from the Retry-After header, if sent. */
187
+ retryAfter;
188
+ constructor(message = "Too many requests", retryAfter) {
189
+ super(message);
190
+ this.name = "TooManyRequestsError";
191
+ this.retryAfter = retryAfter;
192
+ }
193
+ };
184
194
  var UnauthorizedError = class extends Error {
185
195
  constructor(message = "Missing or invalid authentication token") {
186
196
  super(message);
@@ -246,6 +256,8 @@ var MIN_VISIBILITY_TIMEOUT_SECONDS = 30;
246
256
  var MAX_RENEWAL_INTERVAL_SECONDS = 60;
247
257
  var MIN_RENEWAL_INTERVAL_SECONDS = 10;
248
258
  var RETRY_INTERVAL_MS = 3e3;
259
+ var DIRECTIVE_CALL_ATTEMPTS = 3;
260
+ var DIRECTIVE_CALL_RETRY_DELAY_MS = 250;
249
261
  function calculateRenewalInterval(visibilityTimeoutSeconds) {
250
262
  return Math.min(
251
263
  MAX_RENEWAL_INTERVAL_SECONDS,
@@ -289,6 +301,54 @@ var ConsumerGroup = class {
289
301
  error instanceof UnauthorizedError || // 401 - auth failed
290
302
  error instanceof ForbiddenError;
291
303
  }
304
+ /**
305
+ * Network-level failures (DNS, connection reset, socket close) surface
306
+ * from fetch as TypeError with the cause attached; any response that
307
+ * actually reached the server — whatever its HTTP status — does not.
308
+ */
309
+ isNetworkError(error) {
310
+ return error instanceof TypeError;
311
+ }
312
+ /**
313
+ * Run a directive call (acknowledge / changeVisibility) with bounded
314
+ * retries. Only failures that are worth re-attempting in process are
315
+ * retried:
316
+ * - network-level failures (the request may never have reached the
317
+ * server), with jittered linear backoff;
318
+ * - 429 responses that carry a Retry-After header, waiting the
319
+ * indicated delay.
320
+ * Everything else — other 4xx, 5xx, 429 without Retry-After — is
321
+ * thrown immediately.
322
+ */
323
+ async directiveCallWithRetries(fn) {
324
+ let lastError;
325
+ for (let attempt = 1; attempt <= DIRECTIVE_CALL_ATTEMPTS; attempt++) {
326
+ try {
327
+ return await fn();
328
+ } catch (error) {
329
+ lastError = error;
330
+ if (attempt === DIRECTIVE_CALL_ATTEMPTS) {
331
+ throw error;
332
+ }
333
+ if (error instanceof TooManyRequestsError) {
334
+ if (error.retryAfter === void 0) {
335
+ throw error;
336
+ }
337
+ await new Promise(
338
+ (resolve2) => setTimeout(resolve2, error.retryAfter * 1e3)
339
+ );
340
+ continue;
341
+ }
342
+ if (!this.isNetworkError(error)) {
343
+ throw error;
344
+ }
345
+ const baseDelayMs = DIRECTIVE_CALL_RETRY_DELAY_MS * attempt;
346
+ const delayMs = baseDelayMs / 2 + Math.random() * (baseDelayMs / 2);
347
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
348
+ }
349
+ }
350
+ throw lastError;
351
+ }
292
352
  /**
293
353
  * Starts a background loop that periodically extends the visibility timeout for a message.
294
354
  *
@@ -433,11 +493,13 @@ var ConsumerGroup = class {
433
493
  if (directive) {
434
494
  if ("acknowledge" in directive && directive.acknowledge) {
435
495
  try {
436
- await this.client.acknowledgeMessage({
437
- queueName: this.topicName,
438
- consumerGroup: this.consumerGroupName,
439
- receiptHandle: message.receiptHandle
440
- });
496
+ await this.directiveCallWithRetries(
497
+ () => this.client.acknowledgeMessage({
498
+ queueName: this.topicName,
499
+ consumerGroup: this.consumerGroupName,
500
+ receiptHandle: message.receiptHandle
501
+ })
502
+ );
441
503
  } catch (ackError) {
442
504
  console.warn("Failed to acknowledge message:", ackError);
443
505
  }
@@ -446,12 +508,14 @@ var ConsumerGroup = class {
446
508
  }
447
509
  if ("afterSeconds" in directive && typeof directive.afterSeconds === "number") {
448
510
  try {
449
- await this.client.changeVisibility({
450
- queueName: this.topicName,
451
- consumerGroup: this.consumerGroupName,
452
- receiptHandle: message.receiptHandle,
453
- visibilityTimeoutSeconds: directive.afterSeconds
454
- });
511
+ await this.directiveCallWithRetries(
512
+ () => this.client.changeVisibility({
513
+ queueName: this.topicName,
514
+ consumerGroup: this.consumerGroupName,
515
+ receiptHandle: message.receiptHandle,
516
+ visibilityTimeoutSeconds: directive.afterSeconds
517
+ })
518
+ );
455
519
  } catch (changeError) {
456
520
  console.warn(
457
521
  "Failed to reschedule message for retry:",
@@ -1514,10 +1578,28 @@ async function consumeStream(stream) {
1514
1578
  reader.releaseLock();
1515
1579
  }
1516
1580
  }
1517
- function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters") {
1581
+ function parseRetryAfterSeconds(value) {
1582
+ if (!value) return void 0;
1583
+ const seconds = Number(value);
1584
+ if (Number.isFinite(seconds) && seconds >= 0) {
1585
+ return seconds;
1586
+ }
1587
+ const dateMs = Date.parse(value);
1588
+ if (!Number.isNaN(dateMs)) {
1589
+ return Math.max(0, (dateMs - Date.now()) / 1e3);
1590
+ }
1591
+ return void 0;
1592
+ }
1593
+ function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters", retryAfterHeader) {
1518
1594
  if (status === 400) {
1519
1595
  throw new BadRequestError(errorText || badRequestDefault);
1520
1596
  }
1597
+ if (status === 429) {
1598
+ throw new TooManyRequestsError(
1599
+ errorText || `Too many requests: ${operation}`,
1600
+ parseRetryAfterSeconds(retryAfterHeader)
1601
+ );
1602
+ }
1521
1603
  if (status === 401) {
1522
1604
  throw new UnauthorizedError(errorText || void 0);
1523
1605
  }
@@ -1694,7 +1776,7 @@ Cause: ${cause}`
1694
1776
  }
1695
1777
  console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
1696
1778
  }
1697
- init.headers.set("User-Agent", `@vercel/queue/${"0.3.0"}`);
1779
+ init.headers.set("User-Agent", `@vercel/queue/${"0.3.1"}`);
1698
1780
  init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
1699
1781
  const fetchInit = this.dispatcher ? { ...init, dispatcher: this.dispatcher } : init;
1700
1782
  const response = await fetch(url, fetchInit);
@@ -1973,7 +2055,8 @@ Cause: ${cause}`
1973
2055
  response.statusText,
1974
2056
  errorText,
1975
2057
  "acknowledge message",
1976
- "Missing or invalid receipt handle"
2058
+ "Missing or invalid receipt handle",
2059
+ response.headers?.get("Retry-After") ?? null
1977
2060
  );
1978
2061
  }
1979
2062
  await response.text();
@@ -2025,7 +2108,8 @@ Cause: ${cause}`
2025
2108
  response.statusText,
2026
2109
  errorText,
2027
2110
  "change visibility",
2028
- "Missing receipt handle or invalid visibility timeout"
2111
+ "Missing receipt handle or invalid visibility timeout",
2112
+ response.headers?.get("Retry-After") ?? null
2029
2113
  );
2030
2114
  }
2031
2115
  await response.text();
@@ -2384,6 +2468,7 @@ function handleCallback2(handler, options) {
2384
2468
  QueueClient,
2385
2469
  QueueEmptyError,
2386
2470
  StreamTransport,
2471
+ TooManyRequestsError,
2387
2472
  UnauthorizedError,
2388
2473
  handleCallback,
2389
2474
  parseCallback,