@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.mjs
CHANGED
|
@@ -19,6 +19,12 @@ var JsonTransport = class {
|
|
|
19
19
|
contentType = "application/json";
|
|
20
20
|
replacer;
|
|
21
21
|
reviver;
|
|
22
|
+
/**
|
|
23
|
+
* Create a new JsonTransport.
|
|
24
|
+
* @param options - Optional JSON serialization options
|
|
25
|
+
* @param options.replacer - Custom replacer for JSON.stringify
|
|
26
|
+
* @param options.reviver - Custom reviver for JSON.parse
|
|
27
|
+
*/
|
|
22
28
|
constructor(options = {}) {
|
|
23
29
|
this.replacer = options.replacer;
|
|
24
30
|
this.reviver = options.reviver;
|
|
@@ -48,6 +54,10 @@ var StreamTransport = class {
|
|
|
48
54
|
async deserialize(stream) {
|
|
49
55
|
return stream;
|
|
50
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Consume any remaining stream data to prevent resource leaks.
|
|
59
|
+
* Called automatically by ConsumerGroup; manual call required for direct client usage.
|
|
60
|
+
*/
|
|
51
61
|
async finalize(payload) {
|
|
52
62
|
const reader = payload.getReader();
|
|
53
63
|
try {
|
|
@@ -64,8 +74,9 @@ var StreamTransport = class {
|
|
|
64
74
|
// src/client.ts
|
|
65
75
|
import { parseMultipartStream } from "mixpart";
|
|
66
76
|
|
|
67
|
-
// src/
|
|
68
|
-
import
|
|
77
|
+
// src/dev.ts
|
|
78
|
+
import * as fs from "fs";
|
|
79
|
+
import * as path from "path";
|
|
69
80
|
|
|
70
81
|
// src/types.ts
|
|
71
82
|
var MessageNotFoundError = class extends Error {
|
|
@@ -97,6 +108,7 @@ var QueueEmptyError = class extends Error {
|
|
|
97
108
|
}
|
|
98
109
|
};
|
|
99
110
|
var MessageLockedError = class extends Error {
|
|
111
|
+
/** Suggested retry delay in seconds, if provided by the server. */
|
|
100
112
|
retryAfter;
|
|
101
113
|
constructor(messageId, retryAfter) {
|
|
102
114
|
const retryMessage = retryAfter ? ` Retry after ${retryAfter} seconds.` : " Try again later.";
|
|
@@ -135,6 +147,265 @@ var InvalidLimitError = class extends Error {
|
|
|
135
147
|
this.name = "InvalidLimitError";
|
|
136
148
|
}
|
|
137
149
|
};
|
|
150
|
+
var MessageAlreadyProcessedError = class extends Error {
|
|
151
|
+
constructor(messageId) {
|
|
152
|
+
super(`Message ${messageId} has already been processed`);
|
|
153
|
+
this.name = "MessageAlreadyProcessedError";
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var ConcurrencyLimitError = class extends Error {
|
|
157
|
+
/** Current number of in-flight messages for this consumer group. */
|
|
158
|
+
currentInflight;
|
|
159
|
+
/** Maximum allowed concurrent messages (as configured). */
|
|
160
|
+
maxConcurrency;
|
|
161
|
+
constructor(message = "Concurrency limit exceeded", currentInflight, maxConcurrency) {
|
|
162
|
+
super(message);
|
|
163
|
+
this.name = "ConcurrencyLimitError";
|
|
164
|
+
this.currentInflight = currentInflight;
|
|
165
|
+
this.maxConcurrency = maxConcurrency;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var DuplicateMessageError = class extends Error {
|
|
169
|
+
idempotencyKey;
|
|
170
|
+
constructor(message, idempotencyKey) {
|
|
171
|
+
super(message);
|
|
172
|
+
this.name = "DuplicateMessageError";
|
|
173
|
+
this.idempotencyKey = idempotencyKey;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var ConsumerDiscoveryError = class extends Error {
|
|
177
|
+
deploymentId;
|
|
178
|
+
constructor(message, deploymentId) {
|
|
179
|
+
super(message);
|
|
180
|
+
this.name = "ConsumerDiscoveryError";
|
|
181
|
+
this.deploymentId = deploymentId;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var ConsumerRegistryNotConfiguredError = class extends Error {
|
|
185
|
+
constructor(message = "Consumer registry not configured") {
|
|
186
|
+
super(message);
|
|
187
|
+
this.name = "ConsumerRegistryNotConfiguredError";
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/dev.ts
|
|
192
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
193
|
+
function filePathToUrlPath(filePath) {
|
|
194
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|js|tsx|jsx)$/, "").replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
195
|
+
if (!urlPath.startsWith("/")) {
|
|
196
|
+
urlPath = "/" + urlPath;
|
|
197
|
+
}
|
|
198
|
+
return urlPath;
|
|
199
|
+
}
|
|
200
|
+
function getDevRouteMappings() {
|
|
201
|
+
const g = globalThis;
|
|
202
|
+
if (ROUTE_MAPPINGS_KEY in g) {
|
|
203
|
+
return g[ROUTE_MAPPINGS_KEY] ?? null;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const vercelJsonPath = path.join(process.cwd(), "vercel.json");
|
|
207
|
+
if (!fs.existsSync(vercelJsonPath)) {
|
|
208
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const vercelJson = JSON.parse(fs.readFileSync(vercelJsonPath, "utf-8"));
|
|
212
|
+
if (!vercelJson.functions) {
|
|
213
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const mappings = [];
|
|
217
|
+
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
218
|
+
if (!config.experimentalTriggers) continue;
|
|
219
|
+
for (const trigger of config.experimentalTriggers) {
|
|
220
|
+
if (trigger.type?.startsWith("queue/") && trigger.topic && trigger.consumer) {
|
|
221
|
+
mappings.push({
|
|
222
|
+
urlPath: filePathToUrlPath(filePath),
|
|
223
|
+
topic: trigger.topic,
|
|
224
|
+
consumer: trigger.consumer
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
230
|
+
return g[ROUTE_MAPPINGS_KEY];
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.warn("[Dev Mode] Failed to read vercel.json:", error);
|
|
233
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function findMatchingRoutes(topicName) {
|
|
238
|
+
const mappings = getDevRouteMappings();
|
|
239
|
+
if (!mappings) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
return mappings.filter((mapping) => {
|
|
243
|
+
if (mapping.topic.includes("*")) {
|
|
244
|
+
return matchesWildcardPattern(topicName, mapping.topic);
|
|
245
|
+
}
|
|
246
|
+
return mapping.topic === topicName;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
function isDevMode() {
|
|
250
|
+
return process.env.NODE_ENV === "development";
|
|
251
|
+
}
|
|
252
|
+
var DEV_VISIBILITY_POLL_INTERVAL = 50;
|
|
253
|
+
var DEV_VISIBILITY_MAX_WAIT = 5e3;
|
|
254
|
+
var DEV_VISIBILITY_BACKOFF_MULTIPLIER = 2;
|
|
255
|
+
async function waitForMessageVisibility(topicName, consumerGroup, messageId) {
|
|
256
|
+
const client = new QueueClient();
|
|
257
|
+
const transport = new JsonTransport();
|
|
258
|
+
let elapsed = 0;
|
|
259
|
+
let interval = DEV_VISIBILITY_POLL_INTERVAL;
|
|
260
|
+
while (elapsed < DEV_VISIBILITY_MAX_WAIT) {
|
|
261
|
+
try {
|
|
262
|
+
await client.receiveMessageById(
|
|
263
|
+
{
|
|
264
|
+
queueName: topicName,
|
|
265
|
+
consumerGroup,
|
|
266
|
+
messageId,
|
|
267
|
+
visibilityTimeoutSeconds: 0
|
|
268
|
+
},
|
|
269
|
+
transport
|
|
270
|
+
);
|
|
271
|
+
return true;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (error instanceof MessageNotFoundError) {
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
275
|
+
elapsed += interval;
|
|
276
|
+
interval = Math.min(
|
|
277
|
+
interval * DEV_VISIBILITY_BACKOFF_MULTIPLIER,
|
|
278
|
+
DEV_VISIBILITY_MAX_WAIT - elapsed
|
|
279
|
+
);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (error instanceof MessageAlreadyProcessedError) {
|
|
283
|
+
console.log(
|
|
284
|
+
`[Dev Mode] Message already processed: topic="${topicName}" messageId="${messageId}"`
|
|
285
|
+
);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
console.error(
|
|
289
|
+
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
290
|
+
error
|
|
291
|
+
);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
console.warn(
|
|
296
|
+
`[Dev Mode] Message visibility timeout after ${DEV_VISIBILITY_MAX_WAIT}ms: topic="${topicName}" messageId="${messageId}"`
|
|
297
|
+
);
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
function triggerDevCallbacks(topicName, messageId, delaySeconds) {
|
|
301
|
+
if (delaySeconds && delaySeconds > 0) {
|
|
302
|
+
console.log(
|
|
303
|
+
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
304
|
+
);
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
triggerDevCallbacks(topicName, messageId);
|
|
307
|
+
}, delaySeconds * 1e3);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
console.log(
|
|
311
|
+
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
312
|
+
);
|
|
313
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
314
|
+
if (matchingRoutes.length === 0) {
|
|
315
|
+
console.log(
|
|
316
|
+
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
317
|
+
);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
321
|
+
console.log(
|
|
322
|
+
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
323
|
+
);
|
|
324
|
+
(async () => {
|
|
325
|
+
const firstRoute = matchingRoutes[0];
|
|
326
|
+
const isVisible = await waitForMessageVisibility(
|
|
327
|
+
topicName,
|
|
328
|
+
firstRoute.consumer,
|
|
329
|
+
messageId
|
|
330
|
+
);
|
|
331
|
+
if (!isVisible) {
|
|
332
|
+
console.warn(
|
|
333
|
+
`[Dev Mode] Skipping callbacks - message not visible: topic="${topicName}" messageId="${messageId}"`
|
|
334
|
+
);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const port = process.env.PORT || 3e3;
|
|
338
|
+
const baseUrl = `http://localhost:${port}`;
|
|
339
|
+
for (const route of matchingRoutes) {
|
|
340
|
+
const url = `${baseUrl}${route.urlPath}`;
|
|
341
|
+
console.log(
|
|
342
|
+
`[Dev Mode] Invoking handler: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`
|
|
343
|
+
);
|
|
344
|
+
const cloudEvent = {
|
|
345
|
+
type: "com.vercel.queue.v1beta",
|
|
346
|
+
source: `/topic/${topicName}/consumer/${route.consumer}`,
|
|
347
|
+
id: messageId,
|
|
348
|
+
datacontenttype: "application/json",
|
|
349
|
+
data: {
|
|
350
|
+
messageId,
|
|
351
|
+
queueName: topicName,
|
|
352
|
+
consumerGroup: route.consumer
|
|
353
|
+
},
|
|
354
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
355
|
+
specversion: "1.0"
|
|
356
|
+
};
|
|
357
|
+
try {
|
|
358
|
+
const response = await fetch(url, {
|
|
359
|
+
method: "POST",
|
|
360
|
+
headers: {
|
|
361
|
+
"Content-Type": "application/cloudevents+json"
|
|
362
|
+
},
|
|
363
|
+
body: JSON.stringify(cloudEvent)
|
|
364
|
+
});
|
|
365
|
+
if (response.ok) {
|
|
366
|
+
try {
|
|
367
|
+
const responseData = await response.json();
|
|
368
|
+
if (responseData.status === "success") {
|
|
369
|
+
console.log(
|
|
370
|
+
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
console.warn(
|
|
375
|
+
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
try {
|
|
380
|
+
const errorData = await response.json();
|
|
381
|
+
console.error(
|
|
382
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
383
|
+
);
|
|
384
|
+
} catch {
|
|
385
|
+
console.error(
|
|
386
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error(
|
|
392
|
+
`[Dev Mode] \u2717 HTTP request failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`,
|
|
393
|
+
error
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
})();
|
|
398
|
+
}
|
|
399
|
+
function clearDevRouteMappings() {
|
|
400
|
+
const g = globalThis;
|
|
401
|
+
delete g[ROUTE_MAPPINGS_KEY];
|
|
402
|
+
}
|
|
403
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
404
|
+
globalThis.__clearDevRouteMappings = clearDevRouteMappings;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/oidc.ts
|
|
408
|
+
import { getVercelOidcToken } from "@vercel/oidc";
|
|
138
409
|
|
|
139
410
|
// src/client.ts
|
|
140
411
|
function isDebugEnabled() {
|
|
@@ -151,14 +422,6 @@ async function consumeStream(stream) {
|
|
|
151
422
|
reader.releaseLock();
|
|
152
423
|
}
|
|
153
424
|
}
|
|
154
|
-
function parseRetryAfter(headers) {
|
|
155
|
-
const retryAfterHeader = headers.get("Retry-After");
|
|
156
|
-
if (retryAfterHeader) {
|
|
157
|
-
const parsed = parseInt(retryAfterHeader, 10);
|
|
158
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
159
|
-
}
|
|
160
|
-
return void 0;
|
|
161
|
-
}
|
|
162
425
|
function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters") {
|
|
163
426
|
if (status === 400) {
|
|
164
427
|
throw new BadRequestError(errorText || badRequestDefault);
|
|
@@ -181,8 +444,8 @@ function parseQueueHeaders(headers) {
|
|
|
181
444
|
const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
|
|
182
445
|
const timestamp = headers.get("Vqs-Timestamp");
|
|
183
446
|
const contentType = headers.get("Content-Type") || "application/octet-stream";
|
|
184
|
-
const
|
|
185
|
-
if (!messageId || !timestamp || !
|
|
447
|
+
const receiptHandle = headers.get("Vqs-Receipt-Handle");
|
|
448
|
+
if (!messageId || !timestamp || !receiptHandle) {
|
|
186
449
|
return null;
|
|
187
450
|
}
|
|
188
451
|
const deliveryCount = parseInt(deliveryCountStr, 10);
|
|
@@ -194,7 +457,7 @@ function parseQueueHeaders(headers) {
|
|
|
194
457
|
deliveryCount,
|
|
195
458
|
createdAt: new Date(timestamp),
|
|
196
459
|
contentType,
|
|
197
|
-
|
|
460
|
+
receiptHandle
|
|
198
461
|
};
|
|
199
462
|
}
|
|
200
463
|
var QueueClient = class {
|
|
@@ -202,15 +465,30 @@ var QueueClient = class {
|
|
|
202
465
|
basePath;
|
|
203
466
|
customHeaders;
|
|
204
467
|
providedToken;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* @param options QueueClient configuration options
|
|
208
|
-
*/
|
|
468
|
+
defaultDeploymentId;
|
|
469
|
+
pinToDeployment;
|
|
209
470
|
constructor(options = {}) {
|
|
210
471
|
this.baseUrl = options.baseUrl || process.env.VERCEL_QUEUE_BASE_URL || "https://vercel-queue.com";
|
|
211
|
-
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/
|
|
472
|
+
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v3/topic";
|
|
212
473
|
this.customHeaders = options.headers || {};
|
|
213
474
|
this.providedToken = options.token;
|
|
475
|
+
this.defaultDeploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
|
|
476
|
+
this.pinToDeployment = options.pinToDeployment ?? true;
|
|
477
|
+
}
|
|
478
|
+
getSendDeploymentId() {
|
|
479
|
+
if (isDevMode()) {
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
if (this.pinToDeployment) {
|
|
483
|
+
return this.defaultDeploymentId;
|
|
484
|
+
}
|
|
485
|
+
return void 0;
|
|
486
|
+
}
|
|
487
|
+
getConsumeDeploymentId() {
|
|
488
|
+
if (isDevMode()) {
|
|
489
|
+
return void 0;
|
|
490
|
+
}
|
|
491
|
+
return this.defaultDeploymentId;
|
|
214
492
|
}
|
|
215
493
|
async getToken() {
|
|
216
494
|
if (this.providedToken) {
|
|
@@ -224,10 +502,12 @@ var QueueClient = class {
|
|
|
224
502
|
}
|
|
225
503
|
return token;
|
|
226
504
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
505
|
+
buildUrl(queueName, ...pathSegments) {
|
|
506
|
+
const encodedQueue = encodeURIComponent(queueName);
|
|
507
|
+
const segments = pathSegments.map((s) => encodeURIComponent(s));
|
|
508
|
+
const path2 = segments.length > 0 ? "/" + segments.join("/") : "";
|
|
509
|
+
return `${this.baseUrl}${this.basePath}/${encodedQueue}${path2}`;
|
|
510
|
+
}
|
|
231
511
|
async fetch(url, init) {
|
|
232
512
|
const method = init.method || "GET";
|
|
233
513
|
if (isDebugEnabled()) {
|
|
@@ -264,24 +544,38 @@ var QueueClient = class {
|
|
|
264
544
|
return response;
|
|
265
545
|
}
|
|
266
546
|
/**
|
|
267
|
-
* Send a message to a
|
|
268
|
-
*
|
|
269
|
-
* @param
|
|
270
|
-
* @
|
|
271
|
-
* @
|
|
547
|
+
* Send a message to a topic.
|
|
548
|
+
*
|
|
549
|
+
* @param options - Message options including queue name, payload, and optional settings
|
|
550
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
551
|
+
* @param options.payload - Message payload
|
|
552
|
+
* @param options.idempotencyKey - Optional deduplication key (dedup window: min(retention, 24h))
|
|
553
|
+
* @param options.retentionSeconds - Message TTL (default: 86400, min: 60, max: 86400)
|
|
554
|
+
* @param options.delaySeconds - Delivery delay (default: 0, max: retentionSeconds)
|
|
555
|
+
* @param transport - Serializer for the payload
|
|
556
|
+
* @returns Promise with the generated messageId
|
|
557
|
+
* @throws {DuplicateMessageError} When idempotency key was already used
|
|
558
|
+
* @throws {ConsumerDiscoveryError} When consumer discovery fails
|
|
559
|
+
* @throws {ConsumerRegistryNotConfiguredError} When registry not configured
|
|
560
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
272
561
|
* @throws {UnauthorizedError} When authentication fails
|
|
273
|
-
* @throws {ForbiddenError} When access is denied
|
|
562
|
+
* @throws {ForbiddenError} When access is denied
|
|
274
563
|
* @throws {InternalServerError} When server encounters an error
|
|
275
564
|
*/
|
|
276
565
|
async sendMessage(options, transport) {
|
|
277
|
-
const {
|
|
566
|
+
const {
|
|
567
|
+
queueName,
|
|
568
|
+
payload,
|
|
569
|
+
idempotencyKey,
|
|
570
|
+
retentionSeconds,
|
|
571
|
+
delaySeconds
|
|
572
|
+
} = options;
|
|
278
573
|
const headers = new Headers({
|
|
279
574
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
280
|
-
"Vqs-Queue-Name": queueName,
|
|
281
575
|
"Content-Type": transport.contentType,
|
|
282
576
|
...this.customHeaders
|
|
283
577
|
});
|
|
284
|
-
const deploymentId =
|
|
578
|
+
const deploymentId = this.getSendDeploymentId();
|
|
285
579
|
if (deploymentId) {
|
|
286
580
|
headers.set("Vqs-Deployment-Id", deploymentId);
|
|
287
581
|
}
|
|
@@ -291,8 +585,12 @@ var QueueClient = class {
|
|
|
291
585
|
if (retentionSeconds !== void 0) {
|
|
292
586
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
293
587
|
}
|
|
294
|
-
|
|
295
|
-
|
|
588
|
+
if (delaySeconds !== void 0) {
|
|
589
|
+
headers.set("Vqs-Delay-Seconds", delaySeconds.toString());
|
|
590
|
+
}
|
|
591
|
+
const serialized = transport.serialize(payload);
|
|
592
|
+
const body = Buffer.isBuffer(serialized) ? new Uint8Array(serialized) : serialized;
|
|
593
|
+
const response = await this.fetch(this.buildUrl(queueName), {
|
|
296
594
|
method: "POST",
|
|
297
595
|
body,
|
|
298
596
|
headers
|
|
@@ -300,7 +598,21 @@ var QueueClient = class {
|
|
|
300
598
|
if (!response.ok) {
|
|
301
599
|
const errorText = await response.text();
|
|
302
600
|
if (response.status === 409) {
|
|
303
|
-
throw new
|
|
601
|
+
throw new DuplicateMessageError(
|
|
602
|
+
errorText || "Duplicate idempotency key detected",
|
|
603
|
+
idempotencyKey
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
if (response.status === 502) {
|
|
607
|
+
throw new ConsumerDiscoveryError(
|
|
608
|
+
errorText || "Consumer discovery failed",
|
|
609
|
+
deploymentId
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (response.status === 503) {
|
|
613
|
+
throw new ConsumerRegistryNotConfiguredError(
|
|
614
|
+
errorText || "Consumer registry not configured"
|
|
615
|
+
);
|
|
304
616
|
}
|
|
305
617
|
throwCommonHttpError(
|
|
306
618
|
response.status,
|
|
@@ -313,52 +625,78 @@ var QueueClient = class {
|
|
|
313
625
|
return responseData;
|
|
314
626
|
}
|
|
315
627
|
/**
|
|
316
|
-
* Receive messages from a
|
|
317
|
-
*
|
|
318
|
-
* @param
|
|
319
|
-
* @
|
|
320
|
-
* @
|
|
321
|
-
* @
|
|
322
|
-
* @
|
|
323
|
-
* @
|
|
628
|
+
* Receive messages from a topic as an async generator.
|
|
629
|
+
*
|
|
630
|
+
* @param options - Receive options
|
|
631
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
632
|
+
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
633
|
+
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
634
|
+
* @param options.limit - Max messages to retrieve (default: 1, min: 1, max: 10)
|
|
635
|
+
* @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
|
|
636
|
+
* @param transport - Deserializer for message payloads
|
|
637
|
+
* @yields Message objects with payload, messageId, receiptHandle, etc.
|
|
638
|
+
* @throws {QueueEmptyError} When no messages available
|
|
639
|
+
* @throws {InvalidLimitError} When limit is outside 1-10 range
|
|
640
|
+
* @throws {ConcurrencyLimitError} When maxConcurrency exceeded
|
|
641
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
324
642
|
* @throws {UnauthorizedError} When authentication fails
|
|
325
|
-
* @throws {ForbiddenError} When access is denied
|
|
643
|
+
* @throws {ForbiddenError} When access is denied
|
|
326
644
|
* @throws {InternalServerError} When server encounters an error
|
|
327
645
|
*/
|
|
328
646
|
async *receiveMessages(options, transport) {
|
|
329
|
-
const {
|
|
647
|
+
const {
|
|
648
|
+
queueName,
|
|
649
|
+
consumerGroup,
|
|
650
|
+
visibilityTimeoutSeconds,
|
|
651
|
+
limit,
|
|
652
|
+
maxConcurrency
|
|
653
|
+
} = options;
|
|
330
654
|
if (limit !== void 0 && (limit < 1 || limit > 10)) {
|
|
331
655
|
throw new InvalidLimitError(limit);
|
|
332
656
|
}
|
|
333
657
|
const headers = new Headers({
|
|
334
658
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
335
|
-
"Vqs-Queue-Name": queueName,
|
|
336
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
337
659
|
Accept: "multipart/mixed",
|
|
338
660
|
...this.customHeaders
|
|
339
661
|
});
|
|
340
662
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
341
663
|
headers.set(
|
|
342
|
-
"Vqs-Visibility-Timeout",
|
|
664
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
343
665
|
visibilityTimeoutSeconds.toString()
|
|
344
666
|
);
|
|
345
667
|
}
|
|
346
668
|
if (limit !== void 0) {
|
|
347
|
-
headers.set("Vqs-
|
|
669
|
+
headers.set("Vqs-Max-Messages", limit.toString());
|
|
348
670
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
671
|
+
if (maxConcurrency !== void 0) {
|
|
672
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
673
|
+
}
|
|
674
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
675
|
+
if (effectiveDeploymentId) {
|
|
676
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
677
|
+
}
|
|
678
|
+
const response = await this.fetch(
|
|
679
|
+
this.buildUrl(queueName, "consumer", consumerGroup),
|
|
680
|
+
{
|
|
681
|
+
method: "POST",
|
|
682
|
+
headers
|
|
683
|
+
}
|
|
684
|
+
);
|
|
353
685
|
if (response.status === 204) {
|
|
354
686
|
throw new QueueEmptyError(queueName, consumerGroup);
|
|
355
687
|
}
|
|
356
688
|
if (!response.ok) {
|
|
357
689
|
const errorText = await response.text();
|
|
358
|
-
if (response.status ===
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
690
|
+
if (response.status === 429) {
|
|
691
|
+
let errorData = {};
|
|
692
|
+
try {
|
|
693
|
+
errorData = JSON.parse(errorText);
|
|
694
|
+
} catch {
|
|
695
|
+
}
|
|
696
|
+
throw new ConcurrencyLimitError(
|
|
697
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
698
|
+
errorData.currentInflight,
|
|
699
|
+
errorData.maxConcurrency
|
|
362
700
|
);
|
|
363
701
|
}
|
|
364
702
|
throwCommonHttpError(
|
|
@@ -390,34 +728,56 @@ var QueueClient = class {
|
|
|
390
728
|
}
|
|
391
729
|
}
|
|
392
730
|
}
|
|
731
|
+
/**
|
|
732
|
+
* Receive a specific message by its ID.
|
|
733
|
+
*
|
|
734
|
+
* @param options - Receive options
|
|
735
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
736
|
+
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
737
|
+
* @param options.messageId - Message ID to retrieve
|
|
738
|
+
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
739
|
+
* @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
|
|
740
|
+
* @param transport - Deserializer for the message payload
|
|
741
|
+
* @returns Promise with the message
|
|
742
|
+
* @throws {MessageNotFoundError} When message doesn't exist
|
|
743
|
+
* @throws {MessageNotAvailableError} When message is in wrong state or was a duplicate
|
|
744
|
+
* @throws {MessageAlreadyProcessedError} When message was already processed
|
|
745
|
+
* @throws {ConcurrencyLimitError} When maxConcurrency exceeded
|
|
746
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
747
|
+
* @throws {UnauthorizedError} When authentication fails
|
|
748
|
+
* @throws {ForbiddenError} When access is denied
|
|
749
|
+
* @throws {InternalServerError} When server encounters an error
|
|
750
|
+
*/
|
|
393
751
|
async receiveMessageById(options, transport) {
|
|
394
752
|
const {
|
|
395
753
|
queueName,
|
|
396
754
|
consumerGroup,
|
|
397
755
|
messageId,
|
|
398
756
|
visibilityTimeoutSeconds,
|
|
399
|
-
|
|
757
|
+
maxConcurrency
|
|
400
758
|
} = options;
|
|
401
759
|
const headers = new Headers({
|
|
402
760
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
403
|
-
"Vqs-Queue-Name": queueName,
|
|
404
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
405
761
|
Accept: "multipart/mixed",
|
|
406
762
|
...this.customHeaders
|
|
407
763
|
});
|
|
408
764
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
409
765
|
headers.set(
|
|
410
|
-
"Vqs-Visibility-Timeout",
|
|
766
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
411
767
|
visibilityTimeoutSeconds.toString()
|
|
412
768
|
);
|
|
413
769
|
}
|
|
414
|
-
if (
|
|
415
|
-
headers.set("Vqs-
|
|
770
|
+
if (maxConcurrency !== void 0) {
|
|
771
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
772
|
+
}
|
|
773
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
774
|
+
if (effectiveDeploymentId) {
|
|
775
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
416
776
|
}
|
|
417
777
|
const response = await this.fetch(
|
|
418
|
-
|
|
778
|
+
this.buildUrl(queueName, "consumer", consumerGroup, "id", messageId),
|
|
419
779
|
{
|
|
420
|
-
method: "
|
|
780
|
+
method: "POST",
|
|
421
781
|
headers
|
|
422
782
|
}
|
|
423
783
|
);
|
|
@@ -427,12 +787,32 @@ var QueueClient = class {
|
|
|
427
787
|
throw new MessageNotFoundError(messageId);
|
|
428
788
|
}
|
|
429
789
|
if (response.status === 409) {
|
|
790
|
+
let errorData = {};
|
|
791
|
+
try {
|
|
792
|
+
errorData = JSON.parse(errorText);
|
|
793
|
+
} catch {
|
|
794
|
+
}
|
|
795
|
+
if (errorData.originalMessageId) {
|
|
796
|
+
throw new MessageNotAvailableError(
|
|
797
|
+
messageId,
|
|
798
|
+
`This message was a duplicate - use originalMessageId: ${errorData.originalMessageId}`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
430
801
|
throw new MessageNotAvailableError(messageId);
|
|
431
802
|
}
|
|
432
|
-
if (response.status ===
|
|
433
|
-
throw new
|
|
434
|
-
|
|
435
|
-
|
|
803
|
+
if (response.status === 410) {
|
|
804
|
+
throw new MessageAlreadyProcessedError(messageId);
|
|
805
|
+
}
|
|
806
|
+
if (response.status === 429) {
|
|
807
|
+
let errorData = {};
|
|
808
|
+
try {
|
|
809
|
+
errorData = JSON.parse(errorText);
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
throw new ConcurrencyLimitError(
|
|
813
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
814
|
+
errorData.currentInflight,
|
|
815
|
+
errorData.maxConcurrency
|
|
436
816
|
);
|
|
437
817
|
}
|
|
438
818
|
throwCommonHttpError(
|
|
@@ -442,95 +822,73 @@ var QueueClient = class {
|
|
|
442
822
|
"receive message by ID"
|
|
443
823
|
);
|
|
444
824
|
}
|
|
445
|
-
|
|
446
|
-
const parsedHeaders = parseQueueHeaders(
|
|
825
|
+
for await (const multipartMessage of parseMultipartStream(response)) {
|
|
826
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
447
827
|
if (!parsedHeaders) {
|
|
828
|
+
await consumeStream(multipartMessage.payload);
|
|
448
829
|
throw new MessageCorruptedError(
|
|
449
830
|
messageId,
|
|
450
|
-
"Missing required queue headers in
|
|
831
|
+
"Missing required queue headers in response"
|
|
451
832
|
);
|
|
452
833
|
}
|
|
834
|
+
const deserializedPayload = await transport.deserialize(
|
|
835
|
+
multipartMessage.payload
|
|
836
|
+
);
|
|
453
837
|
const message = {
|
|
454
838
|
...parsedHeaders,
|
|
455
|
-
payload:
|
|
839
|
+
payload: deserializedPayload
|
|
456
840
|
};
|
|
457
841
|
return { message };
|
|
458
842
|
}
|
|
459
|
-
if (!transport) {
|
|
460
|
-
throw new Error("Transport is required when skipPayload is not true");
|
|
461
|
-
}
|
|
462
|
-
try {
|
|
463
|
-
for await (const multipartMessage of parseMultipartStream(response)) {
|
|
464
|
-
try {
|
|
465
|
-
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
466
|
-
if (!parsedHeaders) {
|
|
467
|
-
console.warn("Missing required queue headers in multipart part");
|
|
468
|
-
await consumeStream(multipartMessage.payload);
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
const deserializedPayload = await transport.deserialize(
|
|
472
|
-
multipartMessage.payload
|
|
473
|
-
);
|
|
474
|
-
const message = {
|
|
475
|
-
...parsedHeaders,
|
|
476
|
-
payload: deserializedPayload
|
|
477
|
-
};
|
|
478
|
-
return { message };
|
|
479
|
-
} catch (error) {
|
|
480
|
-
console.warn("Failed to deserialize message by ID:", error);
|
|
481
|
-
await consumeStream(multipartMessage.payload);
|
|
482
|
-
throw new MessageCorruptedError(
|
|
483
|
-
messageId,
|
|
484
|
-
`Failed to deserialize payload: ${error}`
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
} catch (error) {
|
|
489
|
-
if (error instanceof MessageCorruptedError) {
|
|
490
|
-
throw error;
|
|
491
|
-
}
|
|
492
|
-
throw new MessageCorruptedError(
|
|
493
|
-
messageId,
|
|
494
|
-
`Failed to parse multipart response: ${error}`
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
843
|
throw new MessageNotFoundError(messageId);
|
|
498
844
|
}
|
|
499
845
|
/**
|
|
500
|
-
* Delete a message
|
|
501
|
-
*
|
|
502
|
-
* @
|
|
503
|
-
* @
|
|
504
|
-
* @
|
|
505
|
-
* @
|
|
846
|
+
* Delete (acknowledge) a message after successful processing.
|
|
847
|
+
*
|
|
848
|
+
* @param options - Delete options
|
|
849
|
+
* @param options.queueName - Topic name
|
|
850
|
+
* @param options.consumerGroup - Consumer group name
|
|
851
|
+
* @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
|
|
852
|
+
* @returns Promise indicating deletion success
|
|
853
|
+
* @throws {MessageNotFoundError} When receipt handle not found
|
|
854
|
+
* @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
|
|
855
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
506
856
|
* @throws {UnauthorizedError} When authentication fails
|
|
507
|
-
* @throws {ForbiddenError} When access is denied
|
|
857
|
+
* @throws {ForbiddenError} When access is denied
|
|
508
858
|
* @throws {InternalServerError} When server encounters an error
|
|
509
859
|
*/
|
|
510
860
|
async deleteMessage(options) {
|
|
511
|
-
const { queueName, consumerGroup,
|
|
861
|
+
const { queueName, consumerGroup, receiptHandle } = options;
|
|
862
|
+
const headers = new Headers({
|
|
863
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
864
|
+
...this.customHeaders
|
|
865
|
+
});
|
|
866
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
867
|
+
if (effectiveDeploymentId) {
|
|
868
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
869
|
+
}
|
|
512
870
|
const response = await this.fetch(
|
|
513
|
-
|
|
871
|
+
this.buildUrl(
|
|
872
|
+
queueName,
|
|
873
|
+
"consumer",
|
|
874
|
+
consumerGroup,
|
|
875
|
+
"lease",
|
|
876
|
+
receiptHandle
|
|
877
|
+
),
|
|
514
878
|
{
|
|
515
879
|
method: "DELETE",
|
|
516
|
-
headers
|
|
517
|
-
Authorization: `Bearer ${await this.getToken()}`,
|
|
518
|
-
"Vqs-Queue-Name": queueName,
|
|
519
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
520
|
-
"Vqs-Ticket": ticket,
|
|
521
|
-
...this.customHeaders
|
|
522
|
-
})
|
|
880
|
+
headers
|
|
523
881
|
}
|
|
524
882
|
);
|
|
525
883
|
if (!response.ok) {
|
|
526
884
|
const errorText = await response.text();
|
|
527
885
|
if (response.status === 404) {
|
|
528
|
-
throw new MessageNotFoundError(
|
|
886
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
529
887
|
}
|
|
530
888
|
if (response.status === 409) {
|
|
531
889
|
throw new MessageNotAvailableError(
|
|
532
|
-
|
|
533
|
-
errorText || "Invalid
|
|
890
|
+
receiptHandle,
|
|
891
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
534
892
|
);
|
|
535
893
|
}
|
|
536
894
|
throwCommonHttpError(
|
|
@@ -538,53 +896,67 @@ var QueueClient = class {
|
|
|
538
896
|
response.statusText,
|
|
539
897
|
errorText,
|
|
540
898
|
"delete message",
|
|
541
|
-
"Missing or invalid
|
|
899
|
+
"Missing or invalid receipt handle"
|
|
542
900
|
);
|
|
543
901
|
}
|
|
544
902
|
return { deleted: true };
|
|
545
903
|
}
|
|
546
904
|
/**
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
* @
|
|
551
|
-
* @
|
|
552
|
-
* @
|
|
905
|
+
* Extend or change the visibility timeout of a message.
|
|
906
|
+
* Used to prevent message redelivery while still processing.
|
|
907
|
+
*
|
|
908
|
+
* @param options - Visibility options
|
|
909
|
+
* @param options.queueName - Topic name
|
|
910
|
+
* @param options.consumerGroup - Consumer group name
|
|
911
|
+
* @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
|
|
912
|
+
* @param options.visibilityTimeoutSeconds - New timeout (min: 0, max: 3600, cannot exceed message expiration)
|
|
913
|
+
* @returns Promise indicating success
|
|
914
|
+
* @throws {MessageNotFoundError} When receipt handle not found
|
|
915
|
+
* @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
|
|
916
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
553
917
|
* @throws {UnauthorizedError} When authentication fails
|
|
554
|
-
* @throws {ForbiddenError} When access is denied
|
|
918
|
+
* @throws {ForbiddenError} When access is denied
|
|
555
919
|
* @throws {InternalServerError} When server encounters an error
|
|
556
920
|
*/
|
|
557
921
|
async changeVisibility(options) {
|
|
558
922
|
const {
|
|
559
923
|
queueName,
|
|
560
924
|
consumerGroup,
|
|
561
|
-
|
|
562
|
-
ticket,
|
|
925
|
+
receiptHandle,
|
|
563
926
|
visibilityTimeoutSeconds
|
|
564
927
|
} = options;
|
|
928
|
+
const headers = new Headers({
|
|
929
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
930
|
+
"Content-Type": "application/json",
|
|
931
|
+
...this.customHeaders
|
|
932
|
+
});
|
|
933
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
934
|
+
if (effectiveDeploymentId) {
|
|
935
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
936
|
+
}
|
|
565
937
|
const response = await this.fetch(
|
|
566
|
-
|
|
938
|
+
this.buildUrl(
|
|
939
|
+
queueName,
|
|
940
|
+
"consumer",
|
|
941
|
+
consumerGroup,
|
|
942
|
+
"lease",
|
|
943
|
+
receiptHandle
|
|
944
|
+
),
|
|
567
945
|
{
|
|
568
946
|
method: "PATCH",
|
|
569
|
-
headers
|
|
570
|
-
|
|
571
|
-
"Vqs-Queue-Name": queueName,
|
|
572
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
573
|
-
"Vqs-Ticket": ticket,
|
|
574
|
-
"Vqs-Visibility-Timeout": visibilityTimeoutSeconds.toString(),
|
|
575
|
-
...this.customHeaders
|
|
576
|
-
})
|
|
947
|
+
headers,
|
|
948
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
577
949
|
}
|
|
578
950
|
);
|
|
579
951
|
if (!response.ok) {
|
|
580
952
|
const errorText = await response.text();
|
|
581
953
|
if (response.status === 404) {
|
|
582
|
-
throw new MessageNotFoundError(
|
|
954
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
583
955
|
}
|
|
584
956
|
if (response.status === 409) {
|
|
585
957
|
throw new MessageNotAvailableError(
|
|
586
|
-
|
|
587
|
-
errorText || "Invalid
|
|
958
|
+
receiptHandle,
|
|
959
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
588
960
|
);
|
|
589
961
|
}
|
|
590
962
|
throwCommonHttpError(
|
|
@@ -592,194 +964,72 @@ var QueueClient = class {
|
|
|
592
964
|
response.statusText,
|
|
593
965
|
errorText,
|
|
594
966
|
"change visibility",
|
|
595
|
-
"Missing
|
|
967
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
596
968
|
);
|
|
597
969
|
}
|
|
598
|
-
return {
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
// src/dev.ts
|
|
603
|
-
var GLOBAL_KEY = Symbol.for("@vercel/queue.devHandlers");
|
|
604
|
-
function getDevHandlerState() {
|
|
605
|
-
const g = globalThis;
|
|
606
|
-
if (!g[GLOBAL_KEY]) {
|
|
607
|
-
g[GLOBAL_KEY] = {
|
|
608
|
-
devRouteHandlers: /* @__PURE__ */ new Map(),
|
|
609
|
-
wildcardRouteHandlers: /* @__PURE__ */ new Map()
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
return g[GLOBAL_KEY];
|
|
613
|
-
}
|
|
614
|
-
var { devRouteHandlers, wildcardRouteHandlers } = getDevHandlerState();
|
|
615
|
-
function cleanupDeadRefs(key, refs) {
|
|
616
|
-
const aliveRefs = refs.filter((ref) => ref.deref() !== void 0);
|
|
617
|
-
if (aliveRefs.length === 0) {
|
|
618
|
-
wildcardRouteHandlers.delete(key);
|
|
619
|
-
} else if (aliveRefs.length < refs.length) {
|
|
620
|
-
wildcardRouteHandlers.set(key, aliveRefs);
|
|
970
|
+
return { success: true };
|
|
621
971
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
972
|
+
/**
|
|
973
|
+
* Alternative endpoint for changing message visibility timeout.
|
|
974
|
+
* Uses the /visibility path suffix and expects visibilityTimeoutSeconds in the body.
|
|
975
|
+
* Functionally equivalent to changeVisibility but follows an alternative API pattern.
|
|
976
|
+
*
|
|
977
|
+
* @param options - Options for changing visibility
|
|
978
|
+
* @returns Promise resolving to change visibility response
|
|
979
|
+
*/
|
|
980
|
+
async changeVisibilityAlt(options) {
|
|
981
|
+
const {
|
|
982
|
+
queueName,
|
|
983
|
+
consumerGroup,
|
|
984
|
+
receiptHandle,
|
|
985
|
+
visibilityTimeoutSeconds
|
|
986
|
+
} = options;
|
|
987
|
+
const headers = new Headers({
|
|
988
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
989
|
+
"Content-Type": "application/json",
|
|
990
|
+
...this.customHeaders
|
|
991
|
+
});
|
|
992
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
993
|
+
if (effectiveDeploymentId) {
|
|
994
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
643
995
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
996
|
+
const response = await this.fetch(
|
|
997
|
+
this.buildUrl(
|
|
998
|
+
queueName,
|
|
999
|
+
"consumer",
|
|
1000
|
+
consumerGroup,
|
|
1001
|
+
"lease",
|
|
1002
|
+
receiptHandle,
|
|
1003
|
+
"visibility"
|
|
1004
|
+
),
|
|
1005
|
+
{
|
|
1006
|
+
method: "PATCH",
|
|
1007
|
+
headers,
|
|
1008
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
656
1009
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if (matchesWildcardPattern(topicName, pattern)) {
|
|
663
|
-
cleanupDeadRefs(key, refs);
|
|
664
|
-
const cleanedRefs = wildcardRouteHandlers.get(key) || [];
|
|
665
|
-
for (const ref of cleanedRefs) {
|
|
666
|
-
const routeHandler = ref.deref();
|
|
667
|
-
if (routeHandler) {
|
|
668
|
-
if (!handlersMap.has(routeHandler)) {
|
|
669
|
-
handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
|
|
670
|
-
}
|
|
671
|
-
handlersMap.get(routeHandler).add(consumerGroup);
|
|
672
|
-
}
|
|
1010
|
+
);
|
|
1011
|
+
if (!response.ok) {
|
|
1012
|
+
const errorText = await response.text();
|
|
1013
|
+
if (response.status === 404) {
|
|
1014
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
673
1015
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const cloudEvent = {
|
|
680
|
-
type: "com.vercel.queue.v1beta",
|
|
681
|
-
source: `/topic/${topicName}/consumer/${consumerGroup}`,
|
|
682
|
-
id: messageId,
|
|
683
|
-
datacontenttype: "application/json",
|
|
684
|
-
data: {
|
|
685
|
-
messageId,
|
|
686
|
-
queueName: topicName,
|
|
687
|
-
consumerGroup
|
|
688
|
-
},
|
|
689
|
-
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
690
|
-
specversion: "1.0"
|
|
691
|
-
};
|
|
692
|
-
return new Request("https://localhost/api/queue/callback", {
|
|
693
|
-
method: "POST",
|
|
694
|
-
headers: {
|
|
695
|
-
"Content-Type": "application/cloudevents+json"
|
|
696
|
-
},
|
|
697
|
-
body: JSON.stringify(cloudEvent)
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
var DEV_CALLBACK_DELAY = 1e3;
|
|
701
|
-
function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
|
|
702
|
-
console.log(
|
|
703
|
-
`[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
|
|
704
|
-
);
|
|
705
|
-
setTimeout(
|
|
706
|
-
() => {
|
|
707
|
-
console.log(
|
|
708
|
-
`[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
|
|
709
|
-
);
|
|
710
|
-
triggerDevCallbacks(topicName, messageId);
|
|
711
|
-
},
|
|
712
|
-
timeoutSeconds * 1e3 + DEV_CALLBACK_DELAY
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
function triggerDevCallbacks(topicName, messageId) {
|
|
716
|
-
const handlersMap = findRouteHandlersForTopic(topicName);
|
|
717
|
-
if (handlersMap.size === 0) {
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
const consumerGroups = Array.from(
|
|
721
|
-
new Set(
|
|
722
|
-
Array.from(handlersMap.values()).flatMap((groups) => Array.from(groups))
|
|
723
|
-
)
|
|
724
|
-
);
|
|
725
|
-
console.log(
|
|
726
|
-
`[Dev Mode] Triggering local callbacks for topic "${topicName}" \u2192 consumers: ${consumerGroups.join(", ")}`
|
|
727
|
-
);
|
|
728
|
-
setTimeout(async () => {
|
|
729
|
-
for (const [routeHandler, consumerGroups2] of handlersMap.entries()) {
|
|
730
|
-
for (const consumerGroup of consumerGroups2) {
|
|
731
|
-
try {
|
|
732
|
-
const request = createMockCloudEventRequest(
|
|
733
|
-
topicName,
|
|
734
|
-
consumerGroup,
|
|
735
|
-
messageId
|
|
736
|
-
);
|
|
737
|
-
const response = await routeHandler(request);
|
|
738
|
-
if (response.ok) {
|
|
739
|
-
try {
|
|
740
|
-
const responseData = await response.json();
|
|
741
|
-
if (responseData.status === "success") {
|
|
742
|
-
console.log(
|
|
743
|
-
`[Dev Mode] Message processed for ${topicName}/${consumerGroup}`
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
} catch (jsonError) {
|
|
747
|
-
console.error(
|
|
748
|
-
`[Dev Mode] Failed to parse success response for ${topicName}/${consumerGroup}:`,
|
|
749
|
-
jsonError
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
} else {
|
|
753
|
-
try {
|
|
754
|
-
const errorData = await response.json();
|
|
755
|
-
console.error(
|
|
756
|
-
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
757
|
-
errorData.error || response.statusText
|
|
758
|
-
);
|
|
759
|
-
} catch (jsonError) {
|
|
760
|
-
console.error(
|
|
761
|
-
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
762
|
-
response.statusText
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
} catch (error) {
|
|
767
|
-
console.error(
|
|
768
|
-
`[Dev Mode] Error triggering callback for ${topicName}/${consumerGroup}:`,
|
|
769
|
-
error
|
|
770
|
-
);
|
|
771
|
-
}
|
|
1016
|
+
if (response.status === 409) {
|
|
1017
|
+
throw new MessageNotAvailableError(
|
|
1018
|
+
receiptHandle,
|
|
1019
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
1020
|
+
);
|
|
772
1021
|
}
|
|
1022
|
+
throwCommonHttpError(
|
|
1023
|
+
response.status,
|
|
1024
|
+
response.statusText,
|
|
1025
|
+
errorText,
|
|
1026
|
+
"change visibility (alt)",
|
|
1027
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
1028
|
+
);
|
|
773
1029
|
}
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
devRouteHandlers.clear();
|
|
778
|
-
wildcardRouteHandlers.clear();
|
|
779
|
-
}
|
|
780
|
-
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
781
|
-
globalThis.__clearDevHandlers = clearDevHandlers;
|
|
782
|
-
}
|
|
1030
|
+
return { success: true };
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
783
1033
|
|
|
784
1034
|
// src/consumer-group.ts
|
|
785
1035
|
var ConsumerGroup = class {
|
|
@@ -790,69 +1040,64 @@ var ConsumerGroup = class {
|
|
|
790
1040
|
refreshInterval;
|
|
791
1041
|
transport;
|
|
792
1042
|
/**
|
|
793
|
-
* Create a new ConsumerGroup instance
|
|
794
|
-
*
|
|
795
|
-
* @param
|
|
796
|
-
* @param
|
|
797
|
-
* @param
|
|
1043
|
+
* Create a new ConsumerGroup instance.
|
|
1044
|
+
*
|
|
1045
|
+
* @param client - QueueClient instance to use for API calls
|
|
1046
|
+
* @param topicName - Name of the topic to consume from (pattern: `[A-Za-z0-9_-]+`)
|
|
1047
|
+
* @param consumerGroupName - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
1048
|
+
* @param options - Optional configuration
|
|
1049
|
+
* @param options.transport - Payload serializer (default: JsonTransport)
|
|
1050
|
+
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
|
|
1051
|
+
* @param options.visibilityRefreshInterval - Lock refresh interval in seconds (default: visibilityTimeout / 3)
|
|
798
1052
|
*/
|
|
799
1053
|
constructor(client, topicName, consumerGroupName, options = {}) {
|
|
800
1054
|
this.client = client;
|
|
801
1055
|
this.topicName = topicName;
|
|
802
1056
|
this.consumerGroupName = consumerGroupName;
|
|
803
|
-
this.visibilityTimeout = options.visibilityTimeoutSeconds
|
|
804
|
-
this.refreshInterval = options.
|
|
1057
|
+
this.visibilityTimeout = options.visibilityTimeoutSeconds ?? 30;
|
|
1058
|
+
this.refreshInterval = options.visibilityRefreshInterval ?? Math.floor(this.visibilityTimeout / 3);
|
|
805
1059
|
this.transport = options.transport || new JsonTransport();
|
|
806
1060
|
}
|
|
807
1061
|
/**
|
|
808
1062
|
* Starts a background loop that periodically extends the visibility timeout for a message.
|
|
809
|
-
* This prevents the message from becoming visible to other consumers while it's being processed.
|
|
810
|
-
*
|
|
811
|
-
* The extension loop runs every `refreshInterval` seconds and updates the message's
|
|
812
|
-
* visibility timeout to `visibilityTimeout` seconds from the current time.
|
|
813
|
-
*
|
|
814
|
-
* @param messageId - The unique identifier of the message to extend visibility for
|
|
815
|
-
* @param ticket - The receipt ticket that proves ownership of the message
|
|
816
|
-
* @returns A function that when called will stop the extension loop
|
|
817
|
-
*
|
|
818
|
-
* @remarks
|
|
819
|
-
* - The first extension attempt occurs after `refreshInterval` seconds, not immediately
|
|
820
|
-
* - If an extension fails, the loop terminates with an error logged to console
|
|
821
|
-
* - The returned stop function is idempotent - calling it multiple times is safe
|
|
822
|
-
* - By default, the stop function returns immediately without waiting for in-flight
|
|
823
|
-
* - Pass `true` to the stop function to wait for any in-flight extension to complete
|
|
824
1063
|
*/
|
|
825
|
-
startVisibilityExtension(
|
|
1064
|
+
startVisibilityExtension(receiptHandle) {
|
|
826
1065
|
let isRunning = true;
|
|
1066
|
+
let isResolved = false;
|
|
827
1067
|
let resolveLifecycle;
|
|
828
1068
|
let timeoutId = null;
|
|
829
1069
|
const lifecyclePromise = new Promise((resolve) => {
|
|
830
1070
|
resolveLifecycle = resolve;
|
|
831
1071
|
});
|
|
1072
|
+
const safeResolve = () => {
|
|
1073
|
+
if (!isResolved) {
|
|
1074
|
+
isResolved = true;
|
|
1075
|
+
resolveLifecycle();
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
832
1078
|
const extend = async () => {
|
|
833
1079
|
if (!isRunning) {
|
|
834
|
-
|
|
1080
|
+
safeResolve();
|
|
835
1081
|
return;
|
|
836
1082
|
}
|
|
837
1083
|
try {
|
|
838
1084
|
await this.client.changeVisibility({
|
|
839
1085
|
queueName: this.topicName,
|
|
840
1086
|
consumerGroup: this.consumerGroupName,
|
|
841
|
-
|
|
842
|
-
ticket,
|
|
1087
|
+
receiptHandle,
|
|
843
1088
|
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
844
1089
|
});
|
|
845
1090
|
if (isRunning) {
|
|
846
1091
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
847
1092
|
} else {
|
|
848
|
-
|
|
1093
|
+
safeResolve();
|
|
849
1094
|
}
|
|
850
1095
|
} catch (error) {
|
|
851
1096
|
console.error(
|
|
852
|
-
`Failed to extend visibility for
|
|
1097
|
+
`Failed to extend visibility for receipt handle ${receiptHandle}:`,
|
|
853
1098
|
error
|
|
854
1099
|
);
|
|
855
|
-
|
|
1100
|
+
safeResolve();
|
|
856
1101
|
}
|
|
857
1102
|
};
|
|
858
1103
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
@@ -865,22 +1110,14 @@ var ConsumerGroup = class {
|
|
|
865
1110
|
if (waitForCompletion) {
|
|
866
1111
|
await lifecyclePromise;
|
|
867
1112
|
} else {
|
|
868
|
-
|
|
1113
|
+
safeResolve();
|
|
869
1114
|
}
|
|
870
1115
|
};
|
|
871
1116
|
}
|
|
872
|
-
/**
|
|
873
|
-
* Process a single message with the given handler
|
|
874
|
-
* @param message The message to process
|
|
875
|
-
* @param handler Function to process the message
|
|
876
|
-
*/
|
|
877
1117
|
async processMessage(message, handler) {
|
|
878
|
-
const stopExtension = this.startVisibilityExtension(
|
|
879
|
-
message.messageId,
|
|
880
|
-
message.ticket
|
|
881
|
-
);
|
|
1118
|
+
const stopExtension = this.startVisibilityExtension(message.receiptHandle);
|
|
882
1119
|
try {
|
|
883
|
-
|
|
1120
|
+
await handler(message.payload, {
|
|
884
1121
|
messageId: message.messageId,
|
|
885
1122
|
deliveryCount: message.deliveryCount,
|
|
886
1123
|
createdAt: message.createdAt,
|
|
@@ -888,29 +1125,11 @@ var ConsumerGroup = class {
|
|
|
888
1125
|
consumerGroup: this.consumerGroupName
|
|
889
1126
|
});
|
|
890
1127
|
await stopExtension();
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
ticket: message.ticket,
|
|
897
|
-
visibilityTimeoutSeconds: result.timeoutSeconds
|
|
898
|
-
});
|
|
899
|
-
if (isDevMode()) {
|
|
900
|
-
scheduleDevTimeout(
|
|
901
|
-
this.topicName,
|
|
902
|
-
message.messageId,
|
|
903
|
-
result.timeoutSeconds
|
|
904
|
-
);
|
|
905
|
-
}
|
|
906
|
-
} else {
|
|
907
|
-
await this.client.deleteMessage({
|
|
908
|
-
queueName: this.topicName,
|
|
909
|
-
consumerGroup: this.consumerGroupName,
|
|
910
|
-
messageId: message.messageId,
|
|
911
|
-
ticket: message.ticket
|
|
912
|
-
});
|
|
913
|
-
}
|
|
1128
|
+
await this.client.deleteMessage({
|
|
1129
|
+
queueName: this.topicName,
|
|
1130
|
+
consumerGroup: this.consumerGroupName,
|
|
1131
|
+
receiptHandle: message.receiptHandle
|
|
1132
|
+
});
|
|
914
1133
|
} catch (error) {
|
|
915
1134
|
await stopExtension();
|
|
916
1135
|
if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
|
|
@@ -925,36 +1144,16 @@ var ConsumerGroup = class {
|
|
|
925
1144
|
}
|
|
926
1145
|
async consume(handler, options) {
|
|
927
1146
|
if (options?.messageId) {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
);
|
|
939
|
-
await this.processMessage(
|
|
940
|
-
response.message,
|
|
941
|
-
handler
|
|
942
|
-
);
|
|
943
|
-
} else {
|
|
944
|
-
const response = await this.client.receiveMessageById(
|
|
945
|
-
{
|
|
946
|
-
queueName: this.topicName,
|
|
947
|
-
consumerGroup: this.consumerGroupName,
|
|
948
|
-
messageId: options.messageId,
|
|
949
|
-
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
950
|
-
},
|
|
951
|
-
this.transport
|
|
952
|
-
);
|
|
953
|
-
await this.processMessage(
|
|
954
|
-
response.message,
|
|
955
|
-
handler
|
|
956
|
-
);
|
|
957
|
-
}
|
|
1147
|
+
const response = await this.client.receiveMessageById(
|
|
1148
|
+
{
|
|
1149
|
+
queueName: this.topicName,
|
|
1150
|
+
consumerGroup: this.consumerGroupName,
|
|
1151
|
+
messageId: options.messageId,
|
|
1152
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1153
|
+
},
|
|
1154
|
+
this.transport
|
|
1155
|
+
);
|
|
1156
|
+
await this.processMessage(response.message, handler);
|
|
958
1157
|
} else {
|
|
959
1158
|
let messageFound = false;
|
|
960
1159
|
for await (const message of this.client.receiveMessages(
|
|
@@ -1022,7 +1221,7 @@ var Topic = class {
|
|
|
1022
1221
|
payload,
|
|
1023
1222
|
idempotencyKey: options?.idempotencyKey,
|
|
1024
1223
|
retentionSeconds: options?.retentionSeconds,
|
|
1025
|
-
|
|
1224
|
+
delaySeconds: options?.delaySeconds
|
|
1026
1225
|
},
|
|
1027
1226
|
this.transport
|
|
1028
1227
|
);
|
|
@@ -1132,7 +1331,7 @@ async function parseCallback(request) {
|
|
|
1132
1331
|
messageId
|
|
1133
1332
|
};
|
|
1134
1333
|
}
|
|
1135
|
-
function createCallbackHandler(handlers, client) {
|
|
1334
|
+
function createCallbackHandler(handlers, client, visibilityTimeoutSeconds) {
|
|
1136
1335
|
for (const topicPattern in handlers) {
|
|
1137
1336
|
if (topicPattern.includes("*")) {
|
|
1138
1337
|
if (!validateWildcardPattern(topicPattern)) {
|
|
@@ -1168,7 +1367,10 @@ function createCallbackHandler(handlers, client) {
|
|
|
1168
1367
|
);
|
|
1169
1368
|
}
|
|
1170
1369
|
const topic = new Topic(client, queueName);
|
|
1171
|
-
const cg = topic.consumerGroup(
|
|
1370
|
+
const cg = topic.consumerGroup(
|
|
1371
|
+
consumerGroup,
|
|
1372
|
+
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : void 0
|
|
1373
|
+
);
|
|
1172
1374
|
await cg.consume(consumerGroupHandler, { messageId });
|
|
1173
1375
|
return Response.json({ status: "success" });
|
|
1174
1376
|
} catch (error) {
|
|
@@ -1182,13 +1384,14 @@ function createCallbackHandler(handlers, client) {
|
|
|
1182
1384
|
);
|
|
1183
1385
|
}
|
|
1184
1386
|
};
|
|
1185
|
-
if (isDevMode()) {
|
|
1186
|
-
registerDevRouteHandler(routeHandler, handlers);
|
|
1187
|
-
}
|
|
1188
1387
|
return routeHandler;
|
|
1189
1388
|
}
|
|
1190
|
-
function handleCallback(handlers,
|
|
1191
|
-
return createCallbackHandler(
|
|
1389
|
+
function handleCallback(handlers, options) {
|
|
1390
|
+
return createCallbackHandler(
|
|
1391
|
+
handlers,
|
|
1392
|
+
options?.client || new QueueClient(),
|
|
1393
|
+
options?.visibilityTimeoutSeconds
|
|
1394
|
+
);
|
|
1192
1395
|
}
|
|
1193
1396
|
|
|
1194
1397
|
// src/factory.ts
|
|
@@ -1201,12 +1404,12 @@ async function send(topicName, payload, options) {
|
|
|
1201
1404
|
payload,
|
|
1202
1405
|
idempotencyKey: options?.idempotencyKey,
|
|
1203
1406
|
retentionSeconds: options?.retentionSeconds,
|
|
1204
|
-
|
|
1407
|
+
delaySeconds: options?.delaySeconds
|
|
1205
1408
|
},
|
|
1206
1409
|
transport
|
|
1207
1410
|
);
|
|
1208
1411
|
if (isDevMode()) {
|
|
1209
|
-
triggerDevCallbacks(topicName, result.messageId);
|
|
1412
|
+
triggerDevCallbacks(topicName, result.messageId, options?.delaySeconds);
|
|
1210
1413
|
}
|
|
1211
1414
|
return { messageId: result.messageId };
|
|
1212
1415
|
}
|
|
@@ -1214,22 +1417,10 @@ async function receive(topicName, consumerGroup, handler, options) {
|
|
|
1214
1417
|
const transport = options?.transport || new JsonTransport();
|
|
1215
1418
|
const client = options?.client || new QueueClient();
|
|
1216
1419
|
const topic = new Topic(client, topicName, transport);
|
|
1217
|
-
const {
|
|
1218
|
-
messageId,
|
|
1219
|
-
skipPayload,
|
|
1220
|
-
client: _,
|
|
1221
|
-
...consumerGroupOptions
|
|
1222
|
-
} = options || {};
|
|
1420
|
+
const { messageId, client: _, ...consumerGroupOptions } = options || {};
|
|
1223
1421
|
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
1224
1422
|
if (messageId) {
|
|
1225
|
-
|
|
1226
|
-
return consumer.consume(handler, {
|
|
1227
|
-
messageId,
|
|
1228
|
-
skipPayload: true
|
|
1229
|
-
});
|
|
1230
|
-
} else {
|
|
1231
|
-
return consumer.consume(handler, { messageId });
|
|
1232
|
-
}
|
|
1423
|
+
return consumer.consume(handler, { messageId });
|
|
1233
1424
|
} else {
|
|
1234
1425
|
return consumer.consume(handler);
|
|
1235
1426
|
}
|
|
@@ -1263,33 +1454,54 @@ var Client = class {
|
|
|
1263
1454
|
});
|
|
1264
1455
|
}
|
|
1265
1456
|
/**
|
|
1266
|
-
* Create a callback handler for processing queue messages
|
|
1267
|
-
* Returns a Next.js route handler function that routes messages to appropriate handlers
|
|
1268
|
-
*
|
|
1457
|
+
* Create a callback handler for processing queue messages.
|
|
1458
|
+
* Returns a Next.js route handler function that routes messages to appropriate handlers.
|
|
1459
|
+
*
|
|
1460
|
+
* @param handlers - Object with topic-specific handlers organized by consumer groups
|
|
1461
|
+
* @param options - Optional configuration
|
|
1462
|
+
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
|
|
1269
1463
|
* @returns A Next.js route handler function
|
|
1270
1464
|
*
|
|
1271
1465
|
* @example
|
|
1272
1466
|
* ```typescript
|
|
1467
|
+
* // Basic usage
|
|
1273
1468
|
* export const POST = client.handleCallback({
|
|
1274
1469
|
* "user-events": {
|
|
1275
1470
|
* "welcome": (user, metadata) => console.log("Welcoming user", user),
|
|
1276
1471
|
* "analytics": (user, metadata) => console.log("Tracking user", user),
|
|
1277
1472
|
* },
|
|
1278
1473
|
* });
|
|
1474
|
+
*
|
|
1475
|
+
* // With custom visibility timeout
|
|
1476
|
+
* export const POST = client.handleCallback({
|
|
1477
|
+
* "video-processing": {
|
|
1478
|
+
* "transcode": async (video) => await transcodeVideo(video),
|
|
1479
|
+
* },
|
|
1480
|
+
* }, {
|
|
1481
|
+
* visibilityTimeoutSeconds: 300, // 5 minutes for long operations
|
|
1482
|
+
* });
|
|
1279
1483
|
* ```
|
|
1280
1484
|
*/
|
|
1281
|
-
handleCallback(handlers) {
|
|
1282
|
-
return handleCallback(handlers,
|
|
1485
|
+
handleCallback(handlers, options) {
|
|
1486
|
+
return handleCallback(handlers, {
|
|
1487
|
+
...options,
|
|
1488
|
+
client: this.client
|
|
1489
|
+
});
|
|
1283
1490
|
}
|
|
1284
1491
|
};
|
|
1285
1492
|
export {
|
|
1286
1493
|
BadRequestError,
|
|
1287
1494
|
BufferTransport,
|
|
1288
1495
|
Client,
|
|
1496
|
+
ConcurrencyLimitError,
|
|
1497
|
+
ConsumerDiscoveryError,
|
|
1498
|
+
ConsumerRegistryNotConfiguredError,
|
|
1499
|
+
DuplicateMessageError,
|
|
1289
1500
|
ForbiddenError,
|
|
1290
1501
|
InternalServerError,
|
|
1291
1502
|
InvalidLimitError,
|
|
1292
1503
|
JsonTransport,
|
|
1504
|
+
MessageAlreadyProcessedError,
|
|
1293
1505
|
MessageCorruptedError,
|
|
1294
1506
|
MessageLockedError,
|
|
1295
1507
|
MessageNotAvailableError,
|