@vercel/queue 0.0.0-alpha.33 → 0.0.0-alpha.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -52
- package/dist/index.d.mts +17 -95
- package/dist/index.d.ts +17 -95
- package/dist/index.js +551 -440
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +536 -440
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs-pages.d.mts +1 -1
- package/dist/nextjs-pages.d.ts +1 -1
- package/dist/nextjs-pages.js +570 -466
- package/dist/nextjs-pages.js.map +1 -1
- package/dist/nextjs-pages.mjs +560 -466
- package/dist/nextjs-pages.mjs.map +1 -1
- package/dist/{types-Dw29Fr9y.d.mts → types-BHtRP_i_.d.mts} +74 -43
- package/dist/{types-Dw29Fr9y.d.ts → types-BHtRP_i_.d.ts} +74 -43
- package/package.json +2 -6
- package/bin/local-discover.js +0 -196
package/dist/index.mjs
CHANGED
|
@@ -64,8 +64,9 @@ var StreamTransport = class {
|
|
|
64
64
|
// src/client.ts
|
|
65
65
|
import { parseMultipartStream } from "mixpart";
|
|
66
66
|
|
|
67
|
-
// src/
|
|
68
|
-
import
|
|
67
|
+
// src/dev.ts
|
|
68
|
+
import * as fs from "fs";
|
|
69
|
+
import * as path from "path";
|
|
69
70
|
|
|
70
71
|
// src/types.ts
|
|
71
72
|
var MessageNotFoundError = class extends Error {
|
|
@@ -135,6 +136,263 @@ var InvalidLimitError = class extends Error {
|
|
|
135
136
|
this.name = "InvalidLimitError";
|
|
136
137
|
}
|
|
137
138
|
};
|
|
139
|
+
var MessageAlreadyProcessedError = class extends Error {
|
|
140
|
+
constructor(messageId) {
|
|
141
|
+
super(`Message ${messageId} has already been processed`);
|
|
142
|
+
this.name = "MessageAlreadyProcessedError";
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
var ConcurrencyLimitError = class extends Error {
|
|
146
|
+
currentInflight;
|
|
147
|
+
maxConcurrency;
|
|
148
|
+
constructor(message = "Concurrency limit exceeded", currentInflight, maxConcurrency) {
|
|
149
|
+
super(message);
|
|
150
|
+
this.name = "ConcurrencyLimitError";
|
|
151
|
+
this.currentInflight = currentInflight;
|
|
152
|
+
this.maxConcurrency = maxConcurrency;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var DuplicateMessageError = class extends Error {
|
|
156
|
+
idempotencyKey;
|
|
157
|
+
constructor(message, idempotencyKey) {
|
|
158
|
+
super(message);
|
|
159
|
+
this.name = "DuplicateMessageError";
|
|
160
|
+
this.idempotencyKey = idempotencyKey;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var ConsumerDiscoveryError = class extends Error {
|
|
164
|
+
deploymentId;
|
|
165
|
+
constructor(message, deploymentId) {
|
|
166
|
+
super(message);
|
|
167
|
+
this.name = "ConsumerDiscoveryError";
|
|
168
|
+
this.deploymentId = deploymentId;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var ConsumerRegistryNotConfiguredError = class extends Error {
|
|
172
|
+
constructor(message = "Consumer registry not configured") {
|
|
173
|
+
super(message);
|
|
174
|
+
this.name = "ConsumerRegistryNotConfiguredError";
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/dev.ts
|
|
179
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
180
|
+
function filePathToUrlPath(filePath) {
|
|
181
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|js|tsx|jsx)$/, "").replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
182
|
+
if (!urlPath.startsWith("/")) {
|
|
183
|
+
urlPath = "/" + urlPath;
|
|
184
|
+
}
|
|
185
|
+
return urlPath;
|
|
186
|
+
}
|
|
187
|
+
function getDevRouteMappings() {
|
|
188
|
+
const g = globalThis;
|
|
189
|
+
if (ROUTE_MAPPINGS_KEY in g) {
|
|
190
|
+
return g[ROUTE_MAPPINGS_KEY] ?? null;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const vercelJsonPath = path.join(process.cwd(), "vercel.json");
|
|
194
|
+
if (!fs.existsSync(vercelJsonPath)) {
|
|
195
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const vercelJson = JSON.parse(fs.readFileSync(vercelJsonPath, "utf-8"));
|
|
199
|
+
if (!vercelJson.functions) {
|
|
200
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const mappings = [];
|
|
204
|
+
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
205
|
+
if (!config.experimentalTriggers) continue;
|
|
206
|
+
for (const trigger of config.experimentalTriggers) {
|
|
207
|
+
if (trigger.type?.startsWith("queue/") && trigger.topic && trigger.consumer) {
|
|
208
|
+
mappings.push({
|
|
209
|
+
urlPath: filePathToUrlPath(filePath),
|
|
210
|
+
topic: trigger.topic,
|
|
211
|
+
consumer: trigger.consumer
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
217
|
+
return g[ROUTE_MAPPINGS_KEY];
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn("[Dev Mode] Failed to read vercel.json:", error);
|
|
220
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function findMatchingRoutes(topicName) {
|
|
225
|
+
const mappings = getDevRouteMappings();
|
|
226
|
+
if (!mappings) {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
return mappings.filter((mapping) => {
|
|
230
|
+
if (mapping.topic.includes("*")) {
|
|
231
|
+
return matchesWildcardPattern(topicName, mapping.topic);
|
|
232
|
+
}
|
|
233
|
+
return mapping.topic === topicName;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function isDevMode() {
|
|
237
|
+
return process.env.NODE_ENV === "development";
|
|
238
|
+
}
|
|
239
|
+
var DEV_VISIBILITY_POLL_INTERVAL = 50;
|
|
240
|
+
var DEV_VISIBILITY_MAX_WAIT = 5e3;
|
|
241
|
+
var DEV_VISIBILITY_BACKOFF_MULTIPLIER = 2;
|
|
242
|
+
async function waitForMessageVisibility(topicName, consumerGroup, messageId) {
|
|
243
|
+
const client = new QueueClient();
|
|
244
|
+
const transport = new JsonTransport();
|
|
245
|
+
let elapsed = 0;
|
|
246
|
+
let interval = DEV_VISIBILITY_POLL_INTERVAL;
|
|
247
|
+
while (elapsed < DEV_VISIBILITY_MAX_WAIT) {
|
|
248
|
+
try {
|
|
249
|
+
await client.receiveMessageById(
|
|
250
|
+
{
|
|
251
|
+
queueName: topicName,
|
|
252
|
+
consumerGroup,
|
|
253
|
+
messageId,
|
|
254
|
+
visibilityTimeoutSeconds: 0
|
|
255
|
+
},
|
|
256
|
+
transport
|
|
257
|
+
);
|
|
258
|
+
return true;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error instanceof MessageNotFoundError) {
|
|
261
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
262
|
+
elapsed += interval;
|
|
263
|
+
interval = Math.min(
|
|
264
|
+
interval * DEV_VISIBILITY_BACKOFF_MULTIPLIER,
|
|
265
|
+
DEV_VISIBILITY_MAX_WAIT - elapsed
|
|
266
|
+
);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (error instanceof MessageAlreadyProcessedError) {
|
|
270
|
+
console.log(
|
|
271
|
+
`[Dev Mode] Message already processed: topic="${topicName}" messageId="${messageId}"`
|
|
272
|
+
);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
console.error(
|
|
276
|
+
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
277
|
+
error
|
|
278
|
+
);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
console.warn(
|
|
283
|
+
`[Dev Mode] Message visibility timeout after ${DEV_VISIBILITY_MAX_WAIT}ms: topic="${topicName}" messageId="${messageId}"`
|
|
284
|
+
);
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
function triggerDevCallbacks(topicName, messageId, delaySeconds) {
|
|
288
|
+
if (delaySeconds && delaySeconds > 0) {
|
|
289
|
+
console.log(
|
|
290
|
+
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
291
|
+
);
|
|
292
|
+
setTimeout(() => {
|
|
293
|
+
triggerDevCallbacks(topicName, messageId);
|
|
294
|
+
}, delaySeconds * 1e3);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
console.log(
|
|
298
|
+
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
299
|
+
);
|
|
300
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
301
|
+
if (matchingRoutes.length === 0) {
|
|
302
|
+
console.log(
|
|
303
|
+
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
304
|
+
);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
308
|
+
console.log(
|
|
309
|
+
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
310
|
+
);
|
|
311
|
+
(async () => {
|
|
312
|
+
const firstRoute = matchingRoutes[0];
|
|
313
|
+
const isVisible = await waitForMessageVisibility(
|
|
314
|
+
topicName,
|
|
315
|
+
firstRoute.consumer,
|
|
316
|
+
messageId
|
|
317
|
+
);
|
|
318
|
+
if (!isVisible) {
|
|
319
|
+
console.warn(
|
|
320
|
+
`[Dev Mode] Skipping callbacks - message not visible: topic="${topicName}" messageId="${messageId}"`
|
|
321
|
+
);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const port = process.env.PORT || 3e3;
|
|
325
|
+
const baseUrl = `http://localhost:${port}`;
|
|
326
|
+
for (const route of matchingRoutes) {
|
|
327
|
+
const url = `${baseUrl}${route.urlPath}`;
|
|
328
|
+
console.log(
|
|
329
|
+
`[Dev Mode] Invoking handler: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`
|
|
330
|
+
);
|
|
331
|
+
const cloudEvent = {
|
|
332
|
+
type: "com.vercel.queue.v1beta",
|
|
333
|
+
source: `/topic/${topicName}/consumer/${route.consumer}`,
|
|
334
|
+
id: messageId,
|
|
335
|
+
datacontenttype: "application/json",
|
|
336
|
+
data: {
|
|
337
|
+
messageId,
|
|
338
|
+
queueName: topicName,
|
|
339
|
+
consumerGroup: route.consumer
|
|
340
|
+
},
|
|
341
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
342
|
+
specversion: "1.0"
|
|
343
|
+
};
|
|
344
|
+
try {
|
|
345
|
+
const response = await fetch(url, {
|
|
346
|
+
method: "POST",
|
|
347
|
+
headers: {
|
|
348
|
+
"Content-Type": "application/cloudevents+json"
|
|
349
|
+
},
|
|
350
|
+
body: JSON.stringify(cloudEvent)
|
|
351
|
+
});
|
|
352
|
+
if (response.ok) {
|
|
353
|
+
try {
|
|
354
|
+
const responseData = await response.json();
|
|
355
|
+
if (responseData.status === "success") {
|
|
356
|
+
console.log(
|
|
357
|
+
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
} catch {
|
|
361
|
+
console.warn(
|
|
362
|
+
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
try {
|
|
367
|
+
const errorData = await response.json();
|
|
368
|
+
console.error(
|
|
369
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
370
|
+
);
|
|
371
|
+
} catch {
|
|
372
|
+
console.error(
|
|
373
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.error(
|
|
379
|
+
`[Dev Mode] \u2717 HTTP request failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`,
|
|
380
|
+
error
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
})();
|
|
385
|
+
}
|
|
386
|
+
function clearDevRouteMappings() {
|
|
387
|
+
const g = globalThis;
|
|
388
|
+
delete g[ROUTE_MAPPINGS_KEY];
|
|
389
|
+
}
|
|
390
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
391
|
+
globalThis.__clearDevRouteMappings = clearDevRouteMappings;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/oidc.ts
|
|
395
|
+
import { getVercelOidcToken } from "@vercel/oidc";
|
|
138
396
|
|
|
139
397
|
// src/client.ts
|
|
140
398
|
function isDebugEnabled() {
|
|
@@ -151,14 +409,6 @@ async function consumeStream(stream) {
|
|
|
151
409
|
reader.releaseLock();
|
|
152
410
|
}
|
|
153
411
|
}
|
|
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
412
|
function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters") {
|
|
163
413
|
if (status === 400) {
|
|
164
414
|
throw new BadRequestError(errorText || badRequestDefault);
|
|
@@ -181,8 +431,8 @@ function parseQueueHeaders(headers) {
|
|
|
181
431
|
const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
|
|
182
432
|
const timestamp = headers.get("Vqs-Timestamp");
|
|
183
433
|
const contentType = headers.get("Content-Type") || "application/octet-stream";
|
|
184
|
-
const
|
|
185
|
-
if (!messageId || !timestamp || !
|
|
434
|
+
const receiptHandle = headers.get("Vqs-Receipt-Handle");
|
|
435
|
+
if (!messageId || !timestamp || !receiptHandle) {
|
|
186
436
|
return null;
|
|
187
437
|
}
|
|
188
438
|
const deliveryCount = parseInt(deliveryCountStr, 10);
|
|
@@ -194,7 +444,7 @@ function parseQueueHeaders(headers) {
|
|
|
194
444
|
deliveryCount,
|
|
195
445
|
createdAt: new Date(timestamp),
|
|
196
446
|
contentType,
|
|
197
|
-
|
|
447
|
+
receiptHandle
|
|
198
448
|
};
|
|
199
449
|
}
|
|
200
450
|
var QueueClient = class {
|
|
@@ -202,15 +452,30 @@ var QueueClient = class {
|
|
|
202
452
|
basePath;
|
|
203
453
|
customHeaders;
|
|
204
454
|
providedToken;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* @param options QueueClient configuration options
|
|
208
|
-
*/
|
|
455
|
+
defaultDeploymentId;
|
|
456
|
+
pinToDeployment;
|
|
209
457
|
constructor(options = {}) {
|
|
210
458
|
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/
|
|
459
|
+
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v3/topic";
|
|
212
460
|
this.customHeaders = options.headers || {};
|
|
213
461
|
this.providedToken = options.token;
|
|
462
|
+
this.defaultDeploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
|
|
463
|
+
this.pinToDeployment = options.pinToDeployment ?? true;
|
|
464
|
+
}
|
|
465
|
+
getSendDeploymentId() {
|
|
466
|
+
if (isDevMode()) {
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
if (this.pinToDeployment) {
|
|
470
|
+
return this.defaultDeploymentId;
|
|
471
|
+
}
|
|
472
|
+
return void 0;
|
|
473
|
+
}
|
|
474
|
+
getConsumeDeploymentId() {
|
|
475
|
+
if (isDevMode()) {
|
|
476
|
+
return void 0;
|
|
477
|
+
}
|
|
478
|
+
return this.defaultDeploymentId;
|
|
214
479
|
}
|
|
215
480
|
async getToken() {
|
|
216
481
|
if (this.providedToken) {
|
|
@@ -224,10 +489,12 @@ var QueueClient = class {
|
|
|
224
489
|
}
|
|
225
490
|
return token;
|
|
226
491
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
492
|
+
buildUrl(queueName, ...pathSegments) {
|
|
493
|
+
const encodedQueue = encodeURIComponent(queueName);
|
|
494
|
+
const segments = pathSegments.map((s) => encodeURIComponent(s));
|
|
495
|
+
const path2 = segments.length > 0 ? "/" + segments.join("/") : "";
|
|
496
|
+
return `${this.baseUrl}${this.basePath}/${encodedQueue}${path2}`;
|
|
497
|
+
}
|
|
231
498
|
async fetch(url, init) {
|
|
232
499
|
const method = init.method || "GET";
|
|
233
500
|
if (isDebugEnabled()) {
|
|
@@ -263,25 +530,20 @@ var QueueClient = class {
|
|
|
263
530
|
}
|
|
264
531
|
return response;
|
|
265
532
|
}
|
|
266
|
-
/**
|
|
267
|
-
* Send a message to a queue
|
|
268
|
-
* @param options Send message options
|
|
269
|
-
* @param transport Serializer/deserializer for the payload
|
|
270
|
-
* @returns Promise with the message ID
|
|
271
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
272
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
273
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
274
|
-
* @throws {InternalServerError} When server encounters an error
|
|
275
|
-
*/
|
|
276
533
|
async sendMessage(options, transport) {
|
|
277
|
-
const {
|
|
534
|
+
const {
|
|
535
|
+
queueName,
|
|
536
|
+
payload,
|
|
537
|
+
idempotencyKey,
|
|
538
|
+
retentionSeconds,
|
|
539
|
+
delaySeconds
|
|
540
|
+
} = options;
|
|
278
541
|
const headers = new Headers({
|
|
279
542
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
280
|
-
"Vqs-Queue-Name": queueName,
|
|
281
543
|
"Content-Type": transport.contentType,
|
|
282
544
|
...this.customHeaders
|
|
283
545
|
});
|
|
284
|
-
const deploymentId =
|
|
546
|
+
const deploymentId = this.getSendDeploymentId();
|
|
285
547
|
if (deploymentId) {
|
|
286
548
|
headers.set("Vqs-Deployment-Id", deploymentId);
|
|
287
549
|
}
|
|
@@ -291,8 +553,12 @@ var QueueClient = class {
|
|
|
291
553
|
if (retentionSeconds !== void 0) {
|
|
292
554
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
293
555
|
}
|
|
294
|
-
|
|
295
|
-
|
|
556
|
+
if (delaySeconds !== void 0) {
|
|
557
|
+
headers.set("Vqs-Delay-Seconds", delaySeconds.toString());
|
|
558
|
+
}
|
|
559
|
+
const serialized = transport.serialize(payload);
|
|
560
|
+
const body = Buffer.isBuffer(serialized) ? new Uint8Array(serialized) : serialized;
|
|
561
|
+
const response = await this.fetch(this.buildUrl(queueName), {
|
|
296
562
|
method: "POST",
|
|
297
563
|
body,
|
|
298
564
|
headers
|
|
@@ -300,7 +566,21 @@ var QueueClient = class {
|
|
|
300
566
|
if (!response.ok) {
|
|
301
567
|
const errorText = await response.text();
|
|
302
568
|
if (response.status === 409) {
|
|
303
|
-
throw new
|
|
569
|
+
throw new DuplicateMessageError(
|
|
570
|
+
errorText || "Duplicate idempotency key detected",
|
|
571
|
+
idempotencyKey
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
if (response.status === 502) {
|
|
575
|
+
throw new ConsumerDiscoveryError(
|
|
576
|
+
errorText || "Consumer discovery failed",
|
|
577
|
+
deploymentId
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
if (response.status === 503) {
|
|
581
|
+
throw new ConsumerRegistryNotConfiguredError(
|
|
582
|
+
errorText || "Consumer registry not configured"
|
|
583
|
+
);
|
|
304
584
|
}
|
|
305
585
|
throwCommonHttpError(
|
|
306
586
|
response.status,
|
|
@@ -312,53 +592,60 @@ var QueueClient = class {
|
|
|
312
592
|
const responseData = await response.json();
|
|
313
593
|
return responseData;
|
|
314
594
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Receive messages from a queue
|
|
317
|
-
* @param options Receive messages options
|
|
318
|
-
* @param transport Serializer/deserializer for the payload
|
|
319
|
-
* @returns AsyncGenerator that yields messages as they arrive
|
|
320
|
-
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
321
|
-
* @throws {QueueEmptyError} When no messages are available (204)
|
|
322
|
-
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
323
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
324
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
325
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
326
|
-
* @throws {InternalServerError} When server encounters an error
|
|
327
|
-
*/
|
|
328
595
|
async *receiveMessages(options, transport) {
|
|
329
|
-
const {
|
|
596
|
+
const {
|
|
597
|
+
queueName,
|
|
598
|
+
consumerGroup,
|
|
599
|
+
visibilityTimeoutSeconds,
|
|
600
|
+
limit,
|
|
601
|
+
maxConcurrency
|
|
602
|
+
} = options;
|
|
330
603
|
if (limit !== void 0 && (limit < 1 || limit > 10)) {
|
|
331
604
|
throw new InvalidLimitError(limit);
|
|
332
605
|
}
|
|
333
606
|
const headers = new Headers({
|
|
334
607
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
335
|
-
"Vqs-Queue-Name": queueName,
|
|
336
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
337
608
|
Accept: "multipart/mixed",
|
|
338
609
|
...this.customHeaders
|
|
339
610
|
});
|
|
340
611
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
341
612
|
headers.set(
|
|
342
|
-
"Vqs-Visibility-Timeout",
|
|
613
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
343
614
|
visibilityTimeoutSeconds.toString()
|
|
344
615
|
);
|
|
345
616
|
}
|
|
346
617
|
if (limit !== void 0) {
|
|
347
|
-
headers.set("Vqs-
|
|
618
|
+
headers.set("Vqs-Max-Messages", limit.toString());
|
|
348
619
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
620
|
+
if (maxConcurrency !== void 0) {
|
|
621
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
622
|
+
}
|
|
623
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
624
|
+
if (effectiveDeploymentId) {
|
|
625
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
626
|
+
}
|
|
627
|
+
const response = await this.fetch(
|
|
628
|
+
this.buildUrl(queueName, "consumer", consumerGroup),
|
|
629
|
+
{
|
|
630
|
+
method: "POST",
|
|
631
|
+
headers
|
|
632
|
+
}
|
|
633
|
+
);
|
|
353
634
|
if (response.status === 204) {
|
|
354
635
|
throw new QueueEmptyError(queueName, consumerGroup);
|
|
355
636
|
}
|
|
356
637
|
if (!response.ok) {
|
|
357
638
|
const errorText = await response.text();
|
|
358
|
-
if (response.status ===
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
639
|
+
if (response.status === 429) {
|
|
640
|
+
let errorData = {};
|
|
641
|
+
try {
|
|
642
|
+
errorData = JSON.parse(errorText);
|
|
643
|
+
} catch {
|
|
644
|
+
}
|
|
645
|
+
throw new ConcurrencyLimitError(
|
|
646
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
647
|
+
errorData.currentInflight,
|
|
648
|
+
errorData.maxConcurrency
|
|
362
649
|
);
|
|
363
650
|
}
|
|
364
651
|
throwCommonHttpError(
|
|
@@ -396,28 +683,30 @@ var QueueClient = class {
|
|
|
396
683
|
consumerGroup,
|
|
397
684
|
messageId,
|
|
398
685
|
visibilityTimeoutSeconds,
|
|
399
|
-
|
|
686
|
+
maxConcurrency
|
|
400
687
|
} = options;
|
|
401
688
|
const headers = new Headers({
|
|
402
689
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
403
|
-
"Vqs-Queue-Name": queueName,
|
|
404
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
405
690
|
Accept: "multipart/mixed",
|
|
406
691
|
...this.customHeaders
|
|
407
692
|
});
|
|
408
693
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
409
694
|
headers.set(
|
|
410
|
-
"Vqs-Visibility-Timeout",
|
|
695
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
411
696
|
visibilityTimeoutSeconds.toString()
|
|
412
697
|
);
|
|
413
698
|
}
|
|
414
|
-
if (
|
|
415
|
-
headers.set("Vqs-
|
|
699
|
+
if (maxConcurrency !== void 0) {
|
|
700
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
701
|
+
}
|
|
702
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
703
|
+
if (effectiveDeploymentId) {
|
|
704
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
416
705
|
}
|
|
417
706
|
const response = await this.fetch(
|
|
418
|
-
|
|
707
|
+
this.buildUrl(queueName, "consumer", consumerGroup, "id", messageId),
|
|
419
708
|
{
|
|
420
|
-
method: "
|
|
709
|
+
method: "POST",
|
|
421
710
|
headers
|
|
422
711
|
}
|
|
423
712
|
);
|
|
@@ -427,12 +716,32 @@ var QueueClient = class {
|
|
|
427
716
|
throw new MessageNotFoundError(messageId);
|
|
428
717
|
}
|
|
429
718
|
if (response.status === 409) {
|
|
719
|
+
let errorData = {};
|
|
720
|
+
try {
|
|
721
|
+
errorData = JSON.parse(errorText);
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
if (errorData.originalMessageId) {
|
|
725
|
+
throw new MessageNotAvailableError(
|
|
726
|
+
messageId,
|
|
727
|
+
`This message was a duplicate - use originalMessageId: ${errorData.originalMessageId}`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
430
730
|
throw new MessageNotAvailableError(messageId);
|
|
431
731
|
}
|
|
432
|
-
if (response.status ===
|
|
433
|
-
throw new
|
|
434
|
-
|
|
435
|
-
|
|
732
|
+
if (response.status === 410) {
|
|
733
|
+
throw new MessageAlreadyProcessedError(messageId);
|
|
734
|
+
}
|
|
735
|
+
if (response.status === 429) {
|
|
736
|
+
let errorData = {};
|
|
737
|
+
try {
|
|
738
|
+
errorData = JSON.parse(errorText);
|
|
739
|
+
} catch {
|
|
740
|
+
}
|
|
741
|
+
throw new ConcurrencyLimitError(
|
|
742
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
743
|
+
errorData.currentInflight,
|
|
744
|
+
errorData.maxConcurrency
|
|
436
745
|
);
|
|
437
746
|
}
|
|
438
747
|
throwCommonHttpError(
|
|
@@ -442,95 +751,58 @@ var QueueClient = class {
|
|
|
442
751
|
"receive message by ID"
|
|
443
752
|
);
|
|
444
753
|
}
|
|
445
|
-
|
|
446
|
-
const parsedHeaders = parseQueueHeaders(
|
|
754
|
+
for await (const multipartMessage of parseMultipartStream(response)) {
|
|
755
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
447
756
|
if (!parsedHeaders) {
|
|
757
|
+
await consumeStream(multipartMessage.payload);
|
|
448
758
|
throw new MessageCorruptedError(
|
|
449
759
|
messageId,
|
|
450
|
-
"Missing required queue headers in
|
|
760
|
+
"Missing required queue headers in response"
|
|
451
761
|
);
|
|
452
762
|
}
|
|
763
|
+
const deserializedPayload = await transport.deserialize(
|
|
764
|
+
multipartMessage.payload
|
|
765
|
+
);
|
|
453
766
|
const message = {
|
|
454
767
|
...parsedHeaders,
|
|
455
|
-
payload:
|
|
768
|
+
payload: deserializedPayload
|
|
456
769
|
};
|
|
457
770
|
return { message };
|
|
458
771
|
}
|
|
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
772
|
throw new MessageNotFoundError(messageId);
|
|
498
773
|
}
|
|
499
|
-
/**
|
|
500
|
-
* Delete a message (acknowledge processing)
|
|
501
|
-
* @param options Delete message options
|
|
502
|
-
* @returns Promise with delete status
|
|
503
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
504
|
-
* @throws {MessageNotAvailableError} When message can't be deleted (409)
|
|
505
|
-
* @throws {BadRequestError} When ticket is missing or invalid (400)
|
|
506
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
507
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
508
|
-
* @throws {InternalServerError} When server encounters an error
|
|
509
|
-
*/
|
|
510
774
|
async deleteMessage(options) {
|
|
511
|
-
const { queueName, consumerGroup,
|
|
775
|
+
const { queueName, consumerGroup, receiptHandle } = options;
|
|
776
|
+
const headers = new Headers({
|
|
777
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
778
|
+
...this.customHeaders
|
|
779
|
+
});
|
|
780
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
781
|
+
if (effectiveDeploymentId) {
|
|
782
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
783
|
+
}
|
|
512
784
|
const response = await this.fetch(
|
|
513
|
-
|
|
785
|
+
this.buildUrl(
|
|
786
|
+
queueName,
|
|
787
|
+
"consumer",
|
|
788
|
+
consumerGroup,
|
|
789
|
+
"lease",
|
|
790
|
+
receiptHandle
|
|
791
|
+
),
|
|
514
792
|
{
|
|
515
793
|
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
|
-
})
|
|
794
|
+
headers
|
|
523
795
|
}
|
|
524
796
|
);
|
|
525
797
|
if (!response.ok) {
|
|
526
798
|
const errorText = await response.text();
|
|
527
799
|
if (response.status === 404) {
|
|
528
|
-
throw new MessageNotFoundError(
|
|
800
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
529
801
|
}
|
|
530
802
|
if (response.status === 409) {
|
|
531
803
|
throw new MessageNotAvailableError(
|
|
532
|
-
|
|
533
|
-
errorText || "Invalid
|
|
804
|
+
receiptHandle,
|
|
805
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
534
806
|
);
|
|
535
807
|
}
|
|
536
808
|
throwCommonHttpError(
|
|
@@ -538,53 +810,50 @@ var QueueClient = class {
|
|
|
538
810
|
response.statusText,
|
|
539
811
|
errorText,
|
|
540
812
|
"delete message",
|
|
541
|
-
"Missing or invalid
|
|
813
|
+
"Missing or invalid receipt handle"
|
|
542
814
|
);
|
|
543
815
|
}
|
|
544
816
|
return { deleted: true };
|
|
545
817
|
}
|
|
546
|
-
/**
|
|
547
|
-
* Change the visibility timeout of a message
|
|
548
|
-
* @param options Change visibility options
|
|
549
|
-
* @returns Promise with update status
|
|
550
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
551
|
-
* @throws {MessageNotAvailableError} When message can't be updated (409)
|
|
552
|
-
* @throws {BadRequestError} When ticket is missing or visibility timeout invalid (400)
|
|
553
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
554
|
-
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
555
|
-
* @throws {InternalServerError} When server encounters an error
|
|
556
|
-
*/
|
|
557
818
|
async changeVisibility(options) {
|
|
558
819
|
const {
|
|
559
820
|
queueName,
|
|
560
821
|
consumerGroup,
|
|
561
|
-
|
|
562
|
-
ticket,
|
|
822
|
+
receiptHandle,
|
|
563
823
|
visibilityTimeoutSeconds
|
|
564
824
|
} = options;
|
|
825
|
+
const headers = new Headers({
|
|
826
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
827
|
+
"Content-Type": "application/json",
|
|
828
|
+
...this.customHeaders
|
|
829
|
+
});
|
|
830
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
831
|
+
if (effectiveDeploymentId) {
|
|
832
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
833
|
+
}
|
|
565
834
|
const response = await this.fetch(
|
|
566
|
-
|
|
835
|
+
this.buildUrl(
|
|
836
|
+
queueName,
|
|
837
|
+
"consumer",
|
|
838
|
+
consumerGroup,
|
|
839
|
+
"lease",
|
|
840
|
+
receiptHandle
|
|
841
|
+
),
|
|
567
842
|
{
|
|
568
843
|
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
|
-
})
|
|
844
|
+
headers,
|
|
845
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
577
846
|
}
|
|
578
847
|
);
|
|
579
848
|
if (!response.ok) {
|
|
580
849
|
const errorText = await response.text();
|
|
581
850
|
if (response.status === 404) {
|
|
582
|
-
throw new MessageNotFoundError(
|
|
851
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
583
852
|
}
|
|
584
853
|
if (response.status === 409) {
|
|
585
854
|
throw new MessageNotAvailableError(
|
|
586
|
-
|
|
587
|
-
errorText || "Invalid
|
|
855
|
+
receiptHandle,
|
|
856
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
588
857
|
);
|
|
589
858
|
}
|
|
590
859
|
throwCommonHttpError(
|
|
@@ -592,194 +861,72 @@ var QueueClient = class {
|
|
|
592
861
|
response.statusText,
|
|
593
862
|
errorText,
|
|
594
863
|
"change visibility",
|
|
595
|
-
"Missing
|
|
864
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
596
865
|
);
|
|
597
866
|
}
|
|
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);
|
|
867
|
+
return { success: true };
|
|
621
868
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
869
|
+
/**
|
|
870
|
+
* Alternative endpoint for changing message visibility timeout.
|
|
871
|
+
* Uses the /visibility path suffix and expects visibilityTimeoutSeconds in the body.
|
|
872
|
+
* Functionally equivalent to changeVisibility but follows an alternative API pattern.
|
|
873
|
+
*
|
|
874
|
+
* @param options - Options for changing visibility
|
|
875
|
+
* @returns Promise resolving to change visibility response
|
|
876
|
+
*/
|
|
877
|
+
async changeVisibilityAlt(options) {
|
|
878
|
+
const {
|
|
879
|
+
queueName,
|
|
880
|
+
consumerGroup,
|
|
881
|
+
receiptHandle,
|
|
882
|
+
visibilityTimeoutSeconds
|
|
883
|
+
} = options;
|
|
884
|
+
const headers = new Headers({
|
|
885
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
886
|
+
"Content-Type": "application/json",
|
|
887
|
+
...this.customHeaders
|
|
888
|
+
});
|
|
889
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
890
|
+
if (effectiveDeploymentId) {
|
|
891
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
643
892
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
893
|
+
const response = await this.fetch(
|
|
894
|
+
this.buildUrl(
|
|
895
|
+
queueName,
|
|
896
|
+
"consumer",
|
|
897
|
+
consumerGroup,
|
|
898
|
+
"lease",
|
|
899
|
+
receiptHandle,
|
|
900
|
+
"visibility"
|
|
901
|
+
),
|
|
902
|
+
{
|
|
903
|
+
method: "PATCH",
|
|
904
|
+
headers,
|
|
905
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
656
906
|
}
|
|
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
|
-
}
|
|
907
|
+
);
|
|
908
|
+
if (!response.ok) {
|
|
909
|
+
const errorText = await response.text();
|
|
910
|
+
if (response.status === 404) {
|
|
911
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
673
912
|
}
|
|
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
|
-
}
|
|
913
|
+
if (response.status === 409) {
|
|
914
|
+
throw new MessageNotAvailableError(
|
|
915
|
+
receiptHandle,
|
|
916
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
917
|
+
);
|
|
772
918
|
}
|
|
919
|
+
throwCommonHttpError(
|
|
920
|
+
response.status,
|
|
921
|
+
response.statusText,
|
|
922
|
+
errorText,
|
|
923
|
+
"change visibility (alt)",
|
|
924
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
925
|
+
);
|
|
773
926
|
}
|
|
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
|
-
}
|
|
927
|
+
return { success: true };
|
|
928
|
+
}
|
|
929
|
+
};
|
|
783
930
|
|
|
784
931
|
// src/consumer-group.ts
|
|
785
932
|
var ConsumerGroup = class {
|
|
@@ -811,8 +958,7 @@ var ConsumerGroup = class {
|
|
|
811
958
|
* The extension loop runs every `refreshInterval` seconds and updates the message's
|
|
812
959
|
* visibility timeout to `visibilityTimeout` seconds from the current time.
|
|
813
960
|
*
|
|
814
|
-
* @param
|
|
815
|
-
* @param ticket - The receipt ticket that proves ownership of the message
|
|
961
|
+
* @param receiptHandle - The receipt handle that proves ownership of the message
|
|
816
962
|
* @returns A function that when called will stop the extension loop
|
|
817
963
|
*
|
|
818
964
|
* @remarks
|
|
@@ -822,37 +968,43 @@ var ConsumerGroup = class {
|
|
|
822
968
|
* - By default, the stop function returns immediately without waiting for in-flight
|
|
823
969
|
* - Pass `true` to the stop function to wait for any in-flight extension to complete
|
|
824
970
|
*/
|
|
825
|
-
startVisibilityExtension(
|
|
971
|
+
startVisibilityExtension(receiptHandle) {
|
|
826
972
|
let isRunning = true;
|
|
973
|
+
let isResolved = false;
|
|
827
974
|
let resolveLifecycle;
|
|
828
975
|
let timeoutId = null;
|
|
829
976
|
const lifecyclePromise = new Promise((resolve) => {
|
|
830
977
|
resolveLifecycle = resolve;
|
|
831
978
|
});
|
|
979
|
+
const safeResolve = () => {
|
|
980
|
+
if (!isResolved) {
|
|
981
|
+
isResolved = true;
|
|
982
|
+
resolveLifecycle();
|
|
983
|
+
}
|
|
984
|
+
};
|
|
832
985
|
const extend = async () => {
|
|
833
986
|
if (!isRunning) {
|
|
834
|
-
|
|
987
|
+
safeResolve();
|
|
835
988
|
return;
|
|
836
989
|
}
|
|
837
990
|
try {
|
|
838
991
|
await this.client.changeVisibility({
|
|
839
992
|
queueName: this.topicName,
|
|
840
993
|
consumerGroup: this.consumerGroupName,
|
|
841
|
-
|
|
842
|
-
ticket,
|
|
994
|
+
receiptHandle,
|
|
843
995
|
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
844
996
|
});
|
|
845
997
|
if (isRunning) {
|
|
846
998
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
847
999
|
} else {
|
|
848
|
-
|
|
1000
|
+
safeResolve();
|
|
849
1001
|
}
|
|
850
1002
|
} catch (error) {
|
|
851
1003
|
console.error(
|
|
852
|
-
`Failed to extend visibility for
|
|
1004
|
+
`Failed to extend visibility for receipt handle ${receiptHandle}:`,
|
|
853
1005
|
error
|
|
854
1006
|
);
|
|
855
|
-
|
|
1007
|
+
safeResolve();
|
|
856
1008
|
}
|
|
857
1009
|
};
|
|
858
1010
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
@@ -865,22 +1017,14 @@ var ConsumerGroup = class {
|
|
|
865
1017
|
if (waitForCompletion) {
|
|
866
1018
|
await lifecyclePromise;
|
|
867
1019
|
} else {
|
|
868
|
-
|
|
1020
|
+
safeResolve();
|
|
869
1021
|
}
|
|
870
1022
|
};
|
|
871
1023
|
}
|
|
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
1024
|
async processMessage(message, handler) {
|
|
878
|
-
const stopExtension = this.startVisibilityExtension(
|
|
879
|
-
message.messageId,
|
|
880
|
-
message.ticket
|
|
881
|
-
);
|
|
1025
|
+
const stopExtension = this.startVisibilityExtension(message.receiptHandle);
|
|
882
1026
|
try {
|
|
883
|
-
|
|
1027
|
+
await handler(message.payload, {
|
|
884
1028
|
messageId: message.messageId,
|
|
885
1029
|
deliveryCount: message.deliveryCount,
|
|
886
1030
|
createdAt: message.createdAt,
|
|
@@ -888,29 +1032,11 @@ var ConsumerGroup = class {
|
|
|
888
1032
|
consumerGroup: this.consumerGroupName
|
|
889
1033
|
});
|
|
890
1034
|
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
|
-
}
|
|
1035
|
+
await this.client.deleteMessage({
|
|
1036
|
+
queueName: this.topicName,
|
|
1037
|
+
consumerGroup: this.consumerGroupName,
|
|
1038
|
+
receiptHandle: message.receiptHandle
|
|
1039
|
+
});
|
|
914
1040
|
} catch (error) {
|
|
915
1041
|
await stopExtension();
|
|
916
1042
|
if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
|
|
@@ -925,36 +1051,16 @@ var ConsumerGroup = class {
|
|
|
925
1051
|
}
|
|
926
1052
|
async consume(handler, options) {
|
|
927
1053
|
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
|
-
}
|
|
1054
|
+
const response = await this.client.receiveMessageById(
|
|
1055
|
+
{
|
|
1056
|
+
queueName: this.topicName,
|
|
1057
|
+
consumerGroup: this.consumerGroupName,
|
|
1058
|
+
messageId: options.messageId,
|
|
1059
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1060
|
+
},
|
|
1061
|
+
this.transport
|
|
1062
|
+
);
|
|
1063
|
+
await this.processMessage(response.message, handler);
|
|
958
1064
|
} else {
|
|
959
1065
|
let messageFound = false;
|
|
960
1066
|
for await (const message of this.client.receiveMessages(
|
|
@@ -1022,7 +1128,7 @@ var Topic = class {
|
|
|
1022
1128
|
payload,
|
|
1023
1129
|
idempotencyKey: options?.idempotencyKey,
|
|
1024
1130
|
retentionSeconds: options?.retentionSeconds,
|
|
1025
|
-
|
|
1131
|
+
delaySeconds: options?.delaySeconds
|
|
1026
1132
|
},
|
|
1027
1133
|
this.transport
|
|
1028
1134
|
);
|
|
@@ -1182,9 +1288,6 @@ function createCallbackHandler(handlers, client) {
|
|
|
1182
1288
|
);
|
|
1183
1289
|
}
|
|
1184
1290
|
};
|
|
1185
|
-
if (isDevMode()) {
|
|
1186
|
-
registerDevRouteHandler(routeHandler, handlers);
|
|
1187
|
-
}
|
|
1188
1291
|
return routeHandler;
|
|
1189
1292
|
}
|
|
1190
1293
|
function handleCallback(handlers, client) {
|
|
@@ -1201,12 +1304,12 @@ async function send(topicName, payload, options) {
|
|
|
1201
1304
|
payload,
|
|
1202
1305
|
idempotencyKey: options?.idempotencyKey,
|
|
1203
1306
|
retentionSeconds: options?.retentionSeconds,
|
|
1204
|
-
|
|
1307
|
+
delaySeconds: options?.delaySeconds
|
|
1205
1308
|
},
|
|
1206
1309
|
transport
|
|
1207
1310
|
);
|
|
1208
1311
|
if (isDevMode()) {
|
|
1209
|
-
triggerDevCallbacks(topicName, result.messageId);
|
|
1312
|
+
triggerDevCallbacks(topicName, result.messageId, options?.delaySeconds);
|
|
1210
1313
|
}
|
|
1211
1314
|
return { messageId: result.messageId };
|
|
1212
1315
|
}
|
|
@@ -1214,22 +1317,10 @@ async function receive(topicName, consumerGroup, handler, options) {
|
|
|
1214
1317
|
const transport = options?.transport || new JsonTransport();
|
|
1215
1318
|
const client = options?.client || new QueueClient();
|
|
1216
1319
|
const topic = new Topic(client, topicName, transport);
|
|
1217
|
-
const {
|
|
1218
|
-
messageId,
|
|
1219
|
-
skipPayload,
|
|
1220
|
-
client: _,
|
|
1221
|
-
...consumerGroupOptions
|
|
1222
|
-
} = options || {};
|
|
1320
|
+
const { messageId, client: _, ...consumerGroupOptions } = options || {};
|
|
1223
1321
|
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
1224
1322
|
if (messageId) {
|
|
1225
|
-
|
|
1226
|
-
return consumer.consume(handler, {
|
|
1227
|
-
messageId,
|
|
1228
|
-
skipPayload: true
|
|
1229
|
-
});
|
|
1230
|
-
} else {
|
|
1231
|
-
return consumer.consume(handler, { messageId });
|
|
1232
|
-
}
|
|
1323
|
+
return consumer.consume(handler, { messageId });
|
|
1233
1324
|
} else {
|
|
1234
1325
|
return consumer.consume(handler);
|
|
1235
1326
|
}
|
|
@@ -1286,10 +1377,15 @@ export {
|
|
|
1286
1377
|
BadRequestError,
|
|
1287
1378
|
BufferTransport,
|
|
1288
1379
|
Client,
|
|
1380
|
+
ConcurrencyLimitError,
|
|
1381
|
+
ConsumerDiscoveryError,
|
|
1382
|
+
ConsumerRegistryNotConfiguredError,
|
|
1383
|
+
DuplicateMessageError,
|
|
1289
1384
|
ForbiddenError,
|
|
1290
1385
|
InternalServerError,
|
|
1291
1386
|
InvalidLimitError,
|
|
1292
1387
|
JsonTransport,
|
|
1388
|
+
MessageAlreadyProcessedError,
|
|
1293
1389
|
MessageCorruptedError,
|
|
1294
1390
|
MessageLockedError,
|
|
1295
1391
|
MessageNotAvailableError,
|