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