@vercel/queue 0.2.1 → 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.2.1"}`);
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);
@@ -1788,6 +1870,7 @@ Cause: ${cause}`
1788
1870
  );
1789
1871
  }
1790
1872
  if (response.status === 202) {
1873
+ await response.text();
1791
1874
  return { messageId: null };
1792
1875
  }
1793
1876
  const responseData = await response.json();
@@ -1825,6 +1908,7 @@ Cause: ${cause}`
1825
1908
  }
1826
1909
  );
1827
1910
  if (response.status === 204) {
1911
+ await response.text();
1828
1912
  return;
1829
1913
  }
1830
1914
  if (!response.ok) {
@@ -1971,9 +2055,11 @@ Cause: ${cause}`
1971
2055
  response.statusText,
1972
2056
  errorText,
1973
2057
  "acknowledge message",
1974
- "Missing or invalid receipt handle"
2058
+ "Missing or invalid receipt handle",
2059
+ response.headers?.get("Retry-After") ?? null
1975
2060
  );
1976
2061
  }
2062
+ await response.text();
1977
2063
  return { acknowledged: true };
1978
2064
  }
1979
2065
  async changeVisibility(options) {
@@ -2022,9 +2108,11 @@ Cause: ${cause}`
2022
2108
  response.statusText,
2023
2109
  errorText,
2024
2110
  "change visibility",
2025
- "Missing receipt handle or invalid visibility timeout"
2111
+ "Missing receipt handle or invalid visibility timeout",
2112
+ response.headers?.get("Retry-After") ?? null
2026
2113
  );
2027
2114
  }
2115
+ await response.text();
2028
2116
  return { success: true };
2029
2117
  }
2030
2118
  };
@@ -2380,6 +2468,7 @@ function handleCallback2(handler, options) {
2380
2468
  QueueClient,
2381
2469
  QueueEmptyError,
2382
2470
  StreamTransport,
2471
+ TooManyRequestsError,
2383
2472
  UnauthorizedError,
2384
2473
  handleCallback,
2385
2474
  parseCallback,