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