@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 +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +104 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +103 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -120,6 +120,15 @@ var MessageLockedError = class extends Error {
|
|
|
120
120
|
this.retryAfter = retryAfter;
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
|
+
var TooManyRequestsError = class extends Error {
|
|
124
|
+
/** Suggested retry delay in seconds, from the Retry-After header, if sent. */
|
|
125
|
+
retryAfter;
|
|
126
|
+
constructor(message = "Too many requests", retryAfter) {
|
|
127
|
+
super(message);
|
|
128
|
+
this.name = "TooManyRequestsError";
|
|
129
|
+
this.retryAfter = retryAfter;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
123
132
|
var UnauthorizedError = class extends Error {
|
|
124
133
|
constructor(message = "Missing or invalid authentication token") {
|
|
125
134
|
super(message);
|
|
@@ -185,6 +194,8 @@ var MIN_VISIBILITY_TIMEOUT_SECONDS = 30;
|
|
|
185
194
|
var MAX_RENEWAL_INTERVAL_SECONDS = 60;
|
|
186
195
|
var MIN_RENEWAL_INTERVAL_SECONDS = 10;
|
|
187
196
|
var RETRY_INTERVAL_MS = 3e3;
|
|
197
|
+
var DIRECTIVE_CALL_ATTEMPTS = 3;
|
|
198
|
+
var DIRECTIVE_CALL_RETRY_DELAY_MS = 250;
|
|
188
199
|
function calculateRenewalInterval(visibilityTimeoutSeconds) {
|
|
189
200
|
return Math.min(
|
|
190
201
|
MAX_RENEWAL_INTERVAL_SECONDS,
|
|
@@ -228,6 +239,54 @@ var ConsumerGroup = class {
|
|
|
228
239
|
error instanceof UnauthorizedError || // 401 - auth failed
|
|
229
240
|
error instanceof ForbiddenError;
|
|
230
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Network-level failures (DNS, connection reset, socket close) surface
|
|
244
|
+
* from fetch as TypeError with the cause attached; any response that
|
|
245
|
+
* actually reached the server — whatever its HTTP status — does not.
|
|
246
|
+
*/
|
|
247
|
+
isNetworkError(error) {
|
|
248
|
+
return error instanceof TypeError;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Run a directive call (acknowledge / changeVisibility) with bounded
|
|
252
|
+
* retries. Only failures that are worth re-attempting in process are
|
|
253
|
+
* retried:
|
|
254
|
+
* - network-level failures (the request may never have reached the
|
|
255
|
+
* server), with jittered linear backoff;
|
|
256
|
+
* - 429 responses that carry a Retry-After header, waiting the
|
|
257
|
+
* indicated delay.
|
|
258
|
+
* Everything else — other 4xx, 5xx, 429 without Retry-After — is
|
|
259
|
+
* thrown immediately.
|
|
260
|
+
*/
|
|
261
|
+
async directiveCallWithRetries(fn) {
|
|
262
|
+
let lastError;
|
|
263
|
+
for (let attempt = 1; attempt <= DIRECTIVE_CALL_ATTEMPTS; attempt++) {
|
|
264
|
+
try {
|
|
265
|
+
return await fn();
|
|
266
|
+
} catch (error) {
|
|
267
|
+
lastError = error;
|
|
268
|
+
if (attempt === DIRECTIVE_CALL_ATTEMPTS) {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
if (error instanceof TooManyRequestsError) {
|
|
272
|
+
if (error.retryAfter === void 0) {
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
await new Promise(
|
|
276
|
+
(resolve2) => setTimeout(resolve2, error.retryAfter * 1e3)
|
|
277
|
+
);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (!this.isNetworkError(error)) {
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
const baseDelayMs = DIRECTIVE_CALL_RETRY_DELAY_MS * attempt;
|
|
284
|
+
const delayMs = baseDelayMs / 2 + Math.random() * (baseDelayMs / 2);
|
|
285
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
throw lastError;
|
|
289
|
+
}
|
|
231
290
|
/**
|
|
232
291
|
* Starts a background loop that periodically extends the visibility timeout for a message.
|
|
233
292
|
*
|
|
@@ -372,11 +431,13 @@ var ConsumerGroup = class {
|
|
|
372
431
|
if (directive) {
|
|
373
432
|
if ("acknowledge" in directive && directive.acknowledge) {
|
|
374
433
|
try {
|
|
375
|
-
await this.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
434
|
+
await this.directiveCallWithRetries(
|
|
435
|
+
() => this.client.acknowledgeMessage({
|
|
436
|
+
queueName: this.topicName,
|
|
437
|
+
consumerGroup: this.consumerGroupName,
|
|
438
|
+
receiptHandle: message.receiptHandle
|
|
439
|
+
})
|
|
440
|
+
);
|
|
380
441
|
} catch (ackError) {
|
|
381
442
|
console.warn("Failed to acknowledge message:", ackError);
|
|
382
443
|
}
|
|
@@ -385,12 +446,14 @@ var ConsumerGroup = class {
|
|
|
385
446
|
}
|
|
386
447
|
if ("afterSeconds" in directive && typeof directive.afterSeconds === "number") {
|
|
387
448
|
try {
|
|
388
|
-
await this.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
449
|
+
await this.directiveCallWithRetries(
|
|
450
|
+
() => this.client.changeVisibility({
|
|
451
|
+
queueName: this.topicName,
|
|
452
|
+
consumerGroup: this.consumerGroupName,
|
|
453
|
+
receiptHandle: message.receiptHandle,
|
|
454
|
+
visibilityTimeoutSeconds: directive.afterSeconds
|
|
455
|
+
})
|
|
456
|
+
);
|
|
394
457
|
} catch (changeError) {
|
|
395
458
|
console.warn(
|
|
396
459
|
"Failed to reschedule message for retry:",
|
|
@@ -1452,10 +1515,28 @@ async function consumeStream(stream) {
|
|
|
1452
1515
|
reader.releaseLock();
|
|
1453
1516
|
}
|
|
1454
1517
|
}
|
|
1455
|
-
function
|
|
1518
|
+
function parseRetryAfterSeconds(value) {
|
|
1519
|
+
if (!value) return void 0;
|
|
1520
|
+
const seconds = Number(value);
|
|
1521
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
1522
|
+
return seconds;
|
|
1523
|
+
}
|
|
1524
|
+
const dateMs = Date.parse(value);
|
|
1525
|
+
if (!Number.isNaN(dateMs)) {
|
|
1526
|
+
return Math.max(0, (dateMs - Date.now()) / 1e3);
|
|
1527
|
+
}
|
|
1528
|
+
return void 0;
|
|
1529
|
+
}
|
|
1530
|
+
function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters", retryAfterHeader) {
|
|
1456
1531
|
if (status === 400) {
|
|
1457
1532
|
throw new BadRequestError(errorText || badRequestDefault);
|
|
1458
1533
|
}
|
|
1534
|
+
if (status === 429) {
|
|
1535
|
+
throw new TooManyRequestsError(
|
|
1536
|
+
errorText || `Too many requests: ${operation}`,
|
|
1537
|
+
parseRetryAfterSeconds(retryAfterHeader)
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1459
1540
|
if (status === 401) {
|
|
1460
1541
|
throw new UnauthorizedError(errorText || void 0);
|
|
1461
1542
|
}
|
|
@@ -1632,7 +1713,7 @@ Cause: ${cause}`
|
|
|
1632
1713
|
}
|
|
1633
1714
|
console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
|
|
1634
1715
|
}
|
|
1635
|
-
init.headers.set("User-Agent", `@vercel/queue/${"0.
|
|
1716
|
+
init.headers.set("User-Agent", `@vercel/queue/${"0.3.1"}`);
|
|
1636
1717
|
init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
|
|
1637
1718
|
const fetchInit = this.dispatcher ? { ...init, dispatcher: this.dispatcher } : init;
|
|
1638
1719
|
const response = await fetch(url, fetchInit);
|
|
@@ -1726,6 +1807,7 @@ Cause: ${cause}`
|
|
|
1726
1807
|
);
|
|
1727
1808
|
}
|
|
1728
1809
|
if (response.status === 202) {
|
|
1810
|
+
await response.text();
|
|
1729
1811
|
return { messageId: null };
|
|
1730
1812
|
}
|
|
1731
1813
|
const responseData = await response.json();
|
|
@@ -1763,6 +1845,7 @@ Cause: ${cause}`
|
|
|
1763
1845
|
}
|
|
1764
1846
|
);
|
|
1765
1847
|
if (response.status === 204) {
|
|
1848
|
+
await response.text();
|
|
1766
1849
|
return;
|
|
1767
1850
|
}
|
|
1768
1851
|
if (!response.ok) {
|
|
@@ -1909,9 +1992,11 @@ Cause: ${cause}`
|
|
|
1909
1992
|
response.statusText,
|
|
1910
1993
|
errorText,
|
|
1911
1994
|
"acknowledge message",
|
|
1912
|
-
"Missing or invalid receipt handle"
|
|
1995
|
+
"Missing or invalid receipt handle",
|
|
1996
|
+
response.headers?.get("Retry-After") ?? null
|
|
1913
1997
|
);
|
|
1914
1998
|
}
|
|
1999
|
+
await response.text();
|
|
1915
2000
|
return { acknowledged: true };
|
|
1916
2001
|
}
|
|
1917
2002
|
async changeVisibility(options) {
|
|
@@ -1960,9 +2045,11 @@ Cause: ${cause}`
|
|
|
1960
2045
|
response.statusText,
|
|
1961
2046
|
errorText,
|
|
1962
2047
|
"change visibility",
|
|
1963
|
-
"Missing receipt handle or invalid visibility timeout"
|
|
2048
|
+
"Missing receipt handle or invalid visibility timeout",
|
|
2049
|
+
response.headers?.get("Retry-After") ?? null
|
|
1964
2050
|
);
|
|
1965
2051
|
}
|
|
2052
|
+
await response.text();
|
|
1966
2053
|
return { success: true };
|
|
1967
2054
|
}
|
|
1968
2055
|
};
|
|
@@ -2317,6 +2404,7 @@ export {
|
|
|
2317
2404
|
QueueClient,
|
|
2318
2405
|
QueueEmptyError,
|
|
2319
2406
|
StreamTransport,
|
|
2407
|
+
TooManyRequestsError,
|
|
2320
2408
|
UnauthorizedError,
|
|
2321
2409
|
handleCallback2 as handleCallback,
|
|
2322
2410
|
parseCallback,
|