@vercel/queue 0.0.0-alpha.33 → 0.0.0-alpha.35
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 +286 -85
- package/dist/index.d.mts +203 -127
- package/dist/index.d.ts +203 -127
- package/dist/index.js +680 -453
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +665 -453
- 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 +673 -474
- package/dist/nextjs-pages.js.map +1 -1
- package/dist/nextjs-pages.mjs +663 -474
- package/dist/nextjs-pages.mjs.map +1 -1
- package/dist/types-BLG4ASI_.d.mts +504 -0
- package/dist/types-BLG4ASI_.d.ts +504 -0
- package/package.json +2 -6
- package/bin/local-discover.js +0 -196
- package/dist/types-Dw29Fr9y.d.mts +0 -380
- package/dist/types-Dw29Fr9y.d.ts +0 -380
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,
|
|
@@ -62,6 +77,12 @@ var JsonTransport = class {
|
|
|
62
77
|
contentType = "application/json";
|
|
63
78
|
replacer;
|
|
64
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
|
+
*/
|
|
65
86
|
constructor(options = {}) {
|
|
66
87
|
this.replacer = options.replacer;
|
|
67
88
|
this.reviver = options.reviver;
|
|
@@ -91,6 +112,10 @@ var StreamTransport = class {
|
|
|
91
112
|
async deserialize(stream) {
|
|
92
113
|
return stream;
|
|
93
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
|
+
*/
|
|
94
119
|
async finalize(payload) {
|
|
95
120
|
const reader = payload.getReader();
|
|
96
121
|
try {
|
|
@@ -107,8 +132,9 @@ var StreamTransport = class {
|
|
|
107
132
|
// src/client.ts
|
|
108
133
|
var import_mixpart = require("mixpart");
|
|
109
134
|
|
|
110
|
-
// src/
|
|
111
|
-
var
|
|
135
|
+
// src/dev.ts
|
|
136
|
+
var fs = __toESM(require("fs"));
|
|
137
|
+
var path = __toESM(require("path"));
|
|
112
138
|
|
|
113
139
|
// src/types.ts
|
|
114
140
|
var MessageNotFoundError = class extends Error {
|
|
@@ -140,6 +166,7 @@ var QueueEmptyError = class extends Error {
|
|
|
140
166
|
}
|
|
141
167
|
};
|
|
142
168
|
var MessageLockedError = class extends Error {
|
|
169
|
+
/** Suggested retry delay in seconds, if provided by the server. */
|
|
143
170
|
retryAfter;
|
|
144
171
|
constructor(messageId, retryAfter) {
|
|
145
172
|
const retryMessage = retryAfter ? ` Retry after ${retryAfter} seconds.` : " Try again later.";
|
|
@@ -178,6 +205,265 @@ var InvalidLimitError = class extends Error {
|
|
|
178
205
|
this.name = "InvalidLimitError";
|
|
179
206
|
}
|
|
180
207
|
};
|
|
208
|
+
var MessageAlreadyProcessedError = class extends Error {
|
|
209
|
+
constructor(messageId) {
|
|
210
|
+
super(`Message ${messageId} has already been processed`);
|
|
211
|
+
this.name = "MessageAlreadyProcessedError";
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var ConcurrencyLimitError = class extends Error {
|
|
215
|
+
/** Current number of in-flight messages for this consumer group. */
|
|
216
|
+
currentInflight;
|
|
217
|
+
/** Maximum allowed concurrent messages (as configured). */
|
|
218
|
+
maxConcurrency;
|
|
219
|
+
constructor(message = "Concurrency limit exceeded", currentInflight, maxConcurrency) {
|
|
220
|
+
super(message);
|
|
221
|
+
this.name = "ConcurrencyLimitError";
|
|
222
|
+
this.currentInflight = currentInflight;
|
|
223
|
+
this.maxConcurrency = maxConcurrency;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var DuplicateMessageError = class extends Error {
|
|
227
|
+
idempotencyKey;
|
|
228
|
+
constructor(message, idempotencyKey) {
|
|
229
|
+
super(message);
|
|
230
|
+
this.name = "DuplicateMessageError";
|
|
231
|
+
this.idempotencyKey = idempotencyKey;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var ConsumerDiscoveryError = class extends Error {
|
|
235
|
+
deploymentId;
|
|
236
|
+
constructor(message, deploymentId) {
|
|
237
|
+
super(message);
|
|
238
|
+
this.name = "ConsumerDiscoveryError";
|
|
239
|
+
this.deploymentId = deploymentId;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var ConsumerRegistryNotConfiguredError = class extends Error {
|
|
243
|
+
constructor(message = "Consumer registry not configured") {
|
|
244
|
+
super(message);
|
|
245
|
+
this.name = "ConsumerRegistryNotConfiguredError";
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/dev.ts
|
|
250
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
251
|
+
function filePathToUrlPath(filePath) {
|
|
252
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|js|tsx|jsx)$/, "").replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
253
|
+
if (!urlPath.startsWith("/")) {
|
|
254
|
+
urlPath = "/" + urlPath;
|
|
255
|
+
}
|
|
256
|
+
return urlPath;
|
|
257
|
+
}
|
|
258
|
+
function getDevRouteMappings() {
|
|
259
|
+
const g = globalThis;
|
|
260
|
+
if (ROUTE_MAPPINGS_KEY in g) {
|
|
261
|
+
return g[ROUTE_MAPPINGS_KEY] ?? null;
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const vercelJsonPath = path.join(process.cwd(), "vercel.json");
|
|
265
|
+
if (!fs.existsSync(vercelJsonPath)) {
|
|
266
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const vercelJson = JSON.parse(fs.readFileSync(vercelJsonPath, "utf-8"));
|
|
270
|
+
if (!vercelJson.functions) {
|
|
271
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const mappings = [];
|
|
275
|
+
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
276
|
+
if (!config.experimentalTriggers) continue;
|
|
277
|
+
for (const trigger of config.experimentalTriggers) {
|
|
278
|
+
if (trigger.type?.startsWith("queue/") && trigger.topic && trigger.consumer) {
|
|
279
|
+
mappings.push({
|
|
280
|
+
urlPath: filePathToUrlPath(filePath),
|
|
281
|
+
topic: trigger.topic,
|
|
282
|
+
consumer: trigger.consumer
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
288
|
+
return g[ROUTE_MAPPINGS_KEY];
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.warn("[Dev Mode] Failed to read vercel.json:", error);
|
|
291
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function findMatchingRoutes(topicName) {
|
|
296
|
+
const mappings = getDevRouteMappings();
|
|
297
|
+
if (!mappings) {
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
return mappings.filter((mapping) => {
|
|
301
|
+
if (mapping.topic.includes("*")) {
|
|
302
|
+
return matchesWildcardPattern(topicName, mapping.topic);
|
|
303
|
+
}
|
|
304
|
+
return mapping.topic === topicName;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function isDevMode() {
|
|
308
|
+
return process.env.NODE_ENV === "development";
|
|
309
|
+
}
|
|
310
|
+
var DEV_VISIBILITY_POLL_INTERVAL = 50;
|
|
311
|
+
var DEV_VISIBILITY_MAX_WAIT = 5e3;
|
|
312
|
+
var DEV_VISIBILITY_BACKOFF_MULTIPLIER = 2;
|
|
313
|
+
async function waitForMessageVisibility(topicName, consumerGroup, messageId) {
|
|
314
|
+
const client = new QueueClient();
|
|
315
|
+
const transport = new JsonTransport();
|
|
316
|
+
let elapsed = 0;
|
|
317
|
+
let interval = DEV_VISIBILITY_POLL_INTERVAL;
|
|
318
|
+
while (elapsed < DEV_VISIBILITY_MAX_WAIT) {
|
|
319
|
+
try {
|
|
320
|
+
await client.receiveMessageById(
|
|
321
|
+
{
|
|
322
|
+
queueName: topicName,
|
|
323
|
+
consumerGroup,
|
|
324
|
+
messageId,
|
|
325
|
+
visibilityTimeoutSeconds: 0
|
|
326
|
+
},
|
|
327
|
+
transport
|
|
328
|
+
);
|
|
329
|
+
return true;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (error instanceof MessageNotFoundError) {
|
|
332
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
333
|
+
elapsed += interval;
|
|
334
|
+
interval = Math.min(
|
|
335
|
+
interval * DEV_VISIBILITY_BACKOFF_MULTIPLIER,
|
|
336
|
+
DEV_VISIBILITY_MAX_WAIT - elapsed
|
|
337
|
+
);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (error instanceof MessageAlreadyProcessedError) {
|
|
341
|
+
console.log(
|
|
342
|
+
`[Dev Mode] Message already processed: topic="${topicName}" messageId="${messageId}"`
|
|
343
|
+
);
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
console.error(
|
|
347
|
+
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
348
|
+
error
|
|
349
|
+
);
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
console.warn(
|
|
354
|
+
`[Dev Mode] Message visibility timeout after ${DEV_VISIBILITY_MAX_WAIT}ms: topic="${topicName}" messageId="${messageId}"`
|
|
355
|
+
);
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
function triggerDevCallbacks(topicName, messageId, delaySeconds) {
|
|
359
|
+
if (delaySeconds && delaySeconds > 0) {
|
|
360
|
+
console.log(
|
|
361
|
+
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
362
|
+
);
|
|
363
|
+
setTimeout(() => {
|
|
364
|
+
triggerDevCallbacks(topicName, messageId);
|
|
365
|
+
}, delaySeconds * 1e3);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
console.log(
|
|
369
|
+
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
370
|
+
);
|
|
371
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
372
|
+
if (matchingRoutes.length === 0) {
|
|
373
|
+
console.log(
|
|
374
|
+
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
375
|
+
);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
379
|
+
console.log(
|
|
380
|
+
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
381
|
+
);
|
|
382
|
+
(async () => {
|
|
383
|
+
const firstRoute = matchingRoutes[0];
|
|
384
|
+
const isVisible = await waitForMessageVisibility(
|
|
385
|
+
topicName,
|
|
386
|
+
firstRoute.consumer,
|
|
387
|
+
messageId
|
|
388
|
+
);
|
|
389
|
+
if (!isVisible) {
|
|
390
|
+
console.warn(
|
|
391
|
+
`[Dev Mode] Skipping callbacks - message not visible: topic="${topicName}" messageId="${messageId}"`
|
|
392
|
+
);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const port = process.env.PORT || 3e3;
|
|
396
|
+
const baseUrl = `http://localhost:${port}`;
|
|
397
|
+
for (const route of matchingRoutes) {
|
|
398
|
+
const url = `${baseUrl}${route.urlPath}`;
|
|
399
|
+
console.log(
|
|
400
|
+
`[Dev Mode] Invoking handler: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`
|
|
401
|
+
);
|
|
402
|
+
const cloudEvent = {
|
|
403
|
+
type: "com.vercel.queue.v1beta",
|
|
404
|
+
source: `/topic/${topicName}/consumer/${route.consumer}`,
|
|
405
|
+
id: messageId,
|
|
406
|
+
datacontenttype: "application/json",
|
|
407
|
+
data: {
|
|
408
|
+
messageId,
|
|
409
|
+
queueName: topicName,
|
|
410
|
+
consumerGroup: route.consumer
|
|
411
|
+
},
|
|
412
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
413
|
+
specversion: "1.0"
|
|
414
|
+
};
|
|
415
|
+
try {
|
|
416
|
+
const response = await fetch(url, {
|
|
417
|
+
method: "POST",
|
|
418
|
+
headers: {
|
|
419
|
+
"Content-Type": "application/cloudevents+json"
|
|
420
|
+
},
|
|
421
|
+
body: JSON.stringify(cloudEvent)
|
|
422
|
+
});
|
|
423
|
+
if (response.ok) {
|
|
424
|
+
try {
|
|
425
|
+
const responseData = await response.json();
|
|
426
|
+
if (responseData.status === "success") {
|
|
427
|
+
console.log(
|
|
428
|
+
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
} catch {
|
|
432
|
+
console.warn(
|
|
433
|
+
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
try {
|
|
438
|
+
const errorData = await response.json();
|
|
439
|
+
console.error(
|
|
440
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
441
|
+
);
|
|
442
|
+
} catch {
|
|
443
|
+
console.error(
|
|
444
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error(
|
|
450
|
+
`[Dev Mode] \u2717 HTTP request failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`,
|
|
451
|
+
error
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
})();
|
|
456
|
+
}
|
|
457
|
+
function clearDevRouteMappings() {
|
|
458
|
+
const g = globalThis;
|
|
459
|
+
delete g[ROUTE_MAPPINGS_KEY];
|
|
460
|
+
}
|
|
461
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
462
|
+
globalThis.__clearDevRouteMappings = clearDevRouteMappings;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/oidc.ts
|
|
466
|
+
var import_oidc = require("@vercel/oidc");
|
|
181
467
|
|
|
182
468
|
// src/client.ts
|
|
183
469
|
function isDebugEnabled() {
|
|
@@ -194,14 +480,6 @@ async function consumeStream(stream) {
|
|
|
194
480
|
reader.releaseLock();
|
|
195
481
|
}
|
|
196
482
|
}
|
|
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
483
|
function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters") {
|
|
206
484
|
if (status === 400) {
|
|
207
485
|
throw new BadRequestError(errorText || badRequestDefault);
|
|
@@ -224,8 +502,8 @@ function parseQueueHeaders(headers) {
|
|
|
224
502
|
const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
|
|
225
503
|
const timestamp = headers.get("Vqs-Timestamp");
|
|
226
504
|
const contentType = headers.get("Content-Type") || "application/octet-stream";
|
|
227
|
-
const
|
|
228
|
-
if (!messageId || !timestamp || !
|
|
505
|
+
const receiptHandle = headers.get("Vqs-Receipt-Handle");
|
|
506
|
+
if (!messageId || !timestamp || !receiptHandle) {
|
|
229
507
|
return null;
|
|
230
508
|
}
|
|
231
509
|
const deliveryCount = parseInt(deliveryCountStr, 10);
|
|
@@ -237,7 +515,7 @@ function parseQueueHeaders(headers) {
|
|
|
237
515
|
deliveryCount,
|
|
238
516
|
createdAt: new Date(timestamp),
|
|
239
517
|
contentType,
|
|
240
|
-
|
|
518
|
+
receiptHandle
|
|
241
519
|
};
|
|
242
520
|
}
|
|
243
521
|
var QueueClient = class {
|
|
@@ -245,15 +523,30 @@ var QueueClient = class {
|
|
|
245
523
|
basePath;
|
|
246
524
|
customHeaders;
|
|
247
525
|
providedToken;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
* @param options QueueClient configuration options
|
|
251
|
-
*/
|
|
526
|
+
defaultDeploymentId;
|
|
527
|
+
pinToDeployment;
|
|
252
528
|
constructor(options = {}) {
|
|
253
529
|
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/
|
|
530
|
+
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v3/topic";
|
|
255
531
|
this.customHeaders = options.headers || {};
|
|
256
532
|
this.providedToken = options.token;
|
|
533
|
+
this.defaultDeploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
|
|
534
|
+
this.pinToDeployment = options.pinToDeployment ?? true;
|
|
535
|
+
}
|
|
536
|
+
getSendDeploymentId() {
|
|
537
|
+
if (isDevMode()) {
|
|
538
|
+
return void 0;
|
|
539
|
+
}
|
|
540
|
+
if (this.pinToDeployment) {
|
|
541
|
+
return this.defaultDeploymentId;
|
|
542
|
+
}
|
|
543
|
+
return void 0;
|
|
544
|
+
}
|
|
545
|
+
getConsumeDeploymentId() {
|
|
546
|
+
if (isDevMode()) {
|
|
547
|
+
return void 0;
|
|
548
|
+
}
|
|
549
|
+
return this.defaultDeploymentId;
|
|
257
550
|
}
|
|
258
551
|
async getToken() {
|
|
259
552
|
if (this.providedToken) {
|
|
@@ -267,10 +560,12 @@ var QueueClient = class {
|
|
|
267
560
|
}
|
|
268
561
|
return token;
|
|
269
562
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
563
|
+
buildUrl(queueName, ...pathSegments) {
|
|
564
|
+
const encodedQueue = encodeURIComponent(queueName);
|
|
565
|
+
const segments = pathSegments.map((s) => encodeURIComponent(s));
|
|
566
|
+
const path2 = segments.length > 0 ? "/" + segments.join("/") : "";
|
|
567
|
+
return `${this.baseUrl}${this.basePath}/${encodedQueue}${path2}`;
|
|
568
|
+
}
|
|
274
569
|
async fetch(url, init) {
|
|
275
570
|
const method = init.method || "GET";
|
|
276
571
|
if (isDebugEnabled()) {
|
|
@@ -307,24 +602,38 @@ var QueueClient = class {
|
|
|
307
602
|
return response;
|
|
308
603
|
}
|
|
309
604
|
/**
|
|
310
|
-
* Send a message to a
|
|
311
|
-
*
|
|
312
|
-
* @param
|
|
313
|
-
* @
|
|
314
|
-
* @
|
|
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
|
|
315
619
|
* @throws {UnauthorizedError} When authentication fails
|
|
316
|
-
* @throws {ForbiddenError} When access is denied
|
|
620
|
+
* @throws {ForbiddenError} When access is denied
|
|
317
621
|
* @throws {InternalServerError} When server encounters an error
|
|
318
622
|
*/
|
|
319
623
|
async sendMessage(options, transport) {
|
|
320
|
-
const {
|
|
624
|
+
const {
|
|
625
|
+
queueName,
|
|
626
|
+
payload,
|
|
627
|
+
idempotencyKey,
|
|
628
|
+
retentionSeconds,
|
|
629
|
+
delaySeconds
|
|
630
|
+
} = options;
|
|
321
631
|
const headers = new Headers({
|
|
322
632
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
323
|
-
"Vqs-Queue-Name": queueName,
|
|
324
633
|
"Content-Type": transport.contentType,
|
|
325
634
|
...this.customHeaders
|
|
326
635
|
});
|
|
327
|
-
const deploymentId =
|
|
636
|
+
const deploymentId = this.getSendDeploymentId();
|
|
328
637
|
if (deploymentId) {
|
|
329
638
|
headers.set("Vqs-Deployment-Id", deploymentId);
|
|
330
639
|
}
|
|
@@ -334,8 +643,12 @@ var QueueClient = class {
|
|
|
334
643
|
if (retentionSeconds !== void 0) {
|
|
335
644
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
336
645
|
}
|
|
337
|
-
|
|
338
|
-
|
|
646
|
+
if (delaySeconds !== void 0) {
|
|
647
|
+
headers.set("Vqs-Delay-Seconds", delaySeconds.toString());
|
|
648
|
+
}
|
|
649
|
+
const serialized = transport.serialize(payload);
|
|
650
|
+
const body = Buffer.isBuffer(serialized) ? new Uint8Array(serialized) : serialized;
|
|
651
|
+
const response = await this.fetch(this.buildUrl(queueName), {
|
|
339
652
|
method: "POST",
|
|
340
653
|
body,
|
|
341
654
|
headers
|
|
@@ -343,7 +656,21 @@ var QueueClient = class {
|
|
|
343
656
|
if (!response.ok) {
|
|
344
657
|
const errorText = await response.text();
|
|
345
658
|
if (response.status === 409) {
|
|
346
|
-
throw new
|
|
659
|
+
throw new DuplicateMessageError(
|
|
660
|
+
errorText || "Duplicate idempotency key detected",
|
|
661
|
+
idempotencyKey
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (response.status === 502) {
|
|
665
|
+
throw new ConsumerDiscoveryError(
|
|
666
|
+
errorText || "Consumer discovery failed",
|
|
667
|
+
deploymentId
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (response.status === 503) {
|
|
671
|
+
throw new ConsumerRegistryNotConfiguredError(
|
|
672
|
+
errorText || "Consumer registry not configured"
|
|
673
|
+
);
|
|
347
674
|
}
|
|
348
675
|
throwCommonHttpError(
|
|
349
676
|
response.status,
|
|
@@ -356,52 +683,78 @@ var QueueClient = class {
|
|
|
356
683
|
return responseData;
|
|
357
684
|
}
|
|
358
685
|
/**
|
|
359
|
-
* Receive messages from a
|
|
360
|
-
*
|
|
361
|
-
* @param
|
|
362
|
-
* @
|
|
363
|
-
* @
|
|
364
|
-
* @
|
|
365
|
-
* @
|
|
366
|
-
* @
|
|
686
|
+
* Receive messages from a topic as an async generator.
|
|
687
|
+
*
|
|
688
|
+
* @param options - Receive options
|
|
689
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
690
|
+
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
691
|
+
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
692
|
+
* @param options.limit - Max messages to retrieve (default: 1, min: 1, max: 10)
|
|
693
|
+
* @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
|
|
694
|
+
* @param transport - Deserializer for message payloads
|
|
695
|
+
* @yields Message objects with payload, messageId, receiptHandle, etc.
|
|
696
|
+
* @throws {QueueEmptyError} When no messages available
|
|
697
|
+
* @throws {InvalidLimitError} When limit is outside 1-10 range
|
|
698
|
+
* @throws {ConcurrencyLimitError} When maxConcurrency exceeded
|
|
699
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
367
700
|
* @throws {UnauthorizedError} When authentication fails
|
|
368
|
-
* @throws {ForbiddenError} When access is denied
|
|
701
|
+
* @throws {ForbiddenError} When access is denied
|
|
369
702
|
* @throws {InternalServerError} When server encounters an error
|
|
370
703
|
*/
|
|
371
704
|
async *receiveMessages(options, transport) {
|
|
372
|
-
const {
|
|
705
|
+
const {
|
|
706
|
+
queueName,
|
|
707
|
+
consumerGroup,
|
|
708
|
+
visibilityTimeoutSeconds,
|
|
709
|
+
limit,
|
|
710
|
+
maxConcurrency
|
|
711
|
+
} = options;
|
|
373
712
|
if (limit !== void 0 && (limit < 1 || limit > 10)) {
|
|
374
713
|
throw new InvalidLimitError(limit);
|
|
375
714
|
}
|
|
376
715
|
const headers = new Headers({
|
|
377
716
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
378
|
-
"Vqs-Queue-Name": queueName,
|
|
379
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
380
717
|
Accept: "multipart/mixed",
|
|
381
718
|
...this.customHeaders
|
|
382
719
|
});
|
|
383
720
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
384
721
|
headers.set(
|
|
385
|
-
"Vqs-Visibility-Timeout",
|
|
722
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
386
723
|
visibilityTimeoutSeconds.toString()
|
|
387
724
|
);
|
|
388
725
|
}
|
|
389
726
|
if (limit !== void 0) {
|
|
390
|
-
headers.set("Vqs-
|
|
727
|
+
headers.set("Vqs-Max-Messages", limit.toString());
|
|
391
728
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
729
|
+
if (maxConcurrency !== void 0) {
|
|
730
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
731
|
+
}
|
|
732
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
733
|
+
if (effectiveDeploymentId) {
|
|
734
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
735
|
+
}
|
|
736
|
+
const response = await this.fetch(
|
|
737
|
+
this.buildUrl(queueName, "consumer", consumerGroup),
|
|
738
|
+
{
|
|
739
|
+
method: "POST",
|
|
740
|
+
headers
|
|
741
|
+
}
|
|
742
|
+
);
|
|
396
743
|
if (response.status === 204) {
|
|
397
744
|
throw new QueueEmptyError(queueName, consumerGroup);
|
|
398
745
|
}
|
|
399
746
|
if (!response.ok) {
|
|
400
747
|
const errorText = await response.text();
|
|
401
|
-
if (response.status ===
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
748
|
+
if (response.status === 429) {
|
|
749
|
+
let errorData = {};
|
|
750
|
+
try {
|
|
751
|
+
errorData = JSON.parse(errorText);
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
throw new ConcurrencyLimitError(
|
|
755
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
756
|
+
errorData.currentInflight,
|
|
757
|
+
errorData.maxConcurrency
|
|
405
758
|
);
|
|
406
759
|
}
|
|
407
760
|
throwCommonHttpError(
|
|
@@ -433,34 +786,56 @@ var QueueClient = class {
|
|
|
433
786
|
}
|
|
434
787
|
}
|
|
435
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* Receive a specific message by its ID.
|
|
791
|
+
*
|
|
792
|
+
* @param options - Receive options
|
|
793
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
794
|
+
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
795
|
+
* @param options.messageId - Message ID to retrieve
|
|
796
|
+
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
797
|
+
* @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
|
|
798
|
+
* @param transport - Deserializer for the message payload
|
|
799
|
+
* @returns Promise with the message
|
|
800
|
+
* @throws {MessageNotFoundError} When message doesn't exist
|
|
801
|
+
* @throws {MessageNotAvailableError} When message is in wrong state or was a duplicate
|
|
802
|
+
* @throws {MessageAlreadyProcessedError} When message was already processed
|
|
803
|
+
* @throws {ConcurrencyLimitError} When maxConcurrency exceeded
|
|
804
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
805
|
+
* @throws {UnauthorizedError} When authentication fails
|
|
806
|
+
* @throws {ForbiddenError} When access is denied
|
|
807
|
+
* @throws {InternalServerError} When server encounters an error
|
|
808
|
+
*/
|
|
436
809
|
async receiveMessageById(options, transport) {
|
|
437
810
|
const {
|
|
438
811
|
queueName,
|
|
439
812
|
consumerGroup,
|
|
440
813
|
messageId,
|
|
441
814
|
visibilityTimeoutSeconds,
|
|
442
|
-
|
|
815
|
+
maxConcurrency
|
|
443
816
|
} = options;
|
|
444
817
|
const headers = new Headers({
|
|
445
818
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
446
|
-
"Vqs-Queue-Name": queueName,
|
|
447
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
448
819
|
Accept: "multipart/mixed",
|
|
449
820
|
...this.customHeaders
|
|
450
821
|
});
|
|
451
822
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
452
823
|
headers.set(
|
|
453
|
-
"Vqs-Visibility-Timeout",
|
|
824
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
454
825
|
visibilityTimeoutSeconds.toString()
|
|
455
826
|
);
|
|
456
827
|
}
|
|
457
|
-
if (
|
|
458
|
-
headers.set("Vqs-
|
|
828
|
+
if (maxConcurrency !== void 0) {
|
|
829
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
830
|
+
}
|
|
831
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
832
|
+
if (effectiveDeploymentId) {
|
|
833
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
459
834
|
}
|
|
460
835
|
const response = await this.fetch(
|
|
461
|
-
|
|
836
|
+
this.buildUrl(queueName, "consumer", consumerGroup, "id", messageId),
|
|
462
837
|
{
|
|
463
|
-
method: "
|
|
838
|
+
method: "POST",
|
|
464
839
|
headers
|
|
465
840
|
}
|
|
466
841
|
);
|
|
@@ -470,12 +845,32 @@ var QueueClient = class {
|
|
|
470
845
|
throw new MessageNotFoundError(messageId);
|
|
471
846
|
}
|
|
472
847
|
if (response.status === 409) {
|
|
848
|
+
let errorData = {};
|
|
849
|
+
try {
|
|
850
|
+
errorData = JSON.parse(errorText);
|
|
851
|
+
} catch {
|
|
852
|
+
}
|
|
853
|
+
if (errorData.originalMessageId) {
|
|
854
|
+
throw new MessageNotAvailableError(
|
|
855
|
+
messageId,
|
|
856
|
+
`This message was a duplicate - use originalMessageId: ${errorData.originalMessageId}`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
473
859
|
throw new MessageNotAvailableError(messageId);
|
|
474
860
|
}
|
|
475
|
-
if (response.status ===
|
|
476
|
-
throw new
|
|
477
|
-
|
|
478
|
-
|
|
861
|
+
if (response.status === 410) {
|
|
862
|
+
throw new MessageAlreadyProcessedError(messageId);
|
|
863
|
+
}
|
|
864
|
+
if (response.status === 429) {
|
|
865
|
+
let errorData = {};
|
|
866
|
+
try {
|
|
867
|
+
errorData = JSON.parse(errorText);
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
throw new ConcurrencyLimitError(
|
|
871
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
872
|
+
errorData.currentInflight,
|
|
873
|
+
errorData.maxConcurrency
|
|
479
874
|
);
|
|
480
875
|
}
|
|
481
876
|
throwCommonHttpError(
|
|
@@ -485,95 +880,73 @@ var QueueClient = class {
|
|
|
485
880
|
"receive message by ID"
|
|
486
881
|
);
|
|
487
882
|
}
|
|
488
|
-
|
|
489
|
-
const parsedHeaders = parseQueueHeaders(
|
|
883
|
+
for await (const multipartMessage of (0, import_mixpart.parseMultipartStream)(response)) {
|
|
884
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
490
885
|
if (!parsedHeaders) {
|
|
886
|
+
await consumeStream(multipartMessage.payload);
|
|
491
887
|
throw new MessageCorruptedError(
|
|
492
888
|
messageId,
|
|
493
|
-
"Missing required queue headers in
|
|
889
|
+
"Missing required queue headers in response"
|
|
494
890
|
);
|
|
495
891
|
}
|
|
892
|
+
const deserializedPayload = await transport.deserialize(
|
|
893
|
+
multipartMessage.payload
|
|
894
|
+
);
|
|
496
895
|
const message = {
|
|
497
896
|
...parsedHeaders,
|
|
498
|
-
payload:
|
|
897
|
+
payload: deserializedPayload
|
|
499
898
|
};
|
|
500
899
|
return { message };
|
|
501
900
|
}
|
|
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
901
|
throw new MessageNotFoundError(messageId);
|
|
541
902
|
}
|
|
542
903
|
/**
|
|
543
|
-
* Delete a message
|
|
544
|
-
*
|
|
545
|
-
* @
|
|
546
|
-
* @
|
|
547
|
-
* @
|
|
548
|
-
* @
|
|
904
|
+
* Delete (acknowledge) a message after successful processing.
|
|
905
|
+
*
|
|
906
|
+
* @param options - Delete options
|
|
907
|
+
* @param options.queueName - Topic name
|
|
908
|
+
* @param options.consumerGroup - Consumer group name
|
|
909
|
+
* @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
|
|
910
|
+
* @returns Promise indicating deletion success
|
|
911
|
+
* @throws {MessageNotFoundError} When receipt handle not found
|
|
912
|
+
* @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
|
|
913
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
549
914
|
* @throws {UnauthorizedError} When authentication fails
|
|
550
|
-
* @throws {ForbiddenError} When access is denied
|
|
915
|
+
* @throws {ForbiddenError} When access is denied
|
|
551
916
|
* @throws {InternalServerError} When server encounters an error
|
|
552
917
|
*/
|
|
553
918
|
async deleteMessage(options) {
|
|
554
|
-
const { queueName, consumerGroup,
|
|
919
|
+
const { queueName, consumerGroup, receiptHandle } = options;
|
|
920
|
+
const headers = new Headers({
|
|
921
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
922
|
+
...this.customHeaders
|
|
923
|
+
});
|
|
924
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
925
|
+
if (effectiveDeploymentId) {
|
|
926
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
927
|
+
}
|
|
555
928
|
const response = await this.fetch(
|
|
556
|
-
|
|
929
|
+
this.buildUrl(
|
|
930
|
+
queueName,
|
|
931
|
+
"consumer",
|
|
932
|
+
consumerGroup,
|
|
933
|
+
"lease",
|
|
934
|
+
receiptHandle
|
|
935
|
+
),
|
|
557
936
|
{
|
|
558
937
|
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
|
-
})
|
|
938
|
+
headers
|
|
566
939
|
}
|
|
567
940
|
);
|
|
568
941
|
if (!response.ok) {
|
|
569
942
|
const errorText = await response.text();
|
|
570
943
|
if (response.status === 404) {
|
|
571
|
-
throw new MessageNotFoundError(
|
|
944
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
572
945
|
}
|
|
573
946
|
if (response.status === 409) {
|
|
574
947
|
throw new MessageNotAvailableError(
|
|
575
|
-
|
|
576
|
-
errorText || "Invalid
|
|
948
|
+
receiptHandle,
|
|
949
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
577
950
|
);
|
|
578
951
|
}
|
|
579
952
|
throwCommonHttpError(
|
|
@@ -581,53 +954,67 @@ var QueueClient = class {
|
|
|
581
954
|
response.statusText,
|
|
582
955
|
errorText,
|
|
583
956
|
"delete message",
|
|
584
|
-
"Missing or invalid
|
|
957
|
+
"Missing or invalid receipt handle"
|
|
585
958
|
);
|
|
586
959
|
}
|
|
587
960
|
return { deleted: true };
|
|
588
961
|
}
|
|
589
962
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* @
|
|
594
|
-
* @
|
|
595
|
-
* @
|
|
963
|
+
* Extend or change the visibility timeout of a message.
|
|
964
|
+
* Used to prevent message redelivery while still processing.
|
|
965
|
+
*
|
|
966
|
+
* @param options - Visibility options
|
|
967
|
+
* @param options.queueName - Topic name
|
|
968
|
+
* @param options.consumerGroup - Consumer group name
|
|
969
|
+
* @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
|
|
970
|
+
* @param options.visibilityTimeoutSeconds - New timeout (min: 0, max: 3600, cannot exceed message expiration)
|
|
971
|
+
* @returns Promise indicating success
|
|
972
|
+
* @throws {MessageNotFoundError} When receipt handle not found
|
|
973
|
+
* @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
|
|
974
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
596
975
|
* @throws {UnauthorizedError} When authentication fails
|
|
597
|
-
* @throws {ForbiddenError} When access is denied
|
|
976
|
+
* @throws {ForbiddenError} When access is denied
|
|
598
977
|
* @throws {InternalServerError} When server encounters an error
|
|
599
978
|
*/
|
|
600
979
|
async changeVisibility(options) {
|
|
601
980
|
const {
|
|
602
981
|
queueName,
|
|
603
982
|
consumerGroup,
|
|
604
|
-
|
|
605
|
-
ticket,
|
|
983
|
+
receiptHandle,
|
|
606
984
|
visibilityTimeoutSeconds
|
|
607
985
|
} = options;
|
|
986
|
+
const headers = new Headers({
|
|
987
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
988
|
+
"Content-Type": "application/json",
|
|
989
|
+
...this.customHeaders
|
|
990
|
+
});
|
|
991
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
992
|
+
if (effectiveDeploymentId) {
|
|
993
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
994
|
+
}
|
|
608
995
|
const response = await this.fetch(
|
|
609
|
-
|
|
996
|
+
this.buildUrl(
|
|
997
|
+
queueName,
|
|
998
|
+
"consumer",
|
|
999
|
+
consumerGroup,
|
|
1000
|
+
"lease",
|
|
1001
|
+
receiptHandle
|
|
1002
|
+
),
|
|
610
1003
|
{
|
|
611
1004
|
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
|
-
})
|
|
1005
|
+
headers,
|
|
1006
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
620
1007
|
}
|
|
621
1008
|
);
|
|
622
1009
|
if (!response.ok) {
|
|
623
1010
|
const errorText = await response.text();
|
|
624
1011
|
if (response.status === 404) {
|
|
625
|
-
throw new MessageNotFoundError(
|
|
1012
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
626
1013
|
}
|
|
627
1014
|
if (response.status === 409) {
|
|
628
1015
|
throw new MessageNotAvailableError(
|
|
629
|
-
|
|
630
|
-
errorText || "Invalid
|
|
1016
|
+
receiptHandle,
|
|
1017
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
631
1018
|
);
|
|
632
1019
|
}
|
|
633
1020
|
throwCommonHttpError(
|
|
@@ -635,194 +1022,72 @@ var QueueClient = class {
|
|
|
635
1022
|
response.statusText,
|
|
636
1023
|
errorText,
|
|
637
1024
|
"change visibility",
|
|
638
|
-
"Missing
|
|
1025
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
639
1026
|
);
|
|
640
1027
|
}
|
|
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);
|
|
1028
|
+
return { success: true };
|
|
664
1029
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1030
|
+
/**
|
|
1031
|
+
* Alternative endpoint for changing message visibility timeout.
|
|
1032
|
+
* Uses the /visibility path suffix and expects visibilityTimeoutSeconds in the body.
|
|
1033
|
+
* Functionally equivalent to changeVisibility but follows an alternative API pattern.
|
|
1034
|
+
*
|
|
1035
|
+
* @param options - Options for changing visibility
|
|
1036
|
+
* @returns Promise resolving to change visibility response
|
|
1037
|
+
*/
|
|
1038
|
+
async changeVisibilityAlt(options) {
|
|
1039
|
+
const {
|
|
1040
|
+
queueName,
|
|
1041
|
+
consumerGroup,
|
|
1042
|
+
receiptHandle,
|
|
1043
|
+
visibilityTimeoutSeconds
|
|
1044
|
+
} = options;
|
|
1045
|
+
const headers = new Headers({
|
|
1046
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
1047
|
+
"Content-Type": "application/json",
|
|
1048
|
+
...this.customHeaders
|
|
1049
|
+
});
|
|
1050
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
1051
|
+
if (effectiveDeploymentId) {
|
|
1052
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
686
1053
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
1054
|
+
const response = await this.fetch(
|
|
1055
|
+
this.buildUrl(
|
|
1056
|
+
queueName,
|
|
1057
|
+
"consumer",
|
|
1058
|
+
consumerGroup,
|
|
1059
|
+
"lease",
|
|
1060
|
+
receiptHandle,
|
|
1061
|
+
"visibility"
|
|
1062
|
+
),
|
|
1063
|
+
{
|
|
1064
|
+
method: "PATCH",
|
|
1065
|
+
headers,
|
|
1066
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
699
1067
|
}
|
|
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
|
-
}
|
|
1068
|
+
);
|
|
1069
|
+
if (!response.ok) {
|
|
1070
|
+
const errorText = await response.text();
|
|
1071
|
+
if (response.status === 404) {
|
|
1072
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
716
1073
|
}
|
|
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
|
-
}
|
|
1074
|
+
if (response.status === 409) {
|
|
1075
|
+
throw new MessageNotAvailableError(
|
|
1076
|
+
receiptHandle,
|
|
1077
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
1078
|
+
);
|
|
815
1079
|
}
|
|
1080
|
+
throwCommonHttpError(
|
|
1081
|
+
response.status,
|
|
1082
|
+
response.statusText,
|
|
1083
|
+
errorText,
|
|
1084
|
+
"change visibility (alt)",
|
|
1085
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
1086
|
+
);
|
|
816
1087
|
}
|
|
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
|
-
}
|
|
1088
|
+
return { success: true };
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
826
1091
|
|
|
827
1092
|
// src/consumer-group.ts
|
|
828
1093
|
var ConsumerGroup = class {
|
|
@@ -833,69 +1098,64 @@ var ConsumerGroup = class {
|
|
|
833
1098
|
refreshInterval;
|
|
834
1099
|
transport;
|
|
835
1100
|
/**
|
|
836
|
-
* Create a new ConsumerGroup instance
|
|
837
|
-
*
|
|
838
|
-
* @param
|
|
839
|
-
* @param
|
|
840
|
-
* @param
|
|
1101
|
+
* Create a new ConsumerGroup instance.
|
|
1102
|
+
*
|
|
1103
|
+
* @param client - QueueClient instance to use for API calls
|
|
1104
|
+
* @param topicName - Name of the topic to consume from (pattern: `[A-Za-z0-9_-]+`)
|
|
1105
|
+
* @param consumerGroupName - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
1106
|
+
* @param options - Optional configuration
|
|
1107
|
+
* @param options.transport - Payload serializer (default: JsonTransport)
|
|
1108
|
+
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
|
|
1109
|
+
* @param options.visibilityRefreshInterval - Lock refresh interval in seconds (default: visibilityTimeout / 3)
|
|
841
1110
|
*/
|
|
842
1111
|
constructor(client, topicName, consumerGroupName, options = {}) {
|
|
843
1112
|
this.client = client;
|
|
844
1113
|
this.topicName = topicName;
|
|
845
1114
|
this.consumerGroupName = consumerGroupName;
|
|
846
|
-
this.visibilityTimeout = options.visibilityTimeoutSeconds
|
|
847
|
-
this.refreshInterval = options.
|
|
1115
|
+
this.visibilityTimeout = options.visibilityTimeoutSeconds ?? 30;
|
|
1116
|
+
this.refreshInterval = options.visibilityRefreshInterval ?? Math.floor(this.visibilityTimeout / 3);
|
|
848
1117
|
this.transport = options.transport || new JsonTransport();
|
|
849
1118
|
}
|
|
850
1119
|
/**
|
|
851
1120
|
* Starts a background loop that periodically extends the visibility timeout for a message.
|
|
852
|
-
* This prevents the message from becoming visible to other consumers while it's being processed.
|
|
853
|
-
*
|
|
854
|
-
* The extension loop runs every `refreshInterval` seconds and updates the message's
|
|
855
|
-
* visibility timeout to `visibilityTimeout` seconds from the current time.
|
|
856
|
-
*
|
|
857
|
-
* @param messageId - The unique identifier of the message to extend visibility for
|
|
858
|
-
* @param ticket - The receipt ticket that proves ownership of the message
|
|
859
|
-
* @returns A function that when called will stop the extension loop
|
|
860
|
-
*
|
|
861
|
-
* @remarks
|
|
862
|
-
* - The first extension attempt occurs after `refreshInterval` seconds, not immediately
|
|
863
|
-
* - If an extension fails, the loop terminates with an error logged to console
|
|
864
|
-
* - The returned stop function is idempotent - calling it multiple times is safe
|
|
865
|
-
* - By default, the stop function returns immediately without waiting for in-flight
|
|
866
|
-
* - Pass `true` to the stop function to wait for any in-flight extension to complete
|
|
867
1121
|
*/
|
|
868
|
-
startVisibilityExtension(
|
|
1122
|
+
startVisibilityExtension(receiptHandle) {
|
|
869
1123
|
let isRunning = true;
|
|
1124
|
+
let isResolved = false;
|
|
870
1125
|
let resolveLifecycle;
|
|
871
1126
|
let timeoutId = null;
|
|
872
1127
|
const lifecyclePromise = new Promise((resolve) => {
|
|
873
1128
|
resolveLifecycle = resolve;
|
|
874
1129
|
});
|
|
1130
|
+
const safeResolve = () => {
|
|
1131
|
+
if (!isResolved) {
|
|
1132
|
+
isResolved = true;
|
|
1133
|
+
resolveLifecycle();
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
875
1136
|
const extend = async () => {
|
|
876
1137
|
if (!isRunning) {
|
|
877
|
-
|
|
1138
|
+
safeResolve();
|
|
878
1139
|
return;
|
|
879
1140
|
}
|
|
880
1141
|
try {
|
|
881
1142
|
await this.client.changeVisibility({
|
|
882
1143
|
queueName: this.topicName,
|
|
883
1144
|
consumerGroup: this.consumerGroupName,
|
|
884
|
-
|
|
885
|
-
ticket,
|
|
1145
|
+
receiptHandle,
|
|
886
1146
|
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
887
1147
|
});
|
|
888
1148
|
if (isRunning) {
|
|
889
1149
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
890
1150
|
} else {
|
|
891
|
-
|
|
1151
|
+
safeResolve();
|
|
892
1152
|
}
|
|
893
1153
|
} catch (error) {
|
|
894
1154
|
console.error(
|
|
895
|
-
`Failed to extend visibility for
|
|
1155
|
+
`Failed to extend visibility for receipt handle ${receiptHandle}:`,
|
|
896
1156
|
error
|
|
897
1157
|
);
|
|
898
|
-
|
|
1158
|
+
safeResolve();
|
|
899
1159
|
}
|
|
900
1160
|
};
|
|
901
1161
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
@@ -908,22 +1168,14 @@ var ConsumerGroup = class {
|
|
|
908
1168
|
if (waitForCompletion) {
|
|
909
1169
|
await lifecyclePromise;
|
|
910
1170
|
} else {
|
|
911
|
-
|
|
1171
|
+
safeResolve();
|
|
912
1172
|
}
|
|
913
1173
|
};
|
|
914
1174
|
}
|
|
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
1175
|
async processMessage(message, handler) {
|
|
921
|
-
const stopExtension = this.startVisibilityExtension(
|
|
922
|
-
message.messageId,
|
|
923
|
-
message.ticket
|
|
924
|
-
);
|
|
1176
|
+
const stopExtension = this.startVisibilityExtension(message.receiptHandle);
|
|
925
1177
|
try {
|
|
926
|
-
|
|
1178
|
+
await handler(message.payload, {
|
|
927
1179
|
messageId: message.messageId,
|
|
928
1180
|
deliveryCount: message.deliveryCount,
|
|
929
1181
|
createdAt: message.createdAt,
|
|
@@ -931,29 +1183,11 @@ var ConsumerGroup = class {
|
|
|
931
1183
|
consumerGroup: this.consumerGroupName
|
|
932
1184
|
});
|
|
933
1185
|
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
|
-
}
|
|
1186
|
+
await this.client.deleteMessage({
|
|
1187
|
+
queueName: this.topicName,
|
|
1188
|
+
consumerGroup: this.consumerGroupName,
|
|
1189
|
+
receiptHandle: message.receiptHandle
|
|
1190
|
+
});
|
|
957
1191
|
} catch (error) {
|
|
958
1192
|
await stopExtension();
|
|
959
1193
|
if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
|
|
@@ -968,36 +1202,16 @@ var ConsumerGroup = class {
|
|
|
968
1202
|
}
|
|
969
1203
|
async consume(handler, options) {
|
|
970
1204
|
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
|
-
}
|
|
1205
|
+
const response = await this.client.receiveMessageById(
|
|
1206
|
+
{
|
|
1207
|
+
queueName: this.topicName,
|
|
1208
|
+
consumerGroup: this.consumerGroupName,
|
|
1209
|
+
messageId: options.messageId,
|
|
1210
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1211
|
+
},
|
|
1212
|
+
this.transport
|
|
1213
|
+
);
|
|
1214
|
+
await this.processMessage(response.message, handler);
|
|
1001
1215
|
} else {
|
|
1002
1216
|
let messageFound = false;
|
|
1003
1217
|
for await (const message of this.client.receiveMessages(
|
|
@@ -1065,7 +1279,7 @@ var Topic = class {
|
|
|
1065
1279
|
payload,
|
|
1066
1280
|
idempotencyKey: options?.idempotencyKey,
|
|
1067
1281
|
retentionSeconds: options?.retentionSeconds,
|
|
1068
|
-
|
|
1282
|
+
delaySeconds: options?.delaySeconds
|
|
1069
1283
|
},
|
|
1070
1284
|
this.transport
|
|
1071
1285
|
);
|
|
@@ -1175,7 +1389,7 @@ async function parseCallback(request) {
|
|
|
1175
1389
|
messageId
|
|
1176
1390
|
};
|
|
1177
1391
|
}
|
|
1178
|
-
function createCallbackHandler(handlers, client) {
|
|
1392
|
+
function createCallbackHandler(handlers, client, visibilityTimeoutSeconds) {
|
|
1179
1393
|
for (const topicPattern in handlers) {
|
|
1180
1394
|
if (topicPattern.includes("*")) {
|
|
1181
1395
|
if (!validateWildcardPattern(topicPattern)) {
|
|
@@ -1211,7 +1425,10 @@ function createCallbackHandler(handlers, client) {
|
|
|
1211
1425
|
);
|
|
1212
1426
|
}
|
|
1213
1427
|
const topic = new Topic(client, queueName);
|
|
1214
|
-
const cg = topic.consumerGroup(
|
|
1428
|
+
const cg = topic.consumerGroup(
|
|
1429
|
+
consumerGroup,
|
|
1430
|
+
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : void 0
|
|
1431
|
+
);
|
|
1215
1432
|
await cg.consume(consumerGroupHandler, { messageId });
|
|
1216
1433
|
return Response.json({ status: "success" });
|
|
1217
1434
|
} catch (error) {
|
|
@@ -1225,13 +1442,14 @@ function createCallbackHandler(handlers, client) {
|
|
|
1225
1442
|
);
|
|
1226
1443
|
}
|
|
1227
1444
|
};
|
|
1228
|
-
if (isDevMode()) {
|
|
1229
|
-
registerDevRouteHandler(routeHandler, handlers);
|
|
1230
|
-
}
|
|
1231
1445
|
return routeHandler;
|
|
1232
1446
|
}
|
|
1233
|
-
function handleCallback(handlers,
|
|
1234
|
-
return createCallbackHandler(
|
|
1447
|
+
function handleCallback(handlers, options) {
|
|
1448
|
+
return createCallbackHandler(
|
|
1449
|
+
handlers,
|
|
1450
|
+
options?.client || new QueueClient(),
|
|
1451
|
+
options?.visibilityTimeoutSeconds
|
|
1452
|
+
);
|
|
1235
1453
|
}
|
|
1236
1454
|
|
|
1237
1455
|
// src/factory.ts
|
|
@@ -1244,12 +1462,12 @@ async function send(topicName, payload, options) {
|
|
|
1244
1462
|
payload,
|
|
1245
1463
|
idempotencyKey: options?.idempotencyKey,
|
|
1246
1464
|
retentionSeconds: options?.retentionSeconds,
|
|
1247
|
-
|
|
1465
|
+
delaySeconds: options?.delaySeconds
|
|
1248
1466
|
},
|
|
1249
1467
|
transport
|
|
1250
1468
|
);
|
|
1251
1469
|
if (isDevMode()) {
|
|
1252
|
-
triggerDevCallbacks(topicName, result.messageId);
|
|
1470
|
+
triggerDevCallbacks(topicName, result.messageId, options?.delaySeconds);
|
|
1253
1471
|
}
|
|
1254
1472
|
return { messageId: result.messageId };
|
|
1255
1473
|
}
|
|
@@ -1257,22 +1475,10 @@ async function receive(topicName, consumerGroup, handler, options) {
|
|
|
1257
1475
|
const transport = options?.transport || new JsonTransport();
|
|
1258
1476
|
const client = options?.client || new QueueClient();
|
|
1259
1477
|
const topic = new Topic(client, topicName, transport);
|
|
1260
|
-
const {
|
|
1261
|
-
messageId,
|
|
1262
|
-
skipPayload,
|
|
1263
|
-
client: _,
|
|
1264
|
-
...consumerGroupOptions
|
|
1265
|
-
} = options || {};
|
|
1478
|
+
const { messageId, client: _, ...consumerGroupOptions } = options || {};
|
|
1266
1479
|
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
1267
1480
|
if (messageId) {
|
|
1268
|
-
|
|
1269
|
-
return consumer.consume(handler, {
|
|
1270
|
-
messageId,
|
|
1271
|
-
skipPayload: true
|
|
1272
|
-
});
|
|
1273
|
-
} else {
|
|
1274
|
-
return consumer.consume(handler, { messageId });
|
|
1275
|
-
}
|
|
1481
|
+
return consumer.consume(handler, { messageId });
|
|
1276
1482
|
} else {
|
|
1277
1483
|
return consumer.consume(handler);
|
|
1278
1484
|
}
|
|
@@ -1306,23 +1512,39 @@ var Client = class {
|
|
|
1306
1512
|
});
|
|
1307
1513
|
}
|
|
1308
1514
|
/**
|
|
1309
|
-
* Create a callback handler for processing queue messages
|
|
1310
|
-
* Returns a Next.js route handler function that routes messages to appropriate handlers
|
|
1311
|
-
*
|
|
1515
|
+
* Create a callback handler for processing queue messages.
|
|
1516
|
+
* Returns a Next.js route handler function that routes messages to appropriate handlers.
|
|
1517
|
+
*
|
|
1518
|
+
* @param handlers - Object with topic-specific handlers organized by consumer groups
|
|
1519
|
+
* @param options - Optional configuration
|
|
1520
|
+
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
|
|
1312
1521
|
* @returns A Next.js route handler function
|
|
1313
1522
|
*
|
|
1314
1523
|
* @example
|
|
1315
1524
|
* ```typescript
|
|
1525
|
+
* // Basic usage
|
|
1316
1526
|
* export const POST = client.handleCallback({
|
|
1317
1527
|
* "user-events": {
|
|
1318
1528
|
* "welcome": (user, metadata) => console.log("Welcoming user", user),
|
|
1319
1529
|
* "analytics": (user, metadata) => console.log("Tracking user", user),
|
|
1320
1530
|
* },
|
|
1321
1531
|
* });
|
|
1532
|
+
*
|
|
1533
|
+
* // With custom visibility timeout
|
|
1534
|
+
* export const POST = client.handleCallback({
|
|
1535
|
+
* "video-processing": {
|
|
1536
|
+
* "transcode": async (video) => await transcodeVideo(video),
|
|
1537
|
+
* },
|
|
1538
|
+
* }, {
|
|
1539
|
+
* visibilityTimeoutSeconds: 300, // 5 minutes for long operations
|
|
1540
|
+
* });
|
|
1322
1541
|
* ```
|
|
1323
1542
|
*/
|
|
1324
|
-
handleCallback(handlers) {
|
|
1325
|
-
return handleCallback(handlers,
|
|
1543
|
+
handleCallback(handlers, options) {
|
|
1544
|
+
return handleCallback(handlers, {
|
|
1545
|
+
...options,
|
|
1546
|
+
client: this.client
|
|
1547
|
+
});
|
|
1326
1548
|
}
|
|
1327
1549
|
};
|
|
1328
1550
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1330,10 +1552,15 @@ var Client = class {
|
|
|
1330
1552
|
BadRequestError,
|
|
1331
1553
|
BufferTransport,
|
|
1332
1554
|
Client,
|
|
1555
|
+
ConcurrencyLimitError,
|
|
1556
|
+
ConsumerDiscoveryError,
|
|
1557
|
+
ConsumerRegistryNotConfiguredError,
|
|
1558
|
+
DuplicateMessageError,
|
|
1333
1559
|
ForbiddenError,
|
|
1334
1560
|
InternalServerError,
|
|
1335
1561
|
InvalidLimitError,
|
|
1336
1562
|
JsonTransport,
|
|
1563
|
+
MessageAlreadyProcessedError,
|
|
1337
1564
|
MessageCorruptedError,
|
|
1338
1565
|
MessageLockedError,
|
|
1339
1566
|
MessageNotAvailableError,
|