@vercel/queue 0.0.0-alpha.33 → 0.0.0-alpha.34
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/README.md +3 -52
- package/dist/index.d.mts +17 -95
- package/dist/index.d.ts +17 -95
- package/dist/index.js +551 -440
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +536 -440
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs-pages.d.mts +1 -1
- package/dist/nextjs-pages.d.ts +1 -1
- package/dist/nextjs-pages.js +570 -466
- package/dist/nextjs-pages.js.map +1 -1
- package/dist/nextjs-pages.mjs +560 -466
- package/dist/nextjs-pages.mjs.map +1 -1
- package/dist/{types-Dw29Fr9y.d.mts → types-BHtRP_i_.d.mts} +74 -43
- package/dist/{types-Dw29Fr9y.d.ts → types-BHtRP_i_.d.ts} +74 -43
- package/package.json +2 -6
- package/bin/local-discover.js +0 -196
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -23,10 +33,15 @@ __export(index_exports, {
|
|
|
23
33
|
BadRequestError: () => BadRequestError,
|
|
24
34
|
BufferTransport: () => BufferTransport,
|
|
25
35
|
Client: () => Client,
|
|
36
|
+
ConcurrencyLimitError: () => ConcurrencyLimitError,
|
|
37
|
+
ConsumerDiscoveryError: () => ConsumerDiscoveryError,
|
|
38
|
+
ConsumerRegistryNotConfiguredError: () => ConsumerRegistryNotConfiguredError,
|
|
39
|
+
DuplicateMessageError: () => DuplicateMessageError,
|
|
26
40
|
ForbiddenError: () => ForbiddenError,
|
|
27
41
|
InternalServerError: () => InternalServerError,
|
|
28
42
|
InvalidLimitError: () => InvalidLimitError,
|
|
29
43
|
JsonTransport: () => JsonTransport,
|
|
44
|
+
MessageAlreadyProcessedError: () => MessageAlreadyProcessedError,
|
|
30
45
|
MessageCorruptedError: () => MessageCorruptedError,
|
|
31
46
|
MessageLockedError: () => MessageLockedError,
|
|
32
47
|
MessageNotAvailableError: () => MessageNotAvailableError,
|
|
@@ -107,8 +122,9 @@ var StreamTransport = class {
|
|
|
107
122
|
// src/client.ts
|
|
108
123
|
var import_mixpart = require("mixpart");
|
|
109
124
|
|
|
110
|
-
// src/
|
|
111
|
-
var
|
|
125
|
+
// src/dev.ts
|
|
126
|
+
var fs = __toESM(require("fs"));
|
|
127
|
+
var path = __toESM(require("path"));
|
|
112
128
|
|
|
113
129
|
// src/types.ts
|
|
114
130
|
var MessageNotFoundError = class extends Error {
|
|
@@ -178,6 +194,263 @@ var InvalidLimitError = class extends Error {
|
|
|
178
194
|
this.name = "InvalidLimitError";
|
|
179
195
|
}
|
|
180
196
|
};
|
|
197
|
+
var MessageAlreadyProcessedError = class extends Error {
|
|
198
|
+
constructor(messageId) {
|
|
199
|
+
super(`Message ${messageId} has already been processed`);
|
|
200
|
+
this.name = "MessageAlreadyProcessedError";
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var ConcurrencyLimitError = class extends Error {
|
|
204
|
+
currentInflight;
|
|
205
|
+
maxConcurrency;
|
|
206
|
+
constructor(message = "Concurrency limit exceeded", currentInflight, maxConcurrency) {
|
|
207
|
+
super(message);
|
|
208
|
+
this.name = "ConcurrencyLimitError";
|
|
209
|
+
this.currentInflight = currentInflight;
|
|
210
|
+
this.maxConcurrency = maxConcurrency;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var DuplicateMessageError = class extends Error {
|
|
214
|
+
idempotencyKey;
|
|
215
|
+
constructor(message, idempotencyKey) {
|
|
216
|
+
super(message);
|
|
217
|
+
this.name = "DuplicateMessageError";
|
|
218
|
+
this.idempotencyKey = idempotencyKey;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var ConsumerDiscoveryError = class extends Error {
|
|
222
|
+
deploymentId;
|
|
223
|
+
constructor(message, deploymentId) {
|
|
224
|
+
super(message);
|
|
225
|
+
this.name = "ConsumerDiscoveryError";
|
|
226
|
+
this.deploymentId = deploymentId;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var ConsumerRegistryNotConfiguredError = class extends Error {
|
|
230
|
+
constructor(message = "Consumer registry not configured") {
|
|
231
|
+
super(message);
|
|
232
|
+
this.name = "ConsumerRegistryNotConfiguredError";
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/dev.ts
|
|
237
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
238
|
+
function filePathToUrlPath(filePath) {
|
|
239
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|js|tsx|jsx)$/, "").replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
240
|
+
if (!urlPath.startsWith("/")) {
|
|
241
|
+
urlPath = "/" + urlPath;
|
|
242
|
+
}
|
|
243
|
+
return urlPath;
|
|
244
|
+
}
|
|
245
|
+
function getDevRouteMappings() {
|
|
246
|
+
const g = globalThis;
|
|
247
|
+
if (ROUTE_MAPPINGS_KEY in g) {
|
|
248
|
+
return g[ROUTE_MAPPINGS_KEY] ?? null;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const vercelJsonPath = path.join(process.cwd(), "vercel.json");
|
|
252
|
+
if (!fs.existsSync(vercelJsonPath)) {
|
|
253
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const vercelJson = JSON.parse(fs.readFileSync(vercelJsonPath, "utf-8"));
|
|
257
|
+
if (!vercelJson.functions) {
|
|
258
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const mappings = [];
|
|
262
|
+
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
263
|
+
if (!config.experimentalTriggers) continue;
|
|
264
|
+
for (const trigger of config.experimentalTriggers) {
|
|
265
|
+
if (trigger.type?.startsWith("queue/") && trigger.topic && trigger.consumer) {
|
|
266
|
+
mappings.push({
|
|
267
|
+
urlPath: filePathToUrlPath(filePath),
|
|
268
|
+
topic: trigger.topic,
|
|
269
|
+
consumer: trigger.consumer
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
275
|
+
return g[ROUTE_MAPPINGS_KEY];
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.warn("[Dev Mode] Failed to read vercel.json:", error);
|
|
278
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function findMatchingRoutes(topicName) {
|
|
283
|
+
const mappings = getDevRouteMappings();
|
|
284
|
+
if (!mappings) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
return mappings.filter((mapping) => {
|
|
288
|
+
if (mapping.topic.includes("*")) {
|
|
289
|
+
return matchesWildcardPattern(topicName, mapping.topic);
|
|
290
|
+
}
|
|
291
|
+
return mapping.topic === topicName;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function isDevMode() {
|
|
295
|
+
return process.env.NODE_ENV === "development";
|
|
296
|
+
}
|
|
297
|
+
var DEV_VISIBILITY_POLL_INTERVAL = 50;
|
|
298
|
+
var DEV_VISIBILITY_MAX_WAIT = 5e3;
|
|
299
|
+
var DEV_VISIBILITY_BACKOFF_MULTIPLIER = 2;
|
|
300
|
+
async function waitForMessageVisibility(topicName, consumerGroup, messageId) {
|
|
301
|
+
const client = new QueueClient();
|
|
302
|
+
const transport = new JsonTransport();
|
|
303
|
+
let elapsed = 0;
|
|
304
|
+
let interval = DEV_VISIBILITY_POLL_INTERVAL;
|
|
305
|
+
while (elapsed < DEV_VISIBILITY_MAX_WAIT) {
|
|
306
|
+
try {
|
|
307
|
+
await client.receiveMessageById(
|
|
308
|
+
{
|
|
309
|
+
queueName: topicName,
|
|
310
|
+
consumerGroup,
|
|
311
|
+
messageId,
|
|
312
|
+
visibilityTimeoutSeconds: 0
|
|
313
|
+
},
|
|
314
|
+
transport
|
|
315
|
+
);
|
|
316
|
+
return true;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (error instanceof MessageNotFoundError) {
|
|
319
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
320
|
+
elapsed += interval;
|
|
321
|
+
interval = Math.min(
|
|
322
|
+
interval * DEV_VISIBILITY_BACKOFF_MULTIPLIER,
|
|
323
|
+
DEV_VISIBILITY_MAX_WAIT - elapsed
|
|
324
|
+
);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (error instanceof MessageAlreadyProcessedError) {
|
|
328
|
+
console.log(
|
|
329
|
+
`[Dev Mode] Message already processed: topic="${topicName}" messageId="${messageId}"`
|
|
330
|
+
);
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
console.error(
|
|
334
|
+
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
335
|
+
error
|
|
336
|
+
);
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
console.warn(
|
|
341
|
+
`[Dev Mode] Message visibility timeout after ${DEV_VISIBILITY_MAX_WAIT}ms: topic="${topicName}" messageId="${messageId}"`
|
|
342
|
+
);
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
function triggerDevCallbacks(topicName, messageId, delaySeconds) {
|
|
346
|
+
if (delaySeconds && delaySeconds > 0) {
|
|
347
|
+
console.log(
|
|
348
|
+
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
349
|
+
);
|
|
350
|
+
setTimeout(() => {
|
|
351
|
+
triggerDevCallbacks(topicName, messageId);
|
|
352
|
+
}, delaySeconds * 1e3);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
console.log(
|
|
356
|
+
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
357
|
+
);
|
|
358
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
359
|
+
if (matchingRoutes.length === 0) {
|
|
360
|
+
console.log(
|
|
361
|
+
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
362
|
+
);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
366
|
+
console.log(
|
|
367
|
+
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
368
|
+
);
|
|
369
|
+
(async () => {
|
|
370
|
+
const firstRoute = matchingRoutes[0];
|
|
371
|
+
const isVisible = await waitForMessageVisibility(
|
|
372
|
+
topicName,
|
|
373
|
+
firstRoute.consumer,
|
|
374
|
+
messageId
|
|
375
|
+
);
|
|
376
|
+
if (!isVisible) {
|
|
377
|
+
console.warn(
|
|
378
|
+
`[Dev Mode] Skipping callbacks - message not visible: topic="${topicName}" messageId="${messageId}"`
|
|
379
|
+
);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const port = process.env.PORT || 3e3;
|
|
383
|
+
const baseUrl = `http://localhost:${port}`;
|
|
384
|
+
for (const route of matchingRoutes) {
|
|
385
|
+
const url = `${baseUrl}${route.urlPath}`;
|
|
386
|
+
console.log(
|
|
387
|
+
`[Dev Mode] Invoking handler: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`
|
|
388
|
+
);
|
|
389
|
+
const cloudEvent = {
|
|
390
|
+
type: "com.vercel.queue.v1beta",
|
|
391
|
+
source: `/topic/${topicName}/consumer/${route.consumer}`,
|
|
392
|
+
id: messageId,
|
|
393
|
+
datacontenttype: "application/json",
|
|
394
|
+
data: {
|
|
395
|
+
messageId,
|
|
396
|
+
queueName: topicName,
|
|
397
|
+
consumerGroup: route.consumer
|
|
398
|
+
},
|
|
399
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
400
|
+
specversion: "1.0"
|
|
401
|
+
};
|
|
402
|
+
try {
|
|
403
|
+
const response = await fetch(url, {
|
|
404
|
+
method: "POST",
|
|
405
|
+
headers: {
|
|
406
|
+
"Content-Type": "application/cloudevents+json"
|
|
407
|
+
},
|
|
408
|
+
body: JSON.stringify(cloudEvent)
|
|
409
|
+
});
|
|
410
|
+
if (response.ok) {
|
|
411
|
+
try {
|
|
412
|
+
const responseData = await response.json();
|
|
413
|
+
if (responseData.status === "success") {
|
|
414
|
+
console.log(
|
|
415
|
+
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
} catch {
|
|
419
|
+
console.warn(
|
|
420
|
+
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
try {
|
|
425
|
+
const errorData = await response.json();
|
|
426
|
+
console.error(
|
|
427
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
428
|
+
);
|
|
429
|
+
} catch {
|
|
430
|
+
console.error(
|
|
431
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} catch (error) {
|
|
436
|
+
console.error(
|
|
437
|
+
`[Dev Mode] \u2717 HTTP request failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`,
|
|
438
|
+
error
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
})();
|
|
443
|
+
}
|
|
444
|
+
function clearDevRouteMappings() {
|
|
445
|
+
const g = globalThis;
|
|
446
|
+
delete g[ROUTE_MAPPINGS_KEY];
|
|
447
|
+
}
|
|
448
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
449
|
+
globalThis.__clearDevRouteMappings = clearDevRouteMappings;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/oidc.ts
|
|
453
|
+
var import_oidc = require("@vercel/oidc");
|
|
181
454
|
|
|
182
455
|
// src/client.ts
|
|
183
456
|
function isDebugEnabled() {
|
|
@@ -194,14 +467,6 @@ async function consumeStream(stream) {
|
|
|
194
467
|
reader.releaseLock();
|
|
195
468
|
}
|
|
196
469
|
}
|
|
197
|
-
function parseRetryAfter(headers) {
|
|
198
|
-
const retryAfterHeader = headers.get("Retry-After");
|
|
199
|
-
if (retryAfterHeader) {
|
|
200
|
-
const parsed = parseInt(retryAfterHeader, 10);
|
|
201
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
202
|
-
}
|
|
203
|
-
return void 0;
|
|
204
|
-
}
|
|
205
470
|
function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters") {
|
|
206
471
|
if (status === 400) {
|
|
207
472
|
throw new BadRequestError(errorText || badRequestDefault);
|
|
@@ -224,8 +489,8 @@ function parseQueueHeaders(headers) {
|
|
|
224
489
|
const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
|
|
225
490
|
const timestamp = headers.get("Vqs-Timestamp");
|
|
226
491
|
const contentType = headers.get("Content-Type") || "application/octet-stream";
|
|
227
|
-
const
|
|
228
|
-
if (!messageId || !timestamp || !
|
|
492
|
+
const receiptHandle = headers.get("Vqs-Receipt-Handle");
|
|
493
|
+
if (!messageId || !timestamp || !receiptHandle) {
|
|
229
494
|
return null;
|
|
230
495
|
}
|
|
231
496
|
const deliveryCount = parseInt(deliveryCountStr, 10);
|
|
@@ -237,7 +502,7 @@ function parseQueueHeaders(headers) {
|
|
|
237
502
|
deliveryCount,
|
|
238
503
|
createdAt: new Date(timestamp),
|
|
239
504
|
contentType,
|
|
240
|
-
|
|
505
|
+
receiptHandle
|
|
241
506
|
};
|
|
242
507
|
}
|
|
243
508
|
var QueueClient = class {
|
|
@@ -245,15 +510,30 @@ var QueueClient = class {
|
|
|
245
510
|
basePath;
|
|
246
511
|
customHeaders;
|
|
247
512
|
providedToken;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
* @param options QueueClient configuration options
|
|
251
|
-
*/
|
|
513
|
+
defaultDeploymentId;
|
|
514
|
+
pinToDeployment;
|
|
252
515
|
constructor(options = {}) {
|
|
253
516
|
this.baseUrl = options.baseUrl || process.env.VERCEL_QUEUE_BASE_URL || "https://vercel-queue.com";
|
|
254
|
-
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/
|
|
517
|
+
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v3/topic";
|
|
255
518
|
this.customHeaders = options.headers || {};
|
|
256
519
|
this.providedToken = options.token;
|
|
520
|
+
this.defaultDeploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
|
|
521
|
+
this.pinToDeployment = options.pinToDeployment ?? true;
|
|
522
|
+
}
|
|
523
|
+
getSendDeploymentId() {
|
|
524
|
+
if (isDevMode()) {
|
|
525
|
+
return void 0;
|
|
526
|
+
}
|
|
527
|
+
if (this.pinToDeployment) {
|
|
528
|
+
return this.defaultDeploymentId;
|
|
529
|
+
}
|
|
530
|
+
return void 0;
|
|
531
|
+
}
|
|
532
|
+
getConsumeDeploymentId() {
|
|
533
|
+
if (isDevMode()) {
|
|
534
|
+
return void 0;
|
|
535
|
+
}
|
|
536
|
+
return this.defaultDeploymentId;
|
|
257
537
|
}
|
|
258
538
|
async getToken() {
|
|
259
539
|
if (this.providedToken) {
|
|
@@ -267,10 +547,12 @@ var QueueClient = class {
|
|
|
267
547
|
}
|
|
268
548
|
return token;
|
|
269
549
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
550
|
+
buildUrl(queueName, ...pathSegments) {
|
|
551
|
+
const encodedQueue = encodeURIComponent(queueName);
|
|
552
|
+
const segments = pathSegments.map((s) => encodeURIComponent(s));
|
|
553
|
+
const path2 = segments.length > 0 ? "/" + segments.join("/") : "";
|
|
554
|
+
return `${this.baseUrl}${this.basePath}/${encodedQueue}${path2}`;
|
|
555
|
+
}
|
|
274
556
|
async fetch(url, init) {
|
|
275
557
|
const method = init.method || "GET";
|
|
276
558
|
if (isDebugEnabled()) {
|
|
@@ -306,25 +588,20 @@ var QueueClient = class {
|
|
|
306
588
|
}
|
|
307
589
|
return response;
|
|
308
590
|
}
|
|
309
|
-
/**
|
|
310
|
-
* Send a message to a queue
|
|
311
|
-
* @param options Send message options
|
|
312
|
-
* @param transport Serializer/deserializer for the payload
|
|
313
|
-
* @returns Promise with the message ID
|
|
314
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
315
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
316
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
317
|
-
* @throws {InternalServerError} When server encounters an error
|
|
318
|
-
*/
|
|
319
591
|
async sendMessage(options, transport) {
|
|
320
|
-
const {
|
|
592
|
+
const {
|
|
593
|
+
queueName,
|
|
594
|
+
payload,
|
|
595
|
+
idempotencyKey,
|
|
596
|
+
retentionSeconds,
|
|
597
|
+
delaySeconds
|
|
598
|
+
} = options;
|
|
321
599
|
const headers = new Headers({
|
|
322
600
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
323
|
-
"Vqs-Queue-Name": queueName,
|
|
324
601
|
"Content-Type": transport.contentType,
|
|
325
602
|
...this.customHeaders
|
|
326
603
|
});
|
|
327
|
-
const deploymentId =
|
|
604
|
+
const deploymentId = this.getSendDeploymentId();
|
|
328
605
|
if (deploymentId) {
|
|
329
606
|
headers.set("Vqs-Deployment-Id", deploymentId);
|
|
330
607
|
}
|
|
@@ -334,8 +611,12 @@ var QueueClient = class {
|
|
|
334
611
|
if (retentionSeconds !== void 0) {
|
|
335
612
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
336
613
|
}
|
|
337
|
-
|
|
338
|
-
|
|
614
|
+
if (delaySeconds !== void 0) {
|
|
615
|
+
headers.set("Vqs-Delay-Seconds", delaySeconds.toString());
|
|
616
|
+
}
|
|
617
|
+
const serialized = transport.serialize(payload);
|
|
618
|
+
const body = Buffer.isBuffer(serialized) ? new Uint8Array(serialized) : serialized;
|
|
619
|
+
const response = await this.fetch(this.buildUrl(queueName), {
|
|
339
620
|
method: "POST",
|
|
340
621
|
body,
|
|
341
622
|
headers
|
|
@@ -343,7 +624,21 @@ var QueueClient = class {
|
|
|
343
624
|
if (!response.ok) {
|
|
344
625
|
const errorText = await response.text();
|
|
345
626
|
if (response.status === 409) {
|
|
346
|
-
throw new
|
|
627
|
+
throw new DuplicateMessageError(
|
|
628
|
+
errorText || "Duplicate idempotency key detected",
|
|
629
|
+
idempotencyKey
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
if (response.status === 502) {
|
|
633
|
+
throw new ConsumerDiscoveryError(
|
|
634
|
+
errorText || "Consumer discovery failed",
|
|
635
|
+
deploymentId
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
if (response.status === 503) {
|
|
639
|
+
throw new ConsumerRegistryNotConfiguredError(
|
|
640
|
+
errorText || "Consumer registry not configured"
|
|
641
|
+
);
|
|
347
642
|
}
|
|
348
643
|
throwCommonHttpError(
|
|
349
644
|
response.status,
|
|
@@ -355,53 +650,60 @@ var QueueClient = class {
|
|
|
355
650
|
const responseData = await response.json();
|
|
356
651
|
return responseData;
|
|
357
652
|
}
|
|
358
|
-
/**
|
|
359
|
-
* Receive messages from a queue
|
|
360
|
-
* @param options Receive messages options
|
|
361
|
-
* @param transport Serializer/deserializer for the payload
|
|
362
|
-
* @returns AsyncGenerator that yields messages as they arrive
|
|
363
|
-
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
364
|
-
* @throws {QueueEmptyError} When no messages are available (204)
|
|
365
|
-
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
366
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
367
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
368
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
369
|
-
* @throws {InternalServerError} When server encounters an error
|
|
370
|
-
*/
|
|
371
653
|
async *receiveMessages(options, transport) {
|
|
372
|
-
const {
|
|
654
|
+
const {
|
|
655
|
+
queueName,
|
|
656
|
+
consumerGroup,
|
|
657
|
+
visibilityTimeoutSeconds,
|
|
658
|
+
limit,
|
|
659
|
+
maxConcurrency
|
|
660
|
+
} = options;
|
|
373
661
|
if (limit !== void 0 && (limit < 1 || limit > 10)) {
|
|
374
662
|
throw new InvalidLimitError(limit);
|
|
375
663
|
}
|
|
376
664
|
const headers = new Headers({
|
|
377
665
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
378
|
-
"Vqs-Queue-Name": queueName,
|
|
379
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
380
666
|
Accept: "multipart/mixed",
|
|
381
667
|
...this.customHeaders
|
|
382
668
|
});
|
|
383
669
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
384
670
|
headers.set(
|
|
385
|
-
"Vqs-Visibility-Timeout",
|
|
671
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
386
672
|
visibilityTimeoutSeconds.toString()
|
|
387
673
|
);
|
|
388
674
|
}
|
|
389
675
|
if (limit !== void 0) {
|
|
390
|
-
headers.set("Vqs-
|
|
676
|
+
headers.set("Vqs-Max-Messages", limit.toString());
|
|
391
677
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
678
|
+
if (maxConcurrency !== void 0) {
|
|
679
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
680
|
+
}
|
|
681
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
682
|
+
if (effectiveDeploymentId) {
|
|
683
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
684
|
+
}
|
|
685
|
+
const response = await this.fetch(
|
|
686
|
+
this.buildUrl(queueName, "consumer", consumerGroup),
|
|
687
|
+
{
|
|
688
|
+
method: "POST",
|
|
689
|
+
headers
|
|
690
|
+
}
|
|
691
|
+
);
|
|
396
692
|
if (response.status === 204) {
|
|
397
693
|
throw new QueueEmptyError(queueName, consumerGroup);
|
|
398
694
|
}
|
|
399
695
|
if (!response.ok) {
|
|
400
696
|
const errorText = await response.text();
|
|
401
|
-
if (response.status ===
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
697
|
+
if (response.status === 429) {
|
|
698
|
+
let errorData = {};
|
|
699
|
+
try {
|
|
700
|
+
errorData = JSON.parse(errorText);
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
throw new ConcurrencyLimitError(
|
|
704
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
705
|
+
errorData.currentInflight,
|
|
706
|
+
errorData.maxConcurrency
|
|
405
707
|
);
|
|
406
708
|
}
|
|
407
709
|
throwCommonHttpError(
|
|
@@ -439,28 +741,30 @@ var QueueClient = class {
|
|
|
439
741
|
consumerGroup,
|
|
440
742
|
messageId,
|
|
441
743
|
visibilityTimeoutSeconds,
|
|
442
|
-
|
|
744
|
+
maxConcurrency
|
|
443
745
|
} = options;
|
|
444
746
|
const headers = new Headers({
|
|
445
747
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
446
|
-
"Vqs-Queue-Name": queueName,
|
|
447
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
448
748
|
Accept: "multipart/mixed",
|
|
449
749
|
...this.customHeaders
|
|
450
750
|
});
|
|
451
751
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
452
752
|
headers.set(
|
|
453
|
-
"Vqs-Visibility-Timeout",
|
|
753
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
454
754
|
visibilityTimeoutSeconds.toString()
|
|
455
755
|
);
|
|
456
756
|
}
|
|
457
|
-
if (
|
|
458
|
-
headers.set("Vqs-
|
|
757
|
+
if (maxConcurrency !== void 0) {
|
|
758
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
759
|
+
}
|
|
760
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
761
|
+
if (effectiveDeploymentId) {
|
|
762
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
459
763
|
}
|
|
460
764
|
const response = await this.fetch(
|
|
461
|
-
|
|
765
|
+
this.buildUrl(queueName, "consumer", consumerGroup, "id", messageId),
|
|
462
766
|
{
|
|
463
|
-
method: "
|
|
767
|
+
method: "POST",
|
|
464
768
|
headers
|
|
465
769
|
}
|
|
466
770
|
);
|
|
@@ -470,12 +774,32 @@ var QueueClient = class {
|
|
|
470
774
|
throw new MessageNotFoundError(messageId);
|
|
471
775
|
}
|
|
472
776
|
if (response.status === 409) {
|
|
777
|
+
let errorData = {};
|
|
778
|
+
try {
|
|
779
|
+
errorData = JSON.parse(errorText);
|
|
780
|
+
} catch {
|
|
781
|
+
}
|
|
782
|
+
if (errorData.originalMessageId) {
|
|
783
|
+
throw new MessageNotAvailableError(
|
|
784
|
+
messageId,
|
|
785
|
+
`This message was a duplicate - use originalMessageId: ${errorData.originalMessageId}`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
473
788
|
throw new MessageNotAvailableError(messageId);
|
|
474
789
|
}
|
|
475
|
-
if (response.status ===
|
|
476
|
-
throw new
|
|
477
|
-
|
|
478
|
-
|
|
790
|
+
if (response.status === 410) {
|
|
791
|
+
throw new MessageAlreadyProcessedError(messageId);
|
|
792
|
+
}
|
|
793
|
+
if (response.status === 429) {
|
|
794
|
+
let errorData = {};
|
|
795
|
+
try {
|
|
796
|
+
errorData = JSON.parse(errorText);
|
|
797
|
+
} catch {
|
|
798
|
+
}
|
|
799
|
+
throw new ConcurrencyLimitError(
|
|
800
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
801
|
+
errorData.currentInflight,
|
|
802
|
+
errorData.maxConcurrency
|
|
479
803
|
);
|
|
480
804
|
}
|
|
481
805
|
throwCommonHttpError(
|
|
@@ -485,95 +809,58 @@ var QueueClient = class {
|
|
|
485
809
|
"receive message by ID"
|
|
486
810
|
);
|
|
487
811
|
}
|
|
488
|
-
|
|
489
|
-
const parsedHeaders = parseQueueHeaders(
|
|
812
|
+
for await (const multipartMessage of (0, import_mixpart.parseMultipartStream)(response)) {
|
|
813
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
490
814
|
if (!parsedHeaders) {
|
|
815
|
+
await consumeStream(multipartMessage.payload);
|
|
491
816
|
throw new MessageCorruptedError(
|
|
492
817
|
messageId,
|
|
493
|
-
"Missing required queue headers in
|
|
818
|
+
"Missing required queue headers in response"
|
|
494
819
|
);
|
|
495
820
|
}
|
|
821
|
+
const deserializedPayload = await transport.deserialize(
|
|
822
|
+
multipartMessage.payload
|
|
823
|
+
);
|
|
496
824
|
const message = {
|
|
497
825
|
...parsedHeaders,
|
|
498
|
-
payload:
|
|
826
|
+
payload: deserializedPayload
|
|
499
827
|
};
|
|
500
828
|
return { message };
|
|
501
829
|
}
|
|
502
|
-
if (!transport) {
|
|
503
|
-
throw new Error("Transport is required when skipPayload is not true");
|
|
504
|
-
}
|
|
505
|
-
try {
|
|
506
|
-
for await (const multipartMessage of (0, import_mixpart.parseMultipartStream)(response)) {
|
|
507
|
-
try {
|
|
508
|
-
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
509
|
-
if (!parsedHeaders) {
|
|
510
|
-
console.warn("Missing required queue headers in multipart part");
|
|
511
|
-
await consumeStream(multipartMessage.payload);
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
const deserializedPayload = await transport.deserialize(
|
|
515
|
-
multipartMessage.payload
|
|
516
|
-
);
|
|
517
|
-
const message = {
|
|
518
|
-
...parsedHeaders,
|
|
519
|
-
payload: deserializedPayload
|
|
520
|
-
};
|
|
521
|
-
return { message };
|
|
522
|
-
} catch (error) {
|
|
523
|
-
console.warn("Failed to deserialize message by ID:", error);
|
|
524
|
-
await consumeStream(multipartMessage.payload);
|
|
525
|
-
throw new MessageCorruptedError(
|
|
526
|
-
messageId,
|
|
527
|
-
`Failed to deserialize payload: ${error}`
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
} catch (error) {
|
|
532
|
-
if (error instanceof MessageCorruptedError) {
|
|
533
|
-
throw error;
|
|
534
|
-
}
|
|
535
|
-
throw new MessageCorruptedError(
|
|
536
|
-
messageId,
|
|
537
|
-
`Failed to parse multipart response: ${error}`
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
830
|
throw new MessageNotFoundError(messageId);
|
|
541
831
|
}
|
|
542
|
-
/**
|
|
543
|
-
* Delete a message (acknowledge processing)
|
|
544
|
-
* @param options Delete message options
|
|
545
|
-
* @returns Promise with delete status
|
|
546
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
547
|
-
* @throws {MessageNotAvailableError} When message can't be deleted (409)
|
|
548
|
-
* @throws {BadRequestError} When ticket is missing or invalid (400)
|
|
549
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
550
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
551
|
-
* @throws {InternalServerError} When server encounters an error
|
|
552
|
-
*/
|
|
553
832
|
async deleteMessage(options) {
|
|
554
|
-
const { queueName, consumerGroup,
|
|
833
|
+
const { queueName, consumerGroup, receiptHandle } = options;
|
|
834
|
+
const headers = new Headers({
|
|
835
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
836
|
+
...this.customHeaders
|
|
837
|
+
});
|
|
838
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
839
|
+
if (effectiveDeploymentId) {
|
|
840
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
841
|
+
}
|
|
555
842
|
const response = await this.fetch(
|
|
556
|
-
|
|
843
|
+
this.buildUrl(
|
|
844
|
+
queueName,
|
|
845
|
+
"consumer",
|
|
846
|
+
consumerGroup,
|
|
847
|
+
"lease",
|
|
848
|
+
receiptHandle
|
|
849
|
+
),
|
|
557
850
|
{
|
|
558
851
|
method: "DELETE",
|
|
559
|
-
headers
|
|
560
|
-
Authorization: `Bearer ${await this.getToken()}`,
|
|
561
|
-
"Vqs-Queue-Name": queueName,
|
|
562
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
563
|
-
"Vqs-Ticket": ticket,
|
|
564
|
-
...this.customHeaders
|
|
565
|
-
})
|
|
852
|
+
headers
|
|
566
853
|
}
|
|
567
854
|
);
|
|
568
855
|
if (!response.ok) {
|
|
569
856
|
const errorText = await response.text();
|
|
570
857
|
if (response.status === 404) {
|
|
571
|
-
throw new MessageNotFoundError(
|
|
858
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
572
859
|
}
|
|
573
860
|
if (response.status === 409) {
|
|
574
861
|
throw new MessageNotAvailableError(
|
|
575
|
-
|
|
576
|
-
errorText || "Invalid
|
|
862
|
+
receiptHandle,
|
|
863
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
577
864
|
);
|
|
578
865
|
}
|
|
579
866
|
throwCommonHttpError(
|
|
@@ -581,53 +868,50 @@ var QueueClient = class {
|
|
|
581
868
|
response.statusText,
|
|
582
869
|
errorText,
|
|
583
870
|
"delete message",
|
|
584
|
-
"Missing or invalid
|
|
871
|
+
"Missing or invalid receipt handle"
|
|
585
872
|
);
|
|
586
873
|
}
|
|
587
874
|
return { deleted: true };
|
|
588
875
|
}
|
|
589
|
-
/**
|
|
590
|
-
* Change the visibility timeout of a message
|
|
591
|
-
* @param options Change visibility options
|
|
592
|
-
* @returns Promise with update status
|
|
593
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
594
|
-
* @throws {MessageNotAvailableError} When message can't be updated (409)
|
|
595
|
-
* @throws {BadRequestError} When ticket is missing or visibility timeout invalid (400)
|
|
596
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
597
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
598
|
-
* @throws {InternalServerError} When server encounters an error
|
|
599
|
-
*/
|
|
600
876
|
async changeVisibility(options) {
|
|
601
877
|
const {
|
|
602
878
|
queueName,
|
|
603
879
|
consumerGroup,
|
|
604
|
-
|
|
605
|
-
ticket,
|
|
880
|
+
receiptHandle,
|
|
606
881
|
visibilityTimeoutSeconds
|
|
607
882
|
} = options;
|
|
883
|
+
const headers = new Headers({
|
|
884
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
885
|
+
"Content-Type": "application/json",
|
|
886
|
+
...this.customHeaders
|
|
887
|
+
});
|
|
888
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
889
|
+
if (effectiveDeploymentId) {
|
|
890
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
891
|
+
}
|
|
608
892
|
const response = await this.fetch(
|
|
609
|
-
|
|
893
|
+
this.buildUrl(
|
|
894
|
+
queueName,
|
|
895
|
+
"consumer",
|
|
896
|
+
consumerGroup,
|
|
897
|
+
"lease",
|
|
898
|
+
receiptHandle
|
|
899
|
+
),
|
|
610
900
|
{
|
|
611
901
|
method: "PATCH",
|
|
612
|
-
headers
|
|
613
|
-
|
|
614
|
-
"Vqs-Queue-Name": queueName,
|
|
615
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
616
|
-
"Vqs-Ticket": ticket,
|
|
617
|
-
"Vqs-Visibility-Timeout": visibilityTimeoutSeconds.toString(),
|
|
618
|
-
...this.customHeaders
|
|
619
|
-
})
|
|
902
|
+
headers,
|
|
903
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
620
904
|
}
|
|
621
905
|
);
|
|
622
906
|
if (!response.ok) {
|
|
623
907
|
const errorText = await response.text();
|
|
624
908
|
if (response.status === 404) {
|
|
625
|
-
throw new MessageNotFoundError(
|
|
909
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
626
910
|
}
|
|
627
911
|
if (response.status === 409) {
|
|
628
912
|
throw new MessageNotAvailableError(
|
|
629
|
-
|
|
630
|
-
errorText || "Invalid
|
|
913
|
+
receiptHandle,
|
|
914
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
631
915
|
);
|
|
632
916
|
}
|
|
633
917
|
throwCommonHttpError(
|
|
@@ -635,194 +919,72 @@ var QueueClient = class {
|
|
|
635
919
|
response.statusText,
|
|
636
920
|
errorText,
|
|
637
921
|
"change visibility",
|
|
638
|
-
"Missing
|
|
922
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
639
923
|
);
|
|
640
924
|
}
|
|
641
|
-
return {
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
// src/dev.ts
|
|
646
|
-
var GLOBAL_KEY = Symbol.for("@vercel/queue.devHandlers");
|
|
647
|
-
function getDevHandlerState() {
|
|
648
|
-
const g = globalThis;
|
|
649
|
-
if (!g[GLOBAL_KEY]) {
|
|
650
|
-
g[GLOBAL_KEY] = {
|
|
651
|
-
devRouteHandlers: /* @__PURE__ */ new Map(),
|
|
652
|
-
wildcardRouteHandlers: /* @__PURE__ */ new Map()
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
return g[GLOBAL_KEY];
|
|
656
|
-
}
|
|
657
|
-
var { devRouteHandlers, wildcardRouteHandlers } = getDevHandlerState();
|
|
658
|
-
function cleanupDeadRefs(key, refs) {
|
|
659
|
-
const aliveRefs = refs.filter((ref) => ref.deref() !== void 0);
|
|
660
|
-
if (aliveRefs.length === 0) {
|
|
661
|
-
wildcardRouteHandlers.delete(key);
|
|
662
|
-
} else if (aliveRefs.length < refs.length) {
|
|
663
|
-
wildcardRouteHandlers.set(key, aliveRefs);
|
|
925
|
+
return { success: true };
|
|
664
926
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
927
|
+
/**
|
|
928
|
+
* Alternative endpoint for changing message visibility timeout.
|
|
929
|
+
* Uses the /visibility path suffix and expects visibilityTimeoutSeconds in the body.
|
|
930
|
+
* Functionally equivalent to changeVisibility but follows an alternative API pattern.
|
|
931
|
+
*
|
|
932
|
+
* @param options - Options for changing visibility
|
|
933
|
+
* @returns Promise resolving to change visibility response
|
|
934
|
+
*/
|
|
935
|
+
async changeVisibilityAlt(options) {
|
|
936
|
+
const {
|
|
937
|
+
queueName,
|
|
938
|
+
consumerGroup,
|
|
939
|
+
receiptHandle,
|
|
940
|
+
visibilityTimeoutSeconds
|
|
941
|
+
} = options;
|
|
942
|
+
const headers = new Headers({
|
|
943
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
944
|
+
"Content-Type": "application/json",
|
|
945
|
+
...this.customHeaders
|
|
946
|
+
});
|
|
947
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
948
|
+
if (effectiveDeploymentId) {
|
|
949
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
686
950
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
951
|
+
const response = await this.fetch(
|
|
952
|
+
this.buildUrl(
|
|
953
|
+
queueName,
|
|
954
|
+
"consumer",
|
|
955
|
+
consumerGroup,
|
|
956
|
+
"lease",
|
|
957
|
+
receiptHandle,
|
|
958
|
+
"visibility"
|
|
959
|
+
),
|
|
960
|
+
{
|
|
961
|
+
method: "PATCH",
|
|
962
|
+
headers,
|
|
963
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
699
964
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (matchesWildcardPattern(topicName, pattern)) {
|
|
706
|
-
cleanupDeadRefs(key, refs);
|
|
707
|
-
const cleanedRefs = wildcardRouteHandlers.get(key) || [];
|
|
708
|
-
for (const ref of cleanedRefs) {
|
|
709
|
-
const routeHandler = ref.deref();
|
|
710
|
-
if (routeHandler) {
|
|
711
|
-
if (!handlersMap.has(routeHandler)) {
|
|
712
|
-
handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
|
|
713
|
-
}
|
|
714
|
-
handlersMap.get(routeHandler).add(consumerGroup);
|
|
715
|
-
}
|
|
965
|
+
);
|
|
966
|
+
if (!response.ok) {
|
|
967
|
+
const errorText = await response.text();
|
|
968
|
+
if (response.status === 404) {
|
|
969
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
716
970
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const cloudEvent = {
|
|
723
|
-
type: "com.vercel.queue.v1beta",
|
|
724
|
-
source: `/topic/${topicName}/consumer/${consumerGroup}`,
|
|
725
|
-
id: messageId,
|
|
726
|
-
datacontenttype: "application/json",
|
|
727
|
-
data: {
|
|
728
|
-
messageId,
|
|
729
|
-
queueName: topicName,
|
|
730
|
-
consumerGroup
|
|
731
|
-
},
|
|
732
|
-
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
733
|
-
specversion: "1.0"
|
|
734
|
-
};
|
|
735
|
-
return new Request("https://localhost/api/queue/callback", {
|
|
736
|
-
method: "POST",
|
|
737
|
-
headers: {
|
|
738
|
-
"Content-Type": "application/cloudevents+json"
|
|
739
|
-
},
|
|
740
|
-
body: JSON.stringify(cloudEvent)
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
var DEV_CALLBACK_DELAY = 1e3;
|
|
744
|
-
function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
|
|
745
|
-
console.log(
|
|
746
|
-
`[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
|
|
747
|
-
);
|
|
748
|
-
setTimeout(
|
|
749
|
-
() => {
|
|
750
|
-
console.log(
|
|
751
|
-
`[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
|
|
752
|
-
);
|
|
753
|
-
triggerDevCallbacks(topicName, messageId);
|
|
754
|
-
},
|
|
755
|
-
timeoutSeconds * 1e3 + DEV_CALLBACK_DELAY
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
function triggerDevCallbacks(topicName, messageId) {
|
|
759
|
-
const handlersMap = findRouteHandlersForTopic(topicName);
|
|
760
|
-
if (handlersMap.size === 0) {
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
const consumerGroups = Array.from(
|
|
764
|
-
new Set(
|
|
765
|
-
Array.from(handlersMap.values()).flatMap((groups) => Array.from(groups))
|
|
766
|
-
)
|
|
767
|
-
);
|
|
768
|
-
console.log(
|
|
769
|
-
`[Dev Mode] Triggering local callbacks for topic "${topicName}" \u2192 consumers: ${consumerGroups.join(", ")}`
|
|
770
|
-
);
|
|
771
|
-
setTimeout(async () => {
|
|
772
|
-
for (const [routeHandler, consumerGroups2] of handlersMap.entries()) {
|
|
773
|
-
for (const consumerGroup of consumerGroups2) {
|
|
774
|
-
try {
|
|
775
|
-
const request = createMockCloudEventRequest(
|
|
776
|
-
topicName,
|
|
777
|
-
consumerGroup,
|
|
778
|
-
messageId
|
|
779
|
-
);
|
|
780
|
-
const response = await routeHandler(request);
|
|
781
|
-
if (response.ok) {
|
|
782
|
-
try {
|
|
783
|
-
const responseData = await response.json();
|
|
784
|
-
if (responseData.status === "success") {
|
|
785
|
-
console.log(
|
|
786
|
-
`[Dev Mode] Message processed for ${topicName}/${consumerGroup}`
|
|
787
|
-
);
|
|
788
|
-
}
|
|
789
|
-
} catch (jsonError) {
|
|
790
|
-
console.error(
|
|
791
|
-
`[Dev Mode] Failed to parse success response for ${topicName}/${consumerGroup}:`,
|
|
792
|
-
jsonError
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
} else {
|
|
796
|
-
try {
|
|
797
|
-
const errorData = await response.json();
|
|
798
|
-
console.error(
|
|
799
|
-
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
800
|
-
errorData.error || response.statusText
|
|
801
|
-
);
|
|
802
|
-
} catch (jsonError) {
|
|
803
|
-
console.error(
|
|
804
|
-
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
805
|
-
response.statusText
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
} catch (error) {
|
|
810
|
-
console.error(
|
|
811
|
-
`[Dev Mode] Error triggering callback for ${topicName}/${consumerGroup}:`,
|
|
812
|
-
error
|
|
813
|
-
);
|
|
814
|
-
}
|
|
971
|
+
if (response.status === 409) {
|
|
972
|
+
throw new MessageNotAvailableError(
|
|
973
|
+
receiptHandle,
|
|
974
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
975
|
+
);
|
|
815
976
|
}
|
|
977
|
+
throwCommonHttpError(
|
|
978
|
+
response.status,
|
|
979
|
+
response.statusText,
|
|
980
|
+
errorText,
|
|
981
|
+
"change visibility (alt)",
|
|
982
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
983
|
+
);
|
|
816
984
|
}
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
devRouteHandlers.clear();
|
|
821
|
-
wildcardRouteHandlers.clear();
|
|
822
|
-
}
|
|
823
|
-
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
824
|
-
globalThis.__clearDevHandlers = clearDevHandlers;
|
|
825
|
-
}
|
|
985
|
+
return { success: true };
|
|
986
|
+
}
|
|
987
|
+
};
|
|
826
988
|
|
|
827
989
|
// src/consumer-group.ts
|
|
828
990
|
var ConsumerGroup = class {
|
|
@@ -854,8 +1016,7 @@ var ConsumerGroup = class {
|
|
|
854
1016
|
* The extension loop runs every `refreshInterval` seconds and updates the message's
|
|
855
1017
|
* visibility timeout to `visibilityTimeout` seconds from the current time.
|
|
856
1018
|
*
|
|
857
|
-
* @param
|
|
858
|
-
* @param ticket - The receipt ticket that proves ownership of the message
|
|
1019
|
+
* @param receiptHandle - The receipt handle that proves ownership of the message
|
|
859
1020
|
* @returns A function that when called will stop the extension loop
|
|
860
1021
|
*
|
|
861
1022
|
* @remarks
|
|
@@ -865,37 +1026,43 @@ var ConsumerGroup = class {
|
|
|
865
1026
|
* - By default, the stop function returns immediately without waiting for in-flight
|
|
866
1027
|
* - Pass `true` to the stop function to wait for any in-flight extension to complete
|
|
867
1028
|
*/
|
|
868
|
-
startVisibilityExtension(
|
|
1029
|
+
startVisibilityExtension(receiptHandle) {
|
|
869
1030
|
let isRunning = true;
|
|
1031
|
+
let isResolved = false;
|
|
870
1032
|
let resolveLifecycle;
|
|
871
1033
|
let timeoutId = null;
|
|
872
1034
|
const lifecyclePromise = new Promise((resolve) => {
|
|
873
1035
|
resolveLifecycle = resolve;
|
|
874
1036
|
});
|
|
1037
|
+
const safeResolve = () => {
|
|
1038
|
+
if (!isResolved) {
|
|
1039
|
+
isResolved = true;
|
|
1040
|
+
resolveLifecycle();
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
875
1043
|
const extend = async () => {
|
|
876
1044
|
if (!isRunning) {
|
|
877
|
-
|
|
1045
|
+
safeResolve();
|
|
878
1046
|
return;
|
|
879
1047
|
}
|
|
880
1048
|
try {
|
|
881
1049
|
await this.client.changeVisibility({
|
|
882
1050
|
queueName: this.topicName,
|
|
883
1051
|
consumerGroup: this.consumerGroupName,
|
|
884
|
-
|
|
885
|
-
ticket,
|
|
1052
|
+
receiptHandle,
|
|
886
1053
|
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
887
1054
|
});
|
|
888
1055
|
if (isRunning) {
|
|
889
1056
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
890
1057
|
} else {
|
|
891
|
-
|
|
1058
|
+
safeResolve();
|
|
892
1059
|
}
|
|
893
1060
|
} catch (error) {
|
|
894
1061
|
console.error(
|
|
895
|
-
`Failed to extend visibility for
|
|
1062
|
+
`Failed to extend visibility for receipt handle ${receiptHandle}:`,
|
|
896
1063
|
error
|
|
897
1064
|
);
|
|
898
|
-
|
|
1065
|
+
safeResolve();
|
|
899
1066
|
}
|
|
900
1067
|
};
|
|
901
1068
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
@@ -908,22 +1075,14 @@ var ConsumerGroup = class {
|
|
|
908
1075
|
if (waitForCompletion) {
|
|
909
1076
|
await lifecyclePromise;
|
|
910
1077
|
} else {
|
|
911
|
-
|
|
1078
|
+
safeResolve();
|
|
912
1079
|
}
|
|
913
1080
|
};
|
|
914
1081
|
}
|
|
915
|
-
/**
|
|
916
|
-
* Process a single message with the given handler
|
|
917
|
-
* @param message The message to process
|
|
918
|
-
* @param handler Function to process the message
|
|
919
|
-
*/
|
|
920
1082
|
async processMessage(message, handler) {
|
|
921
|
-
const stopExtension = this.startVisibilityExtension(
|
|
922
|
-
message.messageId,
|
|
923
|
-
message.ticket
|
|
924
|
-
);
|
|
1083
|
+
const stopExtension = this.startVisibilityExtension(message.receiptHandle);
|
|
925
1084
|
try {
|
|
926
|
-
|
|
1085
|
+
await handler(message.payload, {
|
|
927
1086
|
messageId: message.messageId,
|
|
928
1087
|
deliveryCount: message.deliveryCount,
|
|
929
1088
|
createdAt: message.createdAt,
|
|
@@ -931,29 +1090,11 @@ var ConsumerGroup = class {
|
|
|
931
1090
|
consumerGroup: this.consumerGroupName
|
|
932
1091
|
});
|
|
933
1092
|
await stopExtension();
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
ticket: message.ticket,
|
|
940
|
-
visibilityTimeoutSeconds: result.timeoutSeconds
|
|
941
|
-
});
|
|
942
|
-
if (isDevMode()) {
|
|
943
|
-
scheduleDevTimeout(
|
|
944
|
-
this.topicName,
|
|
945
|
-
message.messageId,
|
|
946
|
-
result.timeoutSeconds
|
|
947
|
-
);
|
|
948
|
-
}
|
|
949
|
-
} else {
|
|
950
|
-
await this.client.deleteMessage({
|
|
951
|
-
queueName: this.topicName,
|
|
952
|
-
consumerGroup: this.consumerGroupName,
|
|
953
|
-
messageId: message.messageId,
|
|
954
|
-
ticket: message.ticket
|
|
955
|
-
});
|
|
956
|
-
}
|
|
1093
|
+
await this.client.deleteMessage({
|
|
1094
|
+
queueName: this.topicName,
|
|
1095
|
+
consumerGroup: this.consumerGroupName,
|
|
1096
|
+
receiptHandle: message.receiptHandle
|
|
1097
|
+
});
|
|
957
1098
|
} catch (error) {
|
|
958
1099
|
await stopExtension();
|
|
959
1100
|
if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
|
|
@@ -968,36 +1109,16 @@ var ConsumerGroup = class {
|
|
|
968
1109
|
}
|
|
969
1110
|
async consume(handler, options) {
|
|
970
1111
|
if (options?.messageId) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
);
|
|
982
|
-
await this.processMessage(
|
|
983
|
-
response.message,
|
|
984
|
-
handler
|
|
985
|
-
);
|
|
986
|
-
} else {
|
|
987
|
-
const response = await this.client.receiveMessageById(
|
|
988
|
-
{
|
|
989
|
-
queueName: this.topicName,
|
|
990
|
-
consumerGroup: this.consumerGroupName,
|
|
991
|
-
messageId: options.messageId,
|
|
992
|
-
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
993
|
-
},
|
|
994
|
-
this.transport
|
|
995
|
-
);
|
|
996
|
-
await this.processMessage(
|
|
997
|
-
response.message,
|
|
998
|
-
handler
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1112
|
+
const response = await this.client.receiveMessageById(
|
|
1113
|
+
{
|
|
1114
|
+
queueName: this.topicName,
|
|
1115
|
+
consumerGroup: this.consumerGroupName,
|
|
1116
|
+
messageId: options.messageId,
|
|
1117
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1118
|
+
},
|
|
1119
|
+
this.transport
|
|
1120
|
+
);
|
|
1121
|
+
await this.processMessage(response.message, handler);
|
|
1001
1122
|
} else {
|
|
1002
1123
|
let messageFound = false;
|
|
1003
1124
|
for await (const message of this.client.receiveMessages(
|
|
@@ -1065,7 +1186,7 @@ var Topic = class {
|
|
|
1065
1186
|
payload,
|
|
1066
1187
|
idempotencyKey: options?.idempotencyKey,
|
|
1067
1188
|
retentionSeconds: options?.retentionSeconds,
|
|
1068
|
-
|
|
1189
|
+
delaySeconds: options?.delaySeconds
|
|
1069
1190
|
},
|
|
1070
1191
|
this.transport
|
|
1071
1192
|
);
|
|
@@ -1225,9 +1346,6 @@ function createCallbackHandler(handlers, client) {
|
|
|
1225
1346
|
);
|
|
1226
1347
|
}
|
|
1227
1348
|
};
|
|
1228
|
-
if (isDevMode()) {
|
|
1229
|
-
registerDevRouteHandler(routeHandler, handlers);
|
|
1230
|
-
}
|
|
1231
1349
|
return routeHandler;
|
|
1232
1350
|
}
|
|
1233
1351
|
function handleCallback(handlers, client) {
|
|
@@ -1244,12 +1362,12 @@ async function send(topicName, payload, options) {
|
|
|
1244
1362
|
payload,
|
|
1245
1363
|
idempotencyKey: options?.idempotencyKey,
|
|
1246
1364
|
retentionSeconds: options?.retentionSeconds,
|
|
1247
|
-
|
|
1365
|
+
delaySeconds: options?.delaySeconds
|
|
1248
1366
|
},
|
|
1249
1367
|
transport
|
|
1250
1368
|
);
|
|
1251
1369
|
if (isDevMode()) {
|
|
1252
|
-
triggerDevCallbacks(topicName, result.messageId);
|
|
1370
|
+
triggerDevCallbacks(topicName, result.messageId, options?.delaySeconds);
|
|
1253
1371
|
}
|
|
1254
1372
|
return { messageId: result.messageId };
|
|
1255
1373
|
}
|
|
@@ -1257,22 +1375,10 @@ async function receive(topicName, consumerGroup, handler, options) {
|
|
|
1257
1375
|
const transport = options?.transport || new JsonTransport();
|
|
1258
1376
|
const client = options?.client || new QueueClient();
|
|
1259
1377
|
const topic = new Topic(client, topicName, transport);
|
|
1260
|
-
const {
|
|
1261
|
-
messageId,
|
|
1262
|
-
skipPayload,
|
|
1263
|
-
client: _,
|
|
1264
|
-
...consumerGroupOptions
|
|
1265
|
-
} = options || {};
|
|
1378
|
+
const { messageId, client: _, ...consumerGroupOptions } = options || {};
|
|
1266
1379
|
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
1267
1380
|
if (messageId) {
|
|
1268
|
-
|
|
1269
|
-
return consumer.consume(handler, {
|
|
1270
|
-
messageId,
|
|
1271
|
-
skipPayload: true
|
|
1272
|
-
});
|
|
1273
|
-
} else {
|
|
1274
|
-
return consumer.consume(handler, { messageId });
|
|
1275
|
-
}
|
|
1381
|
+
return consumer.consume(handler, { messageId });
|
|
1276
1382
|
} else {
|
|
1277
1383
|
return consumer.consume(handler);
|
|
1278
1384
|
}
|
|
@@ -1330,10 +1436,15 @@ var Client = class {
|
|
|
1330
1436
|
BadRequestError,
|
|
1331
1437
|
BufferTransport,
|
|
1332
1438
|
Client,
|
|
1439
|
+
ConcurrencyLimitError,
|
|
1440
|
+
ConsumerDiscoveryError,
|
|
1441
|
+
ConsumerRegistryNotConfiguredError,
|
|
1442
|
+
DuplicateMessageError,
|
|
1333
1443
|
ForbiddenError,
|
|
1334
1444
|
InternalServerError,
|
|
1335
1445
|
InvalidLimitError,
|
|
1336
1446
|
JsonTransport,
|
|
1447
|
+
MessageAlreadyProcessedError,
|
|
1337
1448
|
MessageCorruptedError,
|
|
1338
1449
|
MessageLockedError,
|
|
1339
1450
|
MessageNotAvailableError,
|