@vercel/queue 0.0.0-alpha.33 → 0.0.0-alpha.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +286 -85
- package/dist/index.d.mts +203 -127
- package/dist/index.d.ts +203 -127
- package/dist/index.js +680 -453
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +665 -453
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs-pages.d.mts +1 -1
- package/dist/nextjs-pages.d.ts +1 -1
- package/dist/nextjs-pages.js +673 -474
- package/dist/nextjs-pages.js.map +1 -1
- package/dist/nextjs-pages.mjs +663 -474
- package/dist/nextjs-pages.mjs.map +1 -1
- package/dist/types-BLG4ASI_.d.mts +504 -0
- package/dist/types-BLG4ASI_.d.ts +504 -0
- package/package.json +2 -6
- package/bin/local-discover.js +0 -196
- package/dist/types-Dw29Fr9y.d.mts +0 -380
- package/dist/types-Dw29Fr9y.d.ts +0 -380
package/dist/nextjs-pages.mjs
CHANGED
|
@@ -1,8 +1,49 @@
|
|
|
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
|
+
/**
|
|
30
|
+
* Create a new JsonTransport.
|
|
31
|
+
* @param options - Optional JSON serialization options
|
|
32
|
+
* @param options.replacer - Custom replacer for JSON.stringify
|
|
33
|
+
* @param options.reviver - Custom reviver for JSON.parse
|
|
34
|
+
*/
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.replacer = options.replacer;
|
|
37
|
+
this.reviver = options.reviver;
|
|
38
|
+
}
|
|
39
|
+
serialize(value) {
|
|
40
|
+
return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
|
|
41
|
+
}
|
|
42
|
+
async deserialize(stream) {
|
|
43
|
+
const buffer = await streamToBuffer(stream);
|
|
44
|
+
return JSON.parse(buffer.toString("utf8"), this.reviver);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
6
47
|
|
|
7
48
|
// src/types.ts
|
|
8
49
|
var MessageNotFoundError = class extends Error {
|
|
@@ -33,15 +74,6 @@ var QueueEmptyError = class extends Error {
|
|
|
33
74
|
this.name = "QueueEmptyError";
|
|
34
75
|
}
|
|
35
76
|
};
|
|
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
77
|
var UnauthorizedError = class extends Error {
|
|
46
78
|
constructor(message = "Missing or invalid authentication token") {
|
|
47
79
|
super(message);
|
|
@@ -72,6 +104,265 @@ var InvalidLimitError = class extends Error {
|
|
|
72
104
|
this.name = "InvalidLimitError";
|
|
73
105
|
}
|
|
74
106
|
};
|
|
107
|
+
var MessageAlreadyProcessedError = class extends Error {
|
|
108
|
+
constructor(messageId) {
|
|
109
|
+
super(`Message ${messageId} has already been processed`);
|
|
110
|
+
this.name = "MessageAlreadyProcessedError";
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var ConcurrencyLimitError = class extends Error {
|
|
114
|
+
/** Current number of in-flight messages for this consumer group. */
|
|
115
|
+
currentInflight;
|
|
116
|
+
/** Maximum allowed concurrent messages (as configured). */
|
|
117
|
+
maxConcurrency;
|
|
118
|
+
constructor(message = "Concurrency limit exceeded", currentInflight, maxConcurrency) {
|
|
119
|
+
super(message);
|
|
120
|
+
this.name = "ConcurrencyLimitError";
|
|
121
|
+
this.currentInflight = currentInflight;
|
|
122
|
+
this.maxConcurrency = maxConcurrency;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var DuplicateMessageError = class extends Error {
|
|
126
|
+
idempotencyKey;
|
|
127
|
+
constructor(message, idempotencyKey) {
|
|
128
|
+
super(message);
|
|
129
|
+
this.name = "DuplicateMessageError";
|
|
130
|
+
this.idempotencyKey = idempotencyKey;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var ConsumerDiscoveryError = class extends Error {
|
|
134
|
+
deploymentId;
|
|
135
|
+
constructor(message, deploymentId) {
|
|
136
|
+
super(message);
|
|
137
|
+
this.name = "ConsumerDiscoveryError";
|
|
138
|
+
this.deploymentId = deploymentId;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var ConsumerRegistryNotConfiguredError = class extends Error {
|
|
142
|
+
constructor(message = "Consumer registry not configured") {
|
|
143
|
+
super(message);
|
|
144
|
+
this.name = "ConsumerRegistryNotConfiguredError";
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/dev.ts
|
|
149
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
150
|
+
function filePathToUrlPath(filePath) {
|
|
151
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|js|tsx|jsx)$/, "").replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
152
|
+
if (!urlPath.startsWith("/")) {
|
|
153
|
+
urlPath = "/" + urlPath;
|
|
154
|
+
}
|
|
155
|
+
return urlPath;
|
|
156
|
+
}
|
|
157
|
+
function getDevRouteMappings() {
|
|
158
|
+
const g = globalThis;
|
|
159
|
+
if (ROUTE_MAPPINGS_KEY in g) {
|
|
160
|
+
return g[ROUTE_MAPPINGS_KEY] ?? null;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const vercelJsonPath = path.join(process.cwd(), "vercel.json");
|
|
164
|
+
if (!fs.existsSync(vercelJsonPath)) {
|
|
165
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const vercelJson = JSON.parse(fs.readFileSync(vercelJsonPath, "utf-8"));
|
|
169
|
+
if (!vercelJson.functions) {
|
|
170
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const mappings = [];
|
|
174
|
+
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
175
|
+
if (!config.experimentalTriggers) continue;
|
|
176
|
+
for (const trigger of config.experimentalTriggers) {
|
|
177
|
+
if (trigger.type?.startsWith("queue/") && trigger.topic && trigger.consumer) {
|
|
178
|
+
mappings.push({
|
|
179
|
+
urlPath: filePathToUrlPath(filePath),
|
|
180
|
+
topic: trigger.topic,
|
|
181
|
+
consumer: trigger.consumer
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
187
|
+
return g[ROUTE_MAPPINGS_KEY];
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.warn("[Dev Mode] Failed to read vercel.json:", error);
|
|
190
|
+
g[ROUTE_MAPPINGS_KEY] = null;
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function findMatchingRoutes(topicName) {
|
|
195
|
+
const mappings = getDevRouteMappings();
|
|
196
|
+
if (!mappings) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
return mappings.filter((mapping) => {
|
|
200
|
+
if (mapping.topic.includes("*")) {
|
|
201
|
+
return matchesWildcardPattern(topicName, mapping.topic);
|
|
202
|
+
}
|
|
203
|
+
return mapping.topic === topicName;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function isDevMode() {
|
|
207
|
+
return process.env.NODE_ENV === "development";
|
|
208
|
+
}
|
|
209
|
+
var DEV_VISIBILITY_POLL_INTERVAL = 50;
|
|
210
|
+
var DEV_VISIBILITY_MAX_WAIT = 5e3;
|
|
211
|
+
var DEV_VISIBILITY_BACKOFF_MULTIPLIER = 2;
|
|
212
|
+
async function waitForMessageVisibility(topicName, consumerGroup, messageId) {
|
|
213
|
+
const client = new QueueClient();
|
|
214
|
+
const transport = new JsonTransport();
|
|
215
|
+
let elapsed = 0;
|
|
216
|
+
let interval = DEV_VISIBILITY_POLL_INTERVAL;
|
|
217
|
+
while (elapsed < DEV_VISIBILITY_MAX_WAIT) {
|
|
218
|
+
try {
|
|
219
|
+
await client.receiveMessageById(
|
|
220
|
+
{
|
|
221
|
+
queueName: topicName,
|
|
222
|
+
consumerGroup,
|
|
223
|
+
messageId,
|
|
224
|
+
visibilityTimeoutSeconds: 0
|
|
225
|
+
},
|
|
226
|
+
transport
|
|
227
|
+
);
|
|
228
|
+
return true;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error instanceof MessageNotFoundError) {
|
|
231
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
232
|
+
elapsed += interval;
|
|
233
|
+
interval = Math.min(
|
|
234
|
+
interval * DEV_VISIBILITY_BACKOFF_MULTIPLIER,
|
|
235
|
+
DEV_VISIBILITY_MAX_WAIT - elapsed
|
|
236
|
+
);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (error instanceof MessageAlreadyProcessedError) {
|
|
240
|
+
console.log(
|
|
241
|
+
`[Dev Mode] Message already processed: topic="${topicName}" messageId="${messageId}"`
|
|
242
|
+
);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
console.error(
|
|
246
|
+
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
247
|
+
error
|
|
248
|
+
);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
console.warn(
|
|
253
|
+
`[Dev Mode] Message visibility timeout after ${DEV_VISIBILITY_MAX_WAIT}ms: topic="${topicName}" messageId="${messageId}"`
|
|
254
|
+
);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
function triggerDevCallbacks(topicName, messageId, delaySeconds) {
|
|
258
|
+
if (delaySeconds && delaySeconds > 0) {
|
|
259
|
+
console.log(
|
|
260
|
+
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
261
|
+
);
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
triggerDevCallbacks(topicName, messageId);
|
|
264
|
+
}, delaySeconds * 1e3);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
console.log(
|
|
268
|
+
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
269
|
+
);
|
|
270
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
271
|
+
if (matchingRoutes.length === 0) {
|
|
272
|
+
console.log(
|
|
273
|
+
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
278
|
+
console.log(
|
|
279
|
+
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
280
|
+
);
|
|
281
|
+
(async () => {
|
|
282
|
+
const firstRoute = matchingRoutes[0];
|
|
283
|
+
const isVisible = await waitForMessageVisibility(
|
|
284
|
+
topicName,
|
|
285
|
+
firstRoute.consumer,
|
|
286
|
+
messageId
|
|
287
|
+
);
|
|
288
|
+
if (!isVisible) {
|
|
289
|
+
console.warn(
|
|
290
|
+
`[Dev Mode] Skipping callbacks - message not visible: topic="${topicName}" messageId="${messageId}"`
|
|
291
|
+
);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const port = process.env.PORT || 3e3;
|
|
295
|
+
const baseUrl = `http://localhost:${port}`;
|
|
296
|
+
for (const route of matchingRoutes) {
|
|
297
|
+
const url = `${baseUrl}${route.urlPath}`;
|
|
298
|
+
console.log(
|
|
299
|
+
`[Dev Mode] Invoking handler: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`
|
|
300
|
+
);
|
|
301
|
+
const cloudEvent = {
|
|
302
|
+
type: "com.vercel.queue.v1beta",
|
|
303
|
+
source: `/topic/${topicName}/consumer/${route.consumer}`,
|
|
304
|
+
id: messageId,
|
|
305
|
+
datacontenttype: "application/json",
|
|
306
|
+
data: {
|
|
307
|
+
messageId,
|
|
308
|
+
queueName: topicName,
|
|
309
|
+
consumerGroup: route.consumer
|
|
310
|
+
},
|
|
311
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
312
|
+
specversion: "1.0"
|
|
313
|
+
};
|
|
314
|
+
try {
|
|
315
|
+
const response = await fetch(url, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: {
|
|
318
|
+
"Content-Type": "application/cloudevents+json"
|
|
319
|
+
},
|
|
320
|
+
body: JSON.stringify(cloudEvent)
|
|
321
|
+
});
|
|
322
|
+
if (response.ok) {
|
|
323
|
+
try {
|
|
324
|
+
const responseData = await response.json();
|
|
325
|
+
if (responseData.status === "success") {
|
|
326
|
+
console.log(
|
|
327
|
+
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
console.warn(
|
|
332
|
+
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
try {
|
|
337
|
+
const errorData = await response.json();
|
|
338
|
+
console.error(
|
|
339
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
340
|
+
);
|
|
341
|
+
} catch {
|
|
342
|
+
console.error(
|
|
343
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error(
|
|
349
|
+
`[Dev Mode] \u2717 HTTP request failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`,
|
|
350
|
+
error
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
})();
|
|
355
|
+
}
|
|
356
|
+
function clearDevRouteMappings() {
|
|
357
|
+
const g = globalThis;
|
|
358
|
+
delete g[ROUTE_MAPPINGS_KEY];
|
|
359
|
+
}
|
|
360
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
361
|
+
globalThis.__clearDevRouteMappings = clearDevRouteMappings;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/oidc.ts
|
|
365
|
+
import { getVercelOidcToken } from "@vercel/oidc";
|
|
75
366
|
|
|
76
367
|
// src/client.ts
|
|
77
368
|
function isDebugEnabled() {
|
|
@@ -88,14 +379,6 @@ async function consumeStream(stream) {
|
|
|
88
379
|
reader.releaseLock();
|
|
89
380
|
}
|
|
90
381
|
}
|
|
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
382
|
function throwCommonHttpError(status, statusText, errorText, operation, badRequestDefault = "Invalid parameters") {
|
|
100
383
|
if (status === 400) {
|
|
101
384
|
throw new BadRequestError(errorText || badRequestDefault);
|
|
@@ -118,8 +401,8 @@ function parseQueueHeaders(headers) {
|
|
|
118
401
|
const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
|
|
119
402
|
const timestamp = headers.get("Vqs-Timestamp");
|
|
120
403
|
const contentType = headers.get("Content-Type") || "application/octet-stream";
|
|
121
|
-
const
|
|
122
|
-
if (!messageId || !timestamp || !
|
|
404
|
+
const receiptHandle = headers.get("Vqs-Receipt-Handle");
|
|
405
|
+
if (!messageId || !timestamp || !receiptHandle) {
|
|
123
406
|
return null;
|
|
124
407
|
}
|
|
125
408
|
const deliveryCount = parseInt(deliveryCountStr, 10);
|
|
@@ -131,7 +414,7 @@ function parseQueueHeaders(headers) {
|
|
|
131
414
|
deliveryCount,
|
|
132
415
|
createdAt: new Date(timestamp),
|
|
133
416
|
contentType,
|
|
134
|
-
|
|
417
|
+
receiptHandle
|
|
135
418
|
};
|
|
136
419
|
}
|
|
137
420
|
var QueueClient = class {
|
|
@@ -139,15 +422,30 @@ var QueueClient = class {
|
|
|
139
422
|
basePath;
|
|
140
423
|
customHeaders;
|
|
141
424
|
providedToken;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
* @param options QueueClient configuration options
|
|
145
|
-
*/
|
|
425
|
+
defaultDeploymentId;
|
|
426
|
+
pinToDeployment;
|
|
146
427
|
constructor(options = {}) {
|
|
147
428
|
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/
|
|
429
|
+
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v3/topic";
|
|
149
430
|
this.customHeaders = options.headers || {};
|
|
150
431
|
this.providedToken = options.token;
|
|
432
|
+
this.defaultDeploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
|
|
433
|
+
this.pinToDeployment = options.pinToDeployment ?? true;
|
|
434
|
+
}
|
|
435
|
+
getSendDeploymentId() {
|
|
436
|
+
if (isDevMode()) {
|
|
437
|
+
return void 0;
|
|
438
|
+
}
|
|
439
|
+
if (this.pinToDeployment) {
|
|
440
|
+
return this.defaultDeploymentId;
|
|
441
|
+
}
|
|
442
|
+
return void 0;
|
|
443
|
+
}
|
|
444
|
+
getConsumeDeploymentId() {
|
|
445
|
+
if (isDevMode()) {
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
return this.defaultDeploymentId;
|
|
151
449
|
}
|
|
152
450
|
async getToken() {
|
|
153
451
|
if (this.providedToken) {
|
|
@@ -161,10 +459,12 @@ var QueueClient = class {
|
|
|
161
459
|
}
|
|
162
460
|
return token;
|
|
163
461
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
462
|
+
buildUrl(queueName, ...pathSegments) {
|
|
463
|
+
const encodedQueue = encodeURIComponent(queueName);
|
|
464
|
+
const segments = pathSegments.map((s) => encodeURIComponent(s));
|
|
465
|
+
const path2 = segments.length > 0 ? "/" + segments.join("/") : "";
|
|
466
|
+
return `${this.baseUrl}${this.basePath}/${encodedQueue}${path2}`;
|
|
467
|
+
}
|
|
168
468
|
async fetch(url, init) {
|
|
169
469
|
const method = init.method || "GET";
|
|
170
470
|
if (isDebugEnabled()) {
|
|
@@ -201,24 +501,38 @@ var QueueClient = class {
|
|
|
201
501
|
return response;
|
|
202
502
|
}
|
|
203
503
|
/**
|
|
204
|
-
* Send a message to a
|
|
205
|
-
*
|
|
206
|
-
* @param
|
|
207
|
-
* @
|
|
208
|
-
* @
|
|
504
|
+
* Send a message to a topic.
|
|
505
|
+
*
|
|
506
|
+
* @param options - Message options including queue name, payload, and optional settings
|
|
507
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
508
|
+
* @param options.payload - Message payload
|
|
509
|
+
* @param options.idempotencyKey - Optional deduplication key (dedup window: min(retention, 24h))
|
|
510
|
+
* @param options.retentionSeconds - Message TTL (default: 86400, min: 60, max: 86400)
|
|
511
|
+
* @param options.delaySeconds - Delivery delay (default: 0, max: retentionSeconds)
|
|
512
|
+
* @param transport - Serializer for the payload
|
|
513
|
+
* @returns Promise with the generated messageId
|
|
514
|
+
* @throws {DuplicateMessageError} When idempotency key was already used
|
|
515
|
+
* @throws {ConsumerDiscoveryError} When consumer discovery fails
|
|
516
|
+
* @throws {ConsumerRegistryNotConfiguredError} When registry not configured
|
|
517
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
209
518
|
* @throws {UnauthorizedError} When authentication fails
|
|
210
|
-
* @throws {ForbiddenError} When access is denied
|
|
519
|
+
* @throws {ForbiddenError} When access is denied
|
|
211
520
|
* @throws {InternalServerError} When server encounters an error
|
|
212
521
|
*/
|
|
213
522
|
async sendMessage(options, transport) {
|
|
214
|
-
const {
|
|
523
|
+
const {
|
|
524
|
+
queueName,
|
|
525
|
+
payload,
|
|
526
|
+
idempotencyKey,
|
|
527
|
+
retentionSeconds,
|
|
528
|
+
delaySeconds
|
|
529
|
+
} = options;
|
|
215
530
|
const headers = new Headers({
|
|
216
531
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
217
|
-
"Vqs-Queue-Name": queueName,
|
|
218
532
|
"Content-Type": transport.contentType,
|
|
219
533
|
...this.customHeaders
|
|
220
534
|
});
|
|
221
|
-
const deploymentId =
|
|
535
|
+
const deploymentId = this.getSendDeploymentId();
|
|
222
536
|
if (deploymentId) {
|
|
223
537
|
headers.set("Vqs-Deployment-Id", deploymentId);
|
|
224
538
|
}
|
|
@@ -228,8 +542,12 @@ var QueueClient = class {
|
|
|
228
542
|
if (retentionSeconds !== void 0) {
|
|
229
543
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
230
544
|
}
|
|
231
|
-
|
|
232
|
-
|
|
545
|
+
if (delaySeconds !== void 0) {
|
|
546
|
+
headers.set("Vqs-Delay-Seconds", delaySeconds.toString());
|
|
547
|
+
}
|
|
548
|
+
const serialized = transport.serialize(payload);
|
|
549
|
+
const body = Buffer.isBuffer(serialized) ? new Uint8Array(serialized) : serialized;
|
|
550
|
+
const response = await this.fetch(this.buildUrl(queueName), {
|
|
233
551
|
method: "POST",
|
|
234
552
|
body,
|
|
235
553
|
headers
|
|
@@ -237,7 +555,21 @@ var QueueClient = class {
|
|
|
237
555
|
if (!response.ok) {
|
|
238
556
|
const errorText = await response.text();
|
|
239
557
|
if (response.status === 409) {
|
|
240
|
-
throw new
|
|
558
|
+
throw new DuplicateMessageError(
|
|
559
|
+
errorText || "Duplicate idempotency key detected",
|
|
560
|
+
idempotencyKey
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (response.status === 502) {
|
|
564
|
+
throw new ConsumerDiscoveryError(
|
|
565
|
+
errorText || "Consumer discovery failed",
|
|
566
|
+
deploymentId
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
if (response.status === 503) {
|
|
570
|
+
throw new ConsumerRegistryNotConfiguredError(
|
|
571
|
+
errorText || "Consumer registry not configured"
|
|
572
|
+
);
|
|
241
573
|
}
|
|
242
574
|
throwCommonHttpError(
|
|
243
575
|
response.status,
|
|
@@ -250,52 +582,78 @@ var QueueClient = class {
|
|
|
250
582
|
return responseData;
|
|
251
583
|
}
|
|
252
584
|
/**
|
|
253
|
-
* Receive messages from a
|
|
254
|
-
*
|
|
255
|
-
* @param
|
|
256
|
-
* @
|
|
257
|
-
* @
|
|
258
|
-
* @
|
|
259
|
-
* @
|
|
260
|
-
* @
|
|
585
|
+
* Receive messages from a topic as an async generator.
|
|
586
|
+
*
|
|
587
|
+
* @param options - Receive options
|
|
588
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
589
|
+
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
590
|
+
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
591
|
+
* @param options.limit - Max messages to retrieve (default: 1, min: 1, max: 10)
|
|
592
|
+
* @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
|
|
593
|
+
* @param transport - Deserializer for message payloads
|
|
594
|
+
* @yields Message objects with payload, messageId, receiptHandle, etc.
|
|
595
|
+
* @throws {QueueEmptyError} When no messages available
|
|
596
|
+
* @throws {InvalidLimitError} When limit is outside 1-10 range
|
|
597
|
+
* @throws {ConcurrencyLimitError} When maxConcurrency exceeded
|
|
598
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
261
599
|
* @throws {UnauthorizedError} When authentication fails
|
|
262
|
-
* @throws {ForbiddenError} When access is denied
|
|
600
|
+
* @throws {ForbiddenError} When access is denied
|
|
263
601
|
* @throws {InternalServerError} When server encounters an error
|
|
264
602
|
*/
|
|
265
603
|
async *receiveMessages(options, transport) {
|
|
266
|
-
const {
|
|
604
|
+
const {
|
|
605
|
+
queueName,
|
|
606
|
+
consumerGroup,
|
|
607
|
+
visibilityTimeoutSeconds,
|
|
608
|
+
limit,
|
|
609
|
+
maxConcurrency
|
|
610
|
+
} = options;
|
|
267
611
|
if (limit !== void 0 && (limit < 1 || limit > 10)) {
|
|
268
612
|
throw new InvalidLimitError(limit);
|
|
269
613
|
}
|
|
270
614
|
const headers = new Headers({
|
|
271
615
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
272
|
-
"Vqs-Queue-Name": queueName,
|
|
273
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
274
616
|
Accept: "multipart/mixed",
|
|
275
617
|
...this.customHeaders
|
|
276
618
|
});
|
|
277
619
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
278
620
|
headers.set(
|
|
279
|
-
"Vqs-Visibility-Timeout",
|
|
621
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
280
622
|
visibilityTimeoutSeconds.toString()
|
|
281
623
|
);
|
|
282
624
|
}
|
|
283
625
|
if (limit !== void 0) {
|
|
284
|
-
headers.set("Vqs-
|
|
626
|
+
headers.set("Vqs-Max-Messages", limit.toString());
|
|
285
627
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
628
|
+
if (maxConcurrency !== void 0) {
|
|
629
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
630
|
+
}
|
|
631
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
632
|
+
if (effectiveDeploymentId) {
|
|
633
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
634
|
+
}
|
|
635
|
+
const response = await this.fetch(
|
|
636
|
+
this.buildUrl(queueName, "consumer", consumerGroup),
|
|
637
|
+
{
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers
|
|
640
|
+
}
|
|
641
|
+
);
|
|
290
642
|
if (response.status === 204) {
|
|
291
643
|
throw new QueueEmptyError(queueName, consumerGroup);
|
|
292
644
|
}
|
|
293
645
|
if (!response.ok) {
|
|
294
646
|
const errorText = await response.text();
|
|
295
|
-
if (response.status ===
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
647
|
+
if (response.status === 429) {
|
|
648
|
+
let errorData = {};
|
|
649
|
+
try {
|
|
650
|
+
errorData = JSON.parse(errorText);
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
throw new ConcurrencyLimitError(
|
|
654
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
655
|
+
errorData.currentInflight,
|
|
656
|
+
errorData.maxConcurrency
|
|
299
657
|
);
|
|
300
658
|
}
|
|
301
659
|
throwCommonHttpError(
|
|
@@ -327,34 +685,56 @@ var QueueClient = class {
|
|
|
327
685
|
}
|
|
328
686
|
}
|
|
329
687
|
}
|
|
688
|
+
/**
|
|
689
|
+
* Receive a specific message by its ID.
|
|
690
|
+
*
|
|
691
|
+
* @param options - Receive options
|
|
692
|
+
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
693
|
+
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
694
|
+
* @param options.messageId - Message ID to retrieve
|
|
695
|
+
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
696
|
+
* @param options.maxConcurrency - Max in-flight messages (default: unlimited, min: 1)
|
|
697
|
+
* @param transport - Deserializer for the message payload
|
|
698
|
+
* @returns Promise with the message
|
|
699
|
+
* @throws {MessageNotFoundError} When message doesn't exist
|
|
700
|
+
* @throws {MessageNotAvailableError} When message is in wrong state or was a duplicate
|
|
701
|
+
* @throws {MessageAlreadyProcessedError} When message was already processed
|
|
702
|
+
* @throws {ConcurrencyLimitError} When maxConcurrency exceeded
|
|
703
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
704
|
+
* @throws {UnauthorizedError} When authentication fails
|
|
705
|
+
* @throws {ForbiddenError} When access is denied
|
|
706
|
+
* @throws {InternalServerError} When server encounters an error
|
|
707
|
+
*/
|
|
330
708
|
async receiveMessageById(options, transport) {
|
|
331
709
|
const {
|
|
332
710
|
queueName,
|
|
333
711
|
consumerGroup,
|
|
334
712
|
messageId,
|
|
335
713
|
visibilityTimeoutSeconds,
|
|
336
|
-
|
|
714
|
+
maxConcurrency
|
|
337
715
|
} = options;
|
|
338
716
|
const headers = new Headers({
|
|
339
717
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
340
|
-
"Vqs-Queue-Name": queueName,
|
|
341
|
-
"Vqs-Consumer-Group": consumerGroup,
|
|
342
718
|
Accept: "multipart/mixed",
|
|
343
719
|
...this.customHeaders
|
|
344
720
|
});
|
|
345
721
|
if (visibilityTimeoutSeconds !== void 0) {
|
|
346
722
|
headers.set(
|
|
347
|
-
"Vqs-Visibility-Timeout",
|
|
723
|
+
"Vqs-Visibility-Timeout-Seconds",
|
|
348
724
|
visibilityTimeoutSeconds.toString()
|
|
349
725
|
);
|
|
350
726
|
}
|
|
351
|
-
if (
|
|
352
|
-
headers.set("Vqs-
|
|
727
|
+
if (maxConcurrency !== void 0) {
|
|
728
|
+
headers.set("Vqs-Max-Concurrency", maxConcurrency.toString());
|
|
729
|
+
}
|
|
730
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
731
|
+
if (effectiveDeploymentId) {
|
|
732
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
353
733
|
}
|
|
354
734
|
const response = await this.fetch(
|
|
355
|
-
|
|
735
|
+
this.buildUrl(queueName, "consumer", consumerGroup, "id", messageId),
|
|
356
736
|
{
|
|
357
|
-
method: "
|
|
737
|
+
method: "POST",
|
|
358
738
|
headers
|
|
359
739
|
}
|
|
360
740
|
);
|
|
@@ -364,12 +744,32 @@ var QueueClient = class {
|
|
|
364
744
|
throw new MessageNotFoundError(messageId);
|
|
365
745
|
}
|
|
366
746
|
if (response.status === 409) {
|
|
747
|
+
let errorData = {};
|
|
748
|
+
try {
|
|
749
|
+
errorData = JSON.parse(errorText);
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
if (errorData.originalMessageId) {
|
|
753
|
+
throw new MessageNotAvailableError(
|
|
754
|
+
messageId,
|
|
755
|
+
`This message was a duplicate - use originalMessageId: ${errorData.originalMessageId}`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
367
758
|
throw new MessageNotAvailableError(messageId);
|
|
368
759
|
}
|
|
369
|
-
if (response.status ===
|
|
370
|
-
throw new
|
|
371
|
-
|
|
372
|
-
|
|
760
|
+
if (response.status === 410) {
|
|
761
|
+
throw new MessageAlreadyProcessedError(messageId);
|
|
762
|
+
}
|
|
763
|
+
if (response.status === 429) {
|
|
764
|
+
let errorData = {};
|
|
765
|
+
try {
|
|
766
|
+
errorData = JSON.parse(errorText);
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
throw new ConcurrencyLimitError(
|
|
770
|
+
errorData.error || "Concurrency limit exceeded or throttled",
|
|
771
|
+
errorData.currentInflight,
|
|
772
|
+
errorData.maxConcurrency
|
|
373
773
|
);
|
|
374
774
|
}
|
|
375
775
|
throwCommonHttpError(
|
|
@@ -379,95 +779,73 @@ var QueueClient = class {
|
|
|
379
779
|
"receive message by ID"
|
|
380
780
|
);
|
|
381
781
|
}
|
|
382
|
-
|
|
383
|
-
const parsedHeaders = parseQueueHeaders(
|
|
782
|
+
for await (const multipartMessage of parseMultipartStream(response)) {
|
|
783
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
384
784
|
if (!parsedHeaders) {
|
|
785
|
+
await consumeStream(multipartMessage.payload);
|
|
385
786
|
throw new MessageCorruptedError(
|
|
386
787
|
messageId,
|
|
387
|
-
"Missing required queue headers in
|
|
788
|
+
"Missing required queue headers in response"
|
|
388
789
|
);
|
|
389
790
|
}
|
|
791
|
+
const deserializedPayload = await transport.deserialize(
|
|
792
|
+
multipartMessage.payload
|
|
793
|
+
);
|
|
390
794
|
const message = {
|
|
391
795
|
...parsedHeaders,
|
|
392
|
-
payload:
|
|
796
|
+
payload: deserializedPayload
|
|
393
797
|
};
|
|
394
798
|
return { message };
|
|
395
799
|
}
|
|
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
800
|
throw new MessageNotFoundError(messageId);
|
|
435
801
|
}
|
|
436
802
|
/**
|
|
437
|
-
* Delete a message
|
|
438
|
-
*
|
|
439
|
-
* @
|
|
440
|
-
* @
|
|
441
|
-
* @
|
|
442
|
-
* @
|
|
803
|
+
* Delete (acknowledge) a message after successful processing.
|
|
804
|
+
*
|
|
805
|
+
* @param options - Delete options
|
|
806
|
+
* @param options.queueName - Topic name
|
|
807
|
+
* @param options.consumerGroup - Consumer group name
|
|
808
|
+
* @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
|
|
809
|
+
* @returns Promise indicating deletion success
|
|
810
|
+
* @throws {MessageNotFoundError} When receipt handle not found
|
|
811
|
+
* @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
|
|
812
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
443
813
|
* @throws {UnauthorizedError} When authentication fails
|
|
444
|
-
* @throws {ForbiddenError} When access is denied
|
|
814
|
+
* @throws {ForbiddenError} When access is denied
|
|
445
815
|
* @throws {InternalServerError} When server encounters an error
|
|
446
816
|
*/
|
|
447
817
|
async deleteMessage(options) {
|
|
448
|
-
const { queueName, consumerGroup,
|
|
818
|
+
const { queueName, consumerGroup, receiptHandle } = options;
|
|
819
|
+
const headers = new Headers({
|
|
820
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
821
|
+
...this.customHeaders
|
|
822
|
+
});
|
|
823
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
824
|
+
if (effectiveDeploymentId) {
|
|
825
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
826
|
+
}
|
|
449
827
|
const response = await this.fetch(
|
|
450
|
-
|
|
828
|
+
this.buildUrl(
|
|
829
|
+
queueName,
|
|
830
|
+
"consumer",
|
|
831
|
+
consumerGroup,
|
|
832
|
+
"lease",
|
|
833
|
+
receiptHandle
|
|
834
|
+
),
|
|
451
835
|
{
|
|
452
836
|
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
|
-
})
|
|
837
|
+
headers
|
|
460
838
|
}
|
|
461
839
|
);
|
|
462
840
|
if (!response.ok) {
|
|
463
841
|
const errorText = await response.text();
|
|
464
842
|
if (response.status === 404) {
|
|
465
|
-
throw new MessageNotFoundError(
|
|
843
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
466
844
|
}
|
|
467
845
|
if (response.status === 409) {
|
|
468
846
|
throw new MessageNotAvailableError(
|
|
469
|
-
|
|
470
|
-
errorText || "Invalid
|
|
847
|
+
receiptHandle,
|
|
848
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
471
849
|
);
|
|
472
850
|
}
|
|
473
851
|
throwCommonHttpError(
|
|
@@ -475,53 +853,67 @@ var QueueClient = class {
|
|
|
475
853
|
response.statusText,
|
|
476
854
|
errorText,
|
|
477
855
|
"delete message",
|
|
478
|
-
"Missing or invalid
|
|
856
|
+
"Missing or invalid receipt handle"
|
|
479
857
|
);
|
|
480
858
|
}
|
|
481
859
|
return { deleted: true };
|
|
482
860
|
}
|
|
483
861
|
/**
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* @
|
|
488
|
-
* @
|
|
489
|
-
* @
|
|
862
|
+
* Extend or change the visibility timeout of a message.
|
|
863
|
+
* Used to prevent message redelivery while still processing.
|
|
864
|
+
*
|
|
865
|
+
* @param options - Visibility options
|
|
866
|
+
* @param options.queueName - Topic name
|
|
867
|
+
* @param options.consumerGroup - Consumer group name
|
|
868
|
+
* @param options.receiptHandle - Receipt handle from the received message (must use same deployment ID as receive)
|
|
869
|
+
* @param options.visibilityTimeoutSeconds - New timeout (min: 0, max: 3600, cannot exceed message expiration)
|
|
870
|
+
* @returns Promise indicating success
|
|
871
|
+
* @throws {MessageNotFoundError} When receipt handle not found
|
|
872
|
+
* @throws {MessageNotAvailableError} When receipt handle invalid or message already processed
|
|
873
|
+
* @throws {BadRequestError} When parameters are invalid
|
|
490
874
|
* @throws {UnauthorizedError} When authentication fails
|
|
491
|
-
* @throws {ForbiddenError} When access is denied
|
|
875
|
+
* @throws {ForbiddenError} When access is denied
|
|
492
876
|
* @throws {InternalServerError} When server encounters an error
|
|
493
877
|
*/
|
|
494
878
|
async changeVisibility(options) {
|
|
495
879
|
const {
|
|
496
880
|
queueName,
|
|
497
881
|
consumerGroup,
|
|
498
|
-
|
|
499
|
-
ticket,
|
|
882
|
+
receiptHandle,
|
|
500
883
|
visibilityTimeoutSeconds
|
|
501
884
|
} = options;
|
|
885
|
+
const headers = new Headers({
|
|
886
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
887
|
+
"Content-Type": "application/json",
|
|
888
|
+
...this.customHeaders
|
|
889
|
+
});
|
|
890
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
891
|
+
if (effectiveDeploymentId) {
|
|
892
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
893
|
+
}
|
|
502
894
|
const response = await this.fetch(
|
|
503
|
-
|
|
895
|
+
this.buildUrl(
|
|
896
|
+
queueName,
|
|
897
|
+
"consumer",
|
|
898
|
+
consumerGroup,
|
|
899
|
+
"lease",
|
|
900
|
+
receiptHandle
|
|
901
|
+
),
|
|
504
902
|
{
|
|
505
903
|
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
|
-
})
|
|
904
|
+
headers,
|
|
905
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
514
906
|
}
|
|
515
907
|
);
|
|
516
908
|
if (!response.ok) {
|
|
517
909
|
const errorText = await response.text();
|
|
518
910
|
if (response.status === 404) {
|
|
519
|
-
throw new MessageNotFoundError(
|
|
911
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
520
912
|
}
|
|
521
913
|
if (response.status === 409) {
|
|
522
914
|
throw new MessageNotAvailableError(
|
|
523
|
-
|
|
524
|
-
errorText || "Invalid
|
|
915
|
+
receiptHandle,
|
|
916
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
525
917
|
);
|
|
526
918
|
}
|
|
527
919
|
throwCommonHttpError(
|
|
@@ -529,228 +921,72 @@ var QueueClient = class {
|
|
|
529
921
|
response.statusText,
|
|
530
922
|
errorText,
|
|
531
923
|
"change visibility",
|
|
532
|
-
"Missing
|
|
924
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
533
925
|
);
|
|
534
926
|
}
|
|
535
|
-
return {
|
|
927
|
+
return { success: true };
|
|
536
928
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
929
|
+
/**
|
|
930
|
+
* Alternative endpoint for changing message visibility timeout.
|
|
931
|
+
* Uses the /visibility path suffix and expects visibilityTimeoutSeconds in the body.
|
|
932
|
+
* Functionally equivalent to changeVisibility but follows an alternative API pattern.
|
|
933
|
+
*
|
|
934
|
+
* @param options - Options for changing visibility
|
|
935
|
+
* @returns Promise resolving to change visibility response
|
|
936
|
+
*/
|
|
937
|
+
async changeVisibilityAlt(options) {
|
|
938
|
+
const {
|
|
939
|
+
queueName,
|
|
940
|
+
consumerGroup,
|
|
941
|
+
receiptHandle,
|
|
942
|
+
visibilityTimeoutSeconds
|
|
943
|
+
} = options;
|
|
944
|
+
const headers = new Headers({
|
|
945
|
+
Authorization: `Bearer ${await this.getToken()}`,
|
|
946
|
+
"Content-Type": "application/json",
|
|
947
|
+
...this.customHeaders
|
|
948
|
+
});
|
|
949
|
+
const effectiveDeploymentId = this.getConsumeDeploymentId();
|
|
950
|
+
if (effectiveDeploymentId) {
|
|
951
|
+
headers.set("Vqs-Deployment-Id", effectiveDeploymentId);
|
|
550
952
|
}
|
|
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
|
-
});
|
|
953
|
+
const response = await this.fetch(
|
|
954
|
+
this.buildUrl(
|
|
955
|
+
queueName,
|
|
956
|
+
"consumer",
|
|
957
|
+
consumerGroup,
|
|
958
|
+
"lease",
|
|
959
|
+
receiptHandle,
|
|
960
|
+
"visibility"
|
|
961
|
+
),
|
|
962
|
+
{
|
|
963
|
+
method: "PATCH",
|
|
964
|
+
headers,
|
|
965
|
+
body: JSON.stringify({ visibilityTimeoutSeconds })
|
|
613
966
|
}
|
|
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());
|
|
967
|
+
);
|
|
968
|
+
if (!response.ok) {
|
|
969
|
+
const errorText = await response.text();
|
|
970
|
+
if (response.status === 404) {
|
|
971
|
+
throw new MessageNotFoundError(receiptHandle);
|
|
627
972
|
}
|
|
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
|
-
}
|
|
973
|
+
if (response.status === 409) {
|
|
974
|
+
throw new MessageNotAvailableError(
|
|
975
|
+
receiptHandle,
|
|
976
|
+
errorText || "Invalid receipt handle, message not in correct state, or already processed"
|
|
977
|
+
);
|
|
644
978
|
}
|
|
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}`
|
|
979
|
+
throwCommonHttpError(
|
|
980
|
+
response.status,
|
|
981
|
+
response.statusText,
|
|
982
|
+
errorText,
|
|
983
|
+
"change visibility (alt)",
|
|
984
|
+
"Missing receipt handle or invalid visibility timeout"
|
|
680
985
|
);
|
|
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
986
|
}
|
|
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
|
-
}
|
|
987
|
+
return { success: true };
|
|
988
|
+
}
|
|
989
|
+
};
|
|
754
990
|
|
|
755
991
|
// src/consumer-group.ts
|
|
756
992
|
var ConsumerGroup = class {
|
|
@@ -761,69 +997,64 @@ var ConsumerGroup = class {
|
|
|
761
997
|
refreshInterval;
|
|
762
998
|
transport;
|
|
763
999
|
/**
|
|
764
|
-
* Create a new ConsumerGroup instance
|
|
765
|
-
*
|
|
766
|
-
* @param
|
|
767
|
-
* @param
|
|
768
|
-
* @param
|
|
1000
|
+
* Create a new ConsumerGroup instance.
|
|
1001
|
+
*
|
|
1002
|
+
* @param client - QueueClient instance to use for API calls
|
|
1003
|
+
* @param topicName - Name of the topic to consume from (pattern: `[A-Za-z0-9_-]+`)
|
|
1004
|
+
* @param consumerGroupName - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
1005
|
+
* @param options - Optional configuration
|
|
1006
|
+
* @param options.transport - Payload serializer (default: JsonTransport)
|
|
1007
|
+
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
|
|
1008
|
+
* @param options.visibilityRefreshInterval - Lock refresh interval in seconds (default: visibilityTimeout / 3)
|
|
769
1009
|
*/
|
|
770
1010
|
constructor(client, topicName, consumerGroupName, options = {}) {
|
|
771
1011
|
this.client = client;
|
|
772
1012
|
this.topicName = topicName;
|
|
773
1013
|
this.consumerGroupName = consumerGroupName;
|
|
774
|
-
this.visibilityTimeout = options.visibilityTimeoutSeconds
|
|
775
|
-
this.refreshInterval = options.
|
|
1014
|
+
this.visibilityTimeout = options.visibilityTimeoutSeconds ?? 30;
|
|
1015
|
+
this.refreshInterval = options.visibilityRefreshInterval ?? Math.floor(this.visibilityTimeout / 3);
|
|
776
1016
|
this.transport = options.transport || new JsonTransport();
|
|
777
1017
|
}
|
|
778
1018
|
/**
|
|
779
1019
|
* Starts a background loop that periodically extends the visibility timeout for a message.
|
|
780
|
-
* This prevents the message from becoming visible to other consumers while it's being processed.
|
|
781
|
-
*
|
|
782
|
-
* The extension loop runs every `refreshInterval` seconds and updates the message's
|
|
783
|
-
* visibility timeout to `visibilityTimeout` seconds from the current time.
|
|
784
|
-
*
|
|
785
|
-
* @param messageId - The unique identifier of the message to extend visibility for
|
|
786
|
-
* @param ticket - The receipt ticket that proves ownership of the message
|
|
787
|
-
* @returns A function that when called will stop the extension loop
|
|
788
|
-
*
|
|
789
|
-
* @remarks
|
|
790
|
-
* - The first extension attempt occurs after `refreshInterval` seconds, not immediately
|
|
791
|
-
* - If an extension fails, the loop terminates with an error logged to console
|
|
792
|
-
* - The returned stop function is idempotent - calling it multiple times is safe
|
|
793
|
-
* - By default, the stop function returns immediately without waiting for in-flight
|
|
794
|
-
* - Pass `true` to the stop function to wait for any in-flight extension to complete
|
|
795
1020
|
*/
|
|
796
|
-
startVisibilityExtension(
|
|
1021
|
+
startVisibilityExtension(receiptHandle) {
|
|
797
1022
|
let isRunning = true;
|
|
1023
|
+
let isResolved = false;
|
|
798
1024
|
let resolveLifecycle;
|
|
799
1025
|
let timeoutId = null;
|
|
800
1026
|
const lifecyclePromise = new Promise((resolve) => {
|
|
801
1027
|
resolveLifecycle = resolve;
|
|
802
1028
|
});
|
|
1029
|
+
const safeResolve = () => {
|
|
1030
|
+
if (!isResolved) {
|
|
1031
|
+
isResolved = true;
|
|
1032
|
+
resolveLifecycle();
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
803
1035
|
const extend = async () => {
|
|
804
1036
|
if (!isRunning) {
|
|
805
|
-
|
|
1037
|
+
safeResolve();
|
|
806
1038
|
return;
|
|
807
1039
|
}
|
|
808
1040
|
try {
|
|
809
1041
|
await this.client.changeVisibility({
|
|
810
1042
|
queueName: this.topicName,
|
|
811
1043
|
consumerGroup: this.consumerGroupName,
|
|
812
|
-
|
|
813
|
-
ticket,
|
|
1044
|
+
receiptHandle,
|
|
814
1045
|
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
815
1046
|
});
|
|
816
1047
|
if (isRunning) {
|
|
817
1048
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
818
1049
|
} else {
|
|
819
|
-
|
|
1050
|
+
safeResolve();
|
|
820
1051
|
}
|
|
821
1052
|
} catch (error) {
|
|
822
1053
|
console.error(
|
|
823
|
-
`Failed to extend visibility for
|
|
1054
|
+
`Failed to extend visibility for receipt handle ${receiptHandle}:`,
|
|
824
1055
|
error
|
|
825
1056
|
);
|
|
826
|
-
|
|
1057
|
+
safeResolve();
|
|
827
1058
|
}
|
|
828
1059
|
};
|
|
829
1060
|
timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
|
|
@@ -836,22 +1067,14 @@ var ConsumerGroup = class {
|
|
|
836
1067
|
if (waitForCompletion) {
|
|
837
1068
|
await lifecyclePromise;
|
|
838
1069
|
} else {
|
|
839
|
-
|
|
1070
|
+
safeResolve();
|
|
840
1071
|
}
|
|
841
1072
|
};
|
|
842
1073
|
}
|
|
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
1074
|
async processMessage(message, handler) {
|
|
849
|
-
const stopExtension = this.startVisibilityExtension(
|
|
850
|
-
message.messageId,
|
|
851
|
-
message.ticket
|
|
852
|
-
);
|
|
1075
|
+
const stopExtension = this.startVisibilityExtension(message.receiptHandle);
|
|
853
1076
|
try {
|
|
854
|
-
|
|
1077
|
+
await handler(message.payload, {
|
|
855
1078
|
messageId: message.messageId,
|
|
856
1079
|
deliveryCount: message.deliveryCount,
|
|
857
1080
|
createdAt: message.createdAt,
|
|
@@ -859,29 +1082,11 @@ var ConsumerGroup = class {
|
|
|
859
1082
|
consumerGroup: this.consumerGroupName
|
|
860
1083
|
});
|
|
861
1084
|
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
|
-
}
|
|
1085
|
+
await this.client.deleteMessage({
|
|
1086
|
+
queueName: this.topicName,
|
|
1087
|
+
consumerGroup: this.consumerGroupName,
|
|
1088
|
+
receiptHandle: message.receiptHandle
|
|
1089
|
+
});
|
|
885
1090
|
} catch (error) {
|
|
886
1091
|
await stopExtension();
|
|
887
1092
|
if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
|
|
@@ -896,36 +1101,16 @@ var ConsumerGroup = class {
|
|
|
896
1101
|
}
|
|
897
1102
|
async consume(handler, options) {
|
|
898
1103
|
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
|
-
}
|
|
1104
|
+
const response = await this.client.receiveMessageById(
|
|
1105
|
+
{
|
|
1106
|
+
queueName: this.topicName,
|
|
1107
|
+
consumerGroup: this.consumerGroupName,
|
|
1108
|
+
messageId: options.messageId,
|
|
1109
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1110
|
+
},
|
|
1111
|
+
this.transport
|
|
1112
|
+
);
|
|
1113
|
+
await this.processMessage(response.message, handler);
|
|
929
1114
|
} else {
|
|
930
1115
|
let messageFound = false;
|
|
931
1116
|
for await (const message of this.client.receiveMessages(
|
|
@@ -993,7 +1178,7 @@ var Topic = class {
|
|
|
993
1178
|
payload,
|
|
994
1179
|
idempotencyKey: options?.idempotencyKey,
|
|
995
1180
|
retentionSeconds: options?.retentionSeconds,
|
|
996
|
-
|
|
1181
|
+
delaySeconds: options?.delaySeconds
|
|
997
1182
|
},
|
|
998
1183
|
this.transport
|
|
999
1184
|
);
|
|
@@ -1103,7 +1288,7 @@ async function parseCallback(request) {
|
|
|
1103
1288
|
messageId
|
|
1104
1289
|
};
|
|
1105
1290
|
}
|
|
1106
|
-
function createCallbackHandler(handlers, client) {
|
|
1291
|
+
function createCallbackHandler(handlers, client, visibilityTimeoutSeconds) {
|
|
1107
1292
|
for (const topicPattern in handlers) {
|
|
1108
1293
|
if (topicPattern.includes("*")) {
|
|
1109
1294
|
if (!validateWildcardPattern(topicPattern)) {
|
|
@@ -1139,7 +1324,10 @@ function createCallbackHandler(handlers, client) {
|
|
|
1139
1324
|
);
|
|
1140
1325
|
}
|
|
1141
1326
|
const topic = new Topic(client, queueName);
|
|
1142
|
-
const cg = topic.consumerGroup(
|
|
1327
|
+
const cg = topic.consumerGroup(
|
|
1328
|
+
consumerGroup,
|
|
1329
|
+
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : void 0
|
|
1330
|
+
);
|
|
1143
1331
|
await cg.consume(consumerGroupHandler, { messageId });
|
|
1144
1332
|
return Response.json({ status: "success" });
|
|
1145
1333
|
} catch (error) {
|
|
@@ -1153,13 +1341,14 @@ function createCallbackHandler(handlers, client) {
|
|
|
1153
1341
|
);
|
|
1154
1342
|
}
|
|
1155
1343
|
};
|
|
1156
|
-
if (isDevMode()) {
|
|
1157
|
-
registerDevRouteHandler(routeHandler, handlers);
|
|
1158
|
-
}
|
|
1159
1344
|
return routeHandler;
|
|
1160
1345
|
}
|
|
1161
|
-
function handleCallback(handlers,
|
|
1162
|
-
return createCallbackHandler(
|
|
1346
|
+
function handleCallback(handlers, options) {
|
|
1347
|
+
return createCallbackHandler(
|
|
1348
|
+
handlers,
|
|
1349
|
+
options?.client || new QueueClient(),
|
|
1350
|
+
options?.visibilityTimeoutSeconds
|
|
1351
|
+
);
|
|
1163
1352
|
}
|
|
1164
1353
|
|
|
1165
1354
|
// src/nextjs-pages.ts
|