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