@vercel/queue 0.0.0-alpha.2 → 0.0.0-alpha.22
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 +236 -754
- package/bin/local-discover.js +188 -0
- package/dist/index.d.mts +153 -472
- package/dist/index.d.ts +153 -472
- package/dist/index.js +513 -535
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +509 -526
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -5
package/dist/index.mjs
CHANGED
|
@@ -1,128 +1,79 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
async function
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
|
|
7
|
-
if (!token) {
|
|
8
|
-
throw new Error(
|
|
9
|
-
`The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
return token;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// src/client.ts
|
|
16
|
-
import { parseMultipartStream } from "mixpart";
|
|
17
|
-
|
|
18
|
-
// src/local.ts
|
|
19
|
-
import { spawn } from "child_process";
|
|
20
|
-
function isLocalhostWithPort(url) {
|
|
1
|
+
// src/transports.ts
|
|
2
|
+
async function streamToBuffer(stream) {
|
|
3
|
+
let totalLength = 0;
|
|
4
|
+
const reader = stream.getReader();
|
|
5
|
+
const chunks = [];
|
|
21
6
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
while (true) {
|
|
8
|
+
const { done, value } = await reader.read();
|
|
9
|
+
if (done) break;
|
|
10
|
+
chunks.push(value);
|
|
11
|
+
totalLength += value.length;
|
|
12
|
+
}
|
|
13
|
+
} finally {
|
|
14
|
+
reader.releaseLock();
|
|
28
15
|
}
|
|
16
|
+
return Buffer.concat(chunks, totalLength);
|
|
29
17
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return [];
|
|
18
|
+
var JsonTransport = class {
|
|
19
|
+
contentType = "application/json";
|
|
20
|
+
replacer;
|
|
21
|
+
reviver;
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.replacer = options.replacer;
|
|
24
|
+
this.reviver = options.reviver;
|
|
38
25
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const { isLocalhost } = isLocalhostWithPort(config.url);
|
|
42
|
-
return isLocalhost;
|
|
43
|
-
});
|
|
44
|
-
if (hasLocalhostCallbacks) {
|
|
45
|
-
console.warn(
|
|
46
|
-
`Queue Development Mode: Localhost callbacks are not supported on ${process.platform}. Localhost callback handling requires bash, nc, and curl which are available on macOS and Linux only. Consider using a production callback URL or developing on a supported platform.`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
return [];
|
|
26
|
+
serialize(value) {
|
|
27
|
+
return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
|
|
50
28
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (isLocalhost && port && port > 0) {
|
|
55
|
-
localhostCallbacks.push({ group, config, port });
|
|
56
|
-
} else {
|
|
57
|
-
console.warn(
|
|
58
|
-
`Queue Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
return localhostCallbacks;
|
|
63
|
-
}
|
|
64
|
-
function fireLocalhostCallbacks(localhostCallbacks, queueName, responseData) {
|
|
65
|
-
localhostCallbacks.forEach(({ group, config, port }) => {
|
|
66
|
-
const callbackHeaders = new Headers();
|
|
67
|
-
callbackHeaders.set("Vqs-Message-Id", responseData.messageId);
|
|
68
|
-
callbackHeaders.set("Vqs-Queue-Name", queueName);
|
|
69
|
-
callbackHeaders.set("Vqs-Consumer-Group", group);
|
|
70
|
-
fireAndForgetWaitForHttpReady(
|
|
71
|
-
config.url,
|
|
72
|
-
port,
|
|
73
|
-
config.delay || 0,
|
|
74
|
-
3,
|
|
75
|
-
// Default retry frequency
|
|
76
|
-
callbackHeaders
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
function fireAndForgetWaitForHttpReady(url, port, initialDelaySeconds = 0, retryFrequencySeconds = 3, headers) {
|
|
81
|
-
if (!isSupportedPlatform()) {
|
|
82
|
-
console.warn(
|
|
83
|
-
`Queue: fireAndForgetWaitForHttpReady is not supported on ${process.platform}. This function requires bash, nc, and curl which are available on macOS and Linux only.`
|
|
84
|
-
);
|
|
85
|
-
return;
|
|
29
|
+
async deserialize(stream) {
|
|
30
|
+
const buffer = await streamToBuffer(stream);
|
|
31
|
+
return JSON.parse(buffer.toString("utf8"), this.reviver);
|
|
86
32
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
33
|
+
};
|
|
34
|
+
var BufferTransport = class {
|
|
35
|
+
contentType = "application/octet-stream";
|
|
36
|
+
serialize(value) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
async deserialize(stream) {
|
|
40
|
+
return await streamToBuffer(stream);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var StreamTransport = class {
|
|
44
|
+
contentType = "application/octet-stream";
|
|
45
|
+
serialize(value) {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
async deserialize(stream) {
|
|
49
|
+
return stream;
|
|
94
50
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
51
|
+
async finalize(payload) {
|
|
52
|
+
const reader = payload.getReader();
|
|
53
|
+
try {
|
|
54
|
+
while (true) {
|
|
55
|
+
const { done } = await reader.read();
|
|
56
|
+
if (done) break;
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
reader.releaseLock();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
98
63
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
# If closed twice in a row, give up immediately
|
|
113
|
-
if [ "$missed" -ge 2 ]; then
|
|
114
|
-
exit 1
|
|
115
|
-
fi
|
|
116
|
-
fi
|
|
117
|
-
# Wait before next cycle
|
|
118
|
-
sleep ${retryFrequencySeconds}
|
|
119
|
-
done
|
|
120
|
-
`;
|
|
121
|
-
const childProcess = spawn("bash", ["-c", bashScript], {
|
|
122
|
-
stdio: "ignore",
|
|
123
|
-
detached: true
|
|
124
|
-
});
|
|
125
|
-
childProcess.unref();
|
|
64
|
+
// src/client.ts
|
|
65
|
+
import { parseMultipartStream } from "mixpart";
|
|
66
|
+
|
|
67
|
+
// src/oidc.ts
|
|
68
|
+
function getVercelOidcToken() {
|
|
69
|
+
const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
|
|
70
|
+
const fromSymbol = globalThis;
|
|
71
|
+
const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
|
|
72
|
+
const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
|
|
73
|
+
if (!token) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return token;
|
|
126
77
|
}
|
|
127
78
|
|
|
128
79
|
// src/types.ts
|
|
@@ -140,16 +91,6 @@ var MessageNotAvailableError = class extends Error {
|
|
|
140
91
|
this.name = "MessageNotAvailableError";
|
|
141
92
|
}
|
|
142
93
|
};
|
|
143
|
-
var FifoOrderingViolationError = class extends Error {
|
|
144
|
-
nextMessageId;
|
|
145
|
-
constructor(messageId, nextMessageId, reason) {
|
|
146
|
-
super(
|
|
147
|
-
`FIFO ordering violation for message ${messageId}: ${reason}. Process message ${nextMessageId} first.`
|
|
148
|
-
);
|
|
149
|
-
this.name = "FifoOrderingViolationError";
|
|
150
|
-
this.nextMessageId = nextMessageId;
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
94
|
var MessageCorruptedError = class extends Error {
|
|
154
95
|
constructor(messageId, reason) {
|
|
155
96
|
super(`Message ${messageId} is corrupted: ${reason}`);
|
|
@@ -191,16 +132,6 @@ var BadRequestError = class extends Error {
|
|
|
191
132
|
this.name = "BadRequestError";
|
|
192
133
|
}
|
|
193
134
|
};
|
|
194
|
-
var FailedDependencyError = class extends Error {
|
|
195
|
-
nextMessageId;
|
|
196
|
-
constructor(messageId, nextMessageId) {
|
|
197
|
-
super(
|
|
198
|
-
`Failed dependency: FIFO ordering violation for message ${messageId}. Must process message ${nextMessageId} first.`
|
|
199
|
-
);
|
|
200
|
-
this.name = "FailedDependencyError";
|
|
201
|
-
this.nextMessageId = nextMessageId;
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
135
|
var InternalServerError = class extends Error {
|
|
205
136
|
constructor(message = "Unexpected server error") {
|
|
206
137
|
super(message);
|
|
@@ -213,12 +144,6 @@ var InvalidLimitError = class extends Error {
|
|
|
213
144
|
this.name = "InvalidLimitError";
|
|
214
145
|
}
|
|
215
146
|
};
|
|
216
|
-
var InvalidCallbackError = class extends Error {
|
|
217
|
-
constructor(message) {
|
|
218
|
-
super(message);
|
|
219
|
-
this.name = "InvalidCallbackError";
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
147
|
|
|
223
148
|
// src/client.ts
|
|
224
149
|
async function consumeStream(stream) {
|
|
@@ -248,40 +173,33 @@ function parseQueueHeaders(headers) {
|
|
|
248
173
|
return {
|
|
249
174
|
messageId,
|
|
250
175
|
deliveryCount,
|
|
251
|
-
timestamp,
|
|
176
|
+
createdAt: new Date(timestamp),
|
|
252
177
|
contentType,
|
|
253
178
|
ticket
|
|
254
179
|
};
|
|
255
180
|
}
|
|
256
|
-
var QueueClient = class
|
|
181
|
+
var QueueClient = class {
|
|
257
182
|
baseUrl;
|
|
183
|
+
basePath;
|
|
258
184
|
token;
|
|
259
185
|
/**
|
|
260
186
|
* Create a new Vercel Queue Service client
|
|
261
|
-
* @param options Client configuration options
|
|
187
|
+
* @param options Client configuration options (optional - will auto-detect Vercel Function environment)
|
|
262
188
|
*/
|
|
263
|
-
constructor(options) {
|
|
264
|
-
this.baseUrl = options.baseUrl || "https://
|
|
265
|
-
this.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (!token) {
|
|
277
|
-
throw new Error(
|
|
278
|
-
"Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment."
|
|
279
|
-
);
|
|
189
|
+
constructor(options = {}) {
|
|
190
|
+
this.baseUrl = options.baseUrl || "https://vercel-queue.com";
|
|
191
|
+
this.basePath = options.basePath || "/api/v2/messages";
|
|
192
|
+
if (options.token) {
|
|
193
|
+
this.token = options.token;
|
|
194
|
+
} else {
|
|
195
|
+
const token = getVercelOidcToken();
|
|
196
|
+
if (!token) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment, or provide a token explicitly.\n\nTo set up your environment:\n1. Link your project: 'vercel link'\n2. Pull environment variables: 'vercel env pull'\n3. Run with environment: 'dotenv -e .env.local -- your-command'"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
this.token = token;
|
|
280
202
|
}
|
|
281
|
-
return new _QueueClient({
|
|
282
|
-
token,
|
|
283
|
-
baseUrl
|
|
284
|
-
});
|
|
285
203
|
}
|
|
286
204
|
/**
|
|
287
205
|
* Send a message to a queue
|
|
@@ -294,40 +212,23 @@ var QueueClient = class _QueueClient {
|
|
|
294
212
|
* @throws {InternalServerError} When server encounters an error
|
|
295
213
|
*/
|
|
296
214
|
async sendMessage(options, transport) {
|
|
297
|
-
const { queueName, payload, idempotencyKey, retentionSeconds
|
|
215
|
+
const { queueName, payload, idempotencyKey, retentionSeconds } = options;
|
|
298
216
|
const headers = new Headers({
|
|
299
217
|
Authorization: `Bearer ${this.token}`,
|
|
300
218
|
"Vqs-Queue-Name": queueName,
|
|
301
219
|
"Content-Type": transport.contentType
|
|
302
220
|
});
|
|
221
|
+
if (process.env.VERCEL_DEPLOYMENT_ID) {
|
|
222
|
+
headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
|
|
223
|
+
}
|
|
303
224
|
if (idempotencyKey) {
|
|
304
225
|
headers.set("Vqs-Idempotency-Key", idempotencyKey);
|
|
305
226
|
}
|
|
306
227
|
if (retentionSeconds !== void 0) {
|
|
307
228
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
308
229
|
}
|
|
309
|
-
let localhostCallbacks = [];
|
|
310
|
-
if (callbacks) {
|
|
311
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
312
|
-
if (isDevelopment) {
|
|
313
|
-
localhostCallbacks = processDevelopmentCallbacks(callbacks);
|
|
314
|
-
} else {
|
|
315
|
-
const endpoints = Object.entries(callbacks).map(
|
|
316
|
-
([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
|
|
317
|
-
).join(",");
|
|
318
|
-
headers.set("Vqs-Callback-Url", endpoints);
|
|
319
|
-
const delays = Object.entries(callbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
|
|
320
|
-
if (delays) {
|
|
321
|
-
headers.set("Vqs-Callback-Delay", delays);
|
|
322
|
-
}
|
|
323
|
-
const frequencies = Object.entries(callbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
|
|
324
|
-
if (frequencies) {
|
|
325
|
-
headers.set("Vqs-Callback-Frequency", frequencies);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
230
|
const body = transport.serialize(payload);
|
|
330
|
-
const response = await fetch(`${this.baseUrl}
|
|
231
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
331
232
|
method: "POST",
|
|
332
233
|
headers,
|
|
333
234
|
body
|
|
@@ -356,9 +257,6 @@ var QueueClient = class _QueueClient {
|
|
|
356
257
|
);
|
|
357
258
|
}
|
|
358
259
|
const responseData = await response.json();
|
|
359
|
-
if (localhostCallbacks.length > 0) {
|
|
360
|
-
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
361
|
-
}
|
|
362
260
|
return responseData;
|
|
363
261
|
}
|
|
364
262
|
/**
|
|
@@ -368,7 +266,7 @@ var QueueClient = class _QueueClient {
|
|
|
368
266
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
369
267
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
370
268
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
371
|
-
* @throws {MessageLockedError} When
|
|
269
|
+
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
372
270
|
* @throws {BadRequestError} When request parameters are invalid
|
|
373
271
|
* @throws {UnauthorizedError} When authentication fails
|
|
374
272
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -394,7 +292,7 @@ var QueueClient = class _QueueClient {
|
|
|
394
292
|
if (limit !== void 0) {
|
|
395
293
|
headers.set("Vqs-Limit", limit.toString());
|
|
396
294
|
}
|
|
397
|
-
const response = await fetch(`${this.baseUrl}
|
|
295
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
398
296
|
method: "GET",
|
|
399
297
|
headers
|
|
400
298
|
});
|
|
@@ -419,7 +317,7 @@ var QueueClient = class _QueueClient {
|
|
|
419
317
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
420
318
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
421
319
|
}
|
|
422
|
-
throw new MessageLockedError("next message
|
|
320
|
+
throw new MessageLockedError("next message", retryAfter);
|
|
423
321
|
}
|
|
424
322
|
if (response.status >= 500) {
|
|
425
323
|
throw new InternalServerError(
|
|
@@ -476,7 +374,7 @@ var QueueClient = class _QueueClient {
|
|
|
476
374
|
headers.set("Vqs-Skip-Payload", "1");
|
|
477
375
|
}
|
|
478
376
|
const response = await fetch(
|
|
479
|
-
`${this.baseUrl}
|
|
377
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
480
378
|
{
|
|
481
379
|
method: "GET",
|
|
482
380
|
headers
|
|
@@ -505,37 +403,7 @@ var QueueClient = class _QueueClient {
|
|
|
505
403
|
}
|
|
506
404
|
throw new MessageLockedError(messageId, retryAfter);
|
|
507
405
|
}
|
|
508
|
-
if (response.status === 424) {
|
|
509
|
-
try {
|
|
510
|
-
const errorData = await response.json();
|
|
511
|
-
if (errorData.meta?.nextMessageId) {
|
|
512
|
-
throw new FailedDependencyError(
|
|
513
|
-
messageId,
|
|
514
|
-
errorData.meta.nextMessageId
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
} catch (parseError) {
|
|
518
|
-
if (parseError instanceof FailedDependencyError) {
|
|
519
|
-
throw parseError;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
throw new MessageNotAvailableError(
|
|
523
|
-
messageId,
|
|
524
|
-
"FIFO ordering violation"
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
406
|
if (response.status === 409) {
|
|
528
|
-
try {
|
|
529
|
-
const errorData = await response.json();
|
|
530
|
-
if (errorData.nextMessageId) {
|
|
531
|
-
throw new FifoOrderingViolationError(
|
|
532
|
-
messageId,
|
|
533
|
-
errorData.nextMessageId,
|
|
534
|
-
errorData.error
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
} catch (parseError) {
|
|
538
|
-
}
|
|
539
407
|
throw new MessageNotAvailableError(messageId);
|
|
540
408
|
}
|
|
541
409
|
if (response.status >= 500) {
|
|
@@ -615,7 +483,7 @@ var QueueClient = class _QueueClient {
|
|
|
615
483
|
async deleteMessage(options) {
|
|
616
484
|
const { queueName, consumerGroup, messageId, ticket } = options;
|
|
617
485
|
const response = await fetch(
|
|
618
|
-
`${this.baseUrl}
|
|
486
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
619
487
|
{
|
|
620
488
|
method: "DELETE",
|
|
621
489
|
headers: new Headers({
|
|
@@ -676,7 +544,7 @@ var QueueClient = class _QueueClient {
|
|
|
676
544
|
visibilityTimeoutSeconds
|
|
677
545
|
} = options;
|
|
678
546
|
const response = await fetch(
|
|
679
|
-
`${this.baseUrl}
|
|
547
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
680
548
|
{
|
|
681
549
|
method: "PATCH",
|
|
682
550
|
headers: new Headers({
|
|
@@ -722,81 +590,336 @@ var QueueClient = class _QueueClient {
|
|
|
722
590
|
}
|
|
723
591
|
};
|
|
724
592
|
|
|
725
|
-
// src/
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
593
|
+
// src/callback.ts
|
|
594
|
+
function validateWildcardPattern(pattern) {
|
|
595
|
+
const firstIndex = pattern.indexOf("*");
|
|
596
|
+
const lastIndex = pattern.lastIndexOf("*");
|
|
597
|
+
if (firstIndex !== lastIndex) {
|
|
598
|
+
return false;
|
|
730
599
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
600
|
+
if (firstIndex === -1) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
if (firstIndex !== pattern.length - 1) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
function matchesWildcardPattern(topicName, pattern) {
|
|
609
|
+
const prefix = pattern.slice(0, -1);
|
|
610
|
+
return topicName.startsWith(prefix);
|
|
611
|
+
}
|
|
612
|
+
function findTopicHandler(queueName, handlers) {
|
|
613
|
+
const exactHandler = handlers[queueName];
|
|
614
|
+
if (exactHandler) {
|
|
615
|
+
return exactHandler;
|
|
616
|
+
}
|
|
617
|
+
for (const pattern in handlers) {
|
|
618
|
+
if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
|
|
619
|
+
return handlers[pattern];
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
async function parseCallback(request) {
|
|
625
|
+
const contentType = request.headers.get("content-type");
|
|
626
|
+
if (!contentType || !contentType.includes("application/cloudevents+json")) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
"Invalid content type: expected 'application/cloudevents+json'"
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
let cloudEvent;
|
|
632
|
+
try {
|
|
633
|
+
cloudEvent = await request.json();
|
|
634
|
+
} catch (error) {
|
|
635
|
+
throw new Error("Failed to parse CloudEvent from request body");
|
|
636
|
+
}
|
|
637
|
+
if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
|
|
638
|
+
throw new Error("Invalid CloudEvent: missing required fields");
|
|
639
|
+
}
|
|
640
|
+
if (cloudEvent.type !== "com.vercel.queue.v1beta") {
|
|
641
|
+
throw new Error(
|
|
642
|
+
`Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
const missingFields = [];
|
|
646
|
+
if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
|
|
647
|
+
if (!("consumerGroup" in cloudEvent.data))
|
|
648
|
+
missingFields.push("consumerGroup");
|
|
649
|
+
if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
|
|
650
|
+
if (missingFields.length > 0) {
|
|
651
|
+
throw new Error(
|
|
652
|
+
`Missing required CloudEvent data fields: ${missingFields.join(", ")}`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
const { messageId, queueName, consumerGroup } = cloudEvent.data;
|
|
656
|
+
return {
|
|
657
|
+
queueName,
|
|
658
|
+
consumerGroup,
|
|
659
|
+
messageId
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function handleCallback(handlers) {
|
|
663
|
+
for (const topicPattern in handlers) {
|
|
664
|
+
if (topicPattern.includes("*")) {
|
|
665
|
+
if (!validateWildcardPattern(topicPattern)) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
`Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
|
|
668
|
+
);
|
|
739
669
|
}
|
|
740
|
-
} finally {
|
|
741
|
-
reader.releaseLock();
|
|
742
670
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
671
|
+
}
|
|
672
|
+
const routeHandler = async (request) => {
|
|
673
|
+
try {
|
|
674
|
+
const { queueName, consumerGroup, messageId } = await parseCallback(request);
|
|
675
|
+
const topicHandler = findTopicHandler(queueName, handlers);
|
|
676
|
+
if (!topicHandler) {
|
|
677
|
+
const availableTopics = Object.keys(handlers).join(", ");
|
|
678
|
+
return Response.json(
|
|
679
|
+
{
|
|
680
|
+
error: `No handler found for topic: ${queueName}`,
|
|
681
|
+
availableTopics
|
|
682
|
+
},
|
|
683
|
+
{ status: 404 }
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
687
|
+
if (!consumerGroupHandler) {
|
|
688
|
+
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
689
|
+
return Response.json(
|
|
690
|
+
{
|
|
691
|
+
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
692
|
+
availableGroups
|
|
693
|
+
},
|
|
694
|
+
{ status: 404 }
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
const client = new QueueClient();
|
|
698
|
+
const topic = new Topic(client, queueName);
|
|
699
|
+
const cg = topic.consumerGroup(consumerGroup);
|
|
700
|
+
await cg.consume(consumerGroupHandler, { messageId });
|
|
701
|
+
return Response.json({ status: "success" });
|
|
702
|
+
} catch (error) {
|
|
703
|
+
console.error("Queue callback error:", error);
|
|
704
|
+
if (error instanceof Error && (error.message.includes("Missing required CloudEvent data fields") || error.message.includes("Invalid CloudEvent") || error.message.includes("Invalid CloudEvent type") || error.message.includes("Invalid content type") || error.message.includes("Failed to parse CloudEvent"))) {
|
|
705
|
+
return Response.json({ error: error.message }, { status: 400 });
|
|
706
|
+
}
|
|
707
|
+
return Response.json(
|
|
708
|
+
{ error: "Failed to process queue message" },
|
|
709
|
+
{ status: 500 }
|
|
710
|
+
);
|
|
749
711
|
}
|
|
750
|
-
|
|
712
|
+
};
|
|
713
|
+
if (isDevMode()) {
|
|
714
|
+
registerDevRouteHandler(routeHandler, handlers);
|
|
751
715
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
716
|
+
return routeHandler;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/dev.ts
|
|
720
|
+
var devRouteHandlers = /* @__PURE__ */ new Map();
|
|
721
|
+
var wildcardRouteHandlers = /* @__PURE__ */ new Map();
|
|
722
|
+
var routeHandlerKeys = /* @__PURE__ */ new WeakMap();
|
|
723
|
+
function cleanupDeadRefs(key, refs) {
|
|
724
|
+
const aliveRefs = refs.filter((ref) => ref.deref() !== void 0);
|
|
725
|
+
if (aliveRefs.length === 0) {
|
|
726
|
+
wildcardRouteHandlers.delete(key);
|
|
727
|
+
} else if (aliveRefs.length < refs.length) {
|
|
728
|
+
wildcardRouteHandlers.set(key, aliveRefs);
|
|
757
729
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
730
|
+
}
|
|
731
|
+
function isDevMode() {
|
|
732
|
+
return process.env.NODE_ENV === "development";
|
|
733
|
+
}
|
|
734
|
+
function registerDevRouteHandler(routeHandler, handlers) {
|
|
735
|
+
const existingKeys = routeHandlerKeys.get(routeHandler);
|
|
736
|
+
if (existingKeys) {
|
|
737
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
738
|
+
for (const topicName in handlers) {
|
|
739
|
+
for (const consumerGroup in handlers[topicName]) {
|
|
740
|
+
newKeys.add(`${topicName}:${consumerGroup}`);
|
|
766
741
|
}
|
|
767
|
-
} finally {
|
|
768
|
-
reader.releaseLock();
|
|
769
742
|
}
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
743
|
+
for (const key of existingKeys) {
|
|
744
|
+
if (!newKeys.has(key)) {
|
|
745
|
+
const [topicPattern] = key.split(":");
|
|
746
|
+
if (topicPattern.includes("*")) {
|
|
747
|
+
const refs = wildcardRouteHandlers.get(key);
|
|
748
|
+
if (refs) {
|
|
749
|
+
const filteredRefs = refs.filter(
|
|
750
|
+
(ref) => ref.deref() !== routeHandler
|
|
751
|
+
);
|
|
752
|
+
if (filteredRefs.length === 0) {
|
|
753
|
+
wildcardRouteHandlers.delete(key);
|
|
754
|
+
} else {
|
|
755
|
+
wildcardRouteHandlers.set(key, filteredRefs);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
} else {
|
|
759
|
+
devRouteHandlers.delete(key);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
776
762
|
}
|
|
777
|
-
return Buffer.from(buffer);
|
|
778
763
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
764
|
+
const keys = /* @__PURE__ */ new Set();
|
|
765
|
+
for (const topicName in handlers) {
|
|
766
|
+
for (const consumerGroup in handlers[topicName]) {
|
|
767
|
+
const key = `${topicName}:${consumerGroup}`;
|
|
768
|
+
keys.add(key);
|
|
769
|
+
if (topicName.includes("*")) {
|
|
770
|
+
const weakRef = new WeakRef(routeHandler);
|
|
771
|
+
const existing = wildcardRouteHandlers.get(key) || [];
|
|
772
|
+
cleanupDeadRefs(key, existing);
|
|
773
|
+
const cleanedRefs = wildcardRouteHandlers.get(key) || [];
|
|
774
|
+
cleanedRefs.push(weakRef);
|
|
775
|
+
wildcardRouteHandlers.set(key, cleanedRefs);
|
|
776
|
+
} else {
|
|
777
|
+
devRouteHandlers.set(key, {
|
|
778
|
+
routeHandler,
|
|
779
|
+
topicPattern: topicName
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
784
783
|
}
|
|
785
|
-
|
|
786
|
-
|
|
784
|
+
routeHandlerKeys.set(routeHandler, keys);
|
|
785
|
+
}
|
|
786
|
+
function findRouteHandlersForTopic(topicName) {
|
|
787
|
+
const handlersMap = /* @__PURE__ */ new Map();
|
|
788
|
+
for (const [
|
|
789
|
+
key,
|
|
790
|
+
{ routeHandler, topicPattern }
|
|
791
|
+
] of devRouteHandlers.entries()) {
|
|
792
|
+
const [_, consumerGroup] = key.split(":");
|
|
793
|
+
if (topicPattern === topicName) {
|
|
794
|
+
if (!handlersMap.has(routeHandler)) {
|
|
795
|
+
handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
|
|
796
|
+
}
|
|
797
|
+
handlersMap.get(routeHandler).add(consumerGroup);
|
|
798
|
+
}
|
|
787
799
|
}
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
800
|
+
for (const [key, refs] of wildcardRouteHandlers.entries()) {
|
|
801
|
+
const [pattern, consumerGroup] = key.split(":");
|
|
802
|
+
if (matchesWildcardPattern(topicName, pattern)) {
|
|
803
|
+
cleanupDeadRefs(key, refs);
|
|
804
|
+
const cleanedRefs = wildcardRouteHandlers.get(key) || [];
|
|
805
|
+
for (const ref of cleanedRefs) {
|
|
806
|
+
const routeHandler = ref.deref();
|
|
807
|
+
if (routeHandler) {
|
|
808
|
+
if (!handlersMap.has(routeHandler)) {
|
|
809
|
+
handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
|
|
810
|
+
}
|
|
811
|
+
handlersMap.get(routeHandler).add(consumerGroup);
|
|
812
|
+
}
|
|
794
813
|
}
|
|
795
|
-
} finally {
|
|
796
|
-
reader.releaseLock();
|
|
797
814
|
}
|
|
798
815
|
}
|
|
799
|
-
|
|
816
|
+
return handlersMap;
|
|
817
|
+
}
|
|
818
|
+
function createMockCloudEventRequest(topicName, consumerGroup, messageId) {
|
|
819
|
+
const cloudEvent = {
|
|
820
|
+
type: "com.vercel.queue.v1beta",
|
|
821
|
+
source: `/topic/${topicName}/consumer/${consumerGroup}`,
|
|
822
|
+
id: messageId,
|
|
823
|
+
datacontenttype: "application/json",
|
|
824
|
+
data: {
|
|
825
|
+
messageId,
|
|
826
|
+
queueName: topicName,
|
|
827
|
+
consumerGroup
|
|
828
|
+
},
|
|
829
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
830
|
+
specversion: "1.0"
|
|
831
|
+
};
|
|
832
|
+
return new Request("https://localhost/api/queue/callback", {
|
|
833
|
+
method: "POST",
|
|
834
|
+
headers: {
|
|
835
|
+
"Content-Type": "application/cloudevents+json"
|
|
836
|
+
},
|
|
837
|
+
body: JSON.stringify(cloudEvent)
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
var DEV_CALLBACK_DELAY = 1e3;
|
|
841
|
+
function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
|
|
842
|
+
console.log(
|
|
843
|
+
`[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
|
|
844
|
+
);
|
|
845
|
+
setTimeout(
|
|
846
|
+
() => {
|
|
847
|
+
console.log(
|
|
848
|
+
`[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
|
|
849
|
+
);
|
|
850
|
+
triggerDevCallbacks(topicName, messageId);
|
|
851
|
+
},
|
|
852
|
+
timeoutSeconds * 1e3 + DEV_CALLBACK_DELAY
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
function triggerDevCallbacks(topicName, messageId) {
|
|
856
|
+
const handlersMap = findRouteHandlersForTopic(topicName);
|
|
857
|
+
if (handlersMap.size === 0) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const consumerGroups = Array.from(
|
|
861
|
+
new Set(
|
|
862
|
+
Array.from(handlersMap.values()).flatMap((groups) => Array.from(groups))
|
|
863
|
+
)
|
|
864
|
+
);
|
|
865
|
+
console.log(
|
|
866
|
+
`[Dev Mode] Triggering local callbacks for topic "${topicName}" \u2192 consumers: ${consumerGroups.join(", ")}`
|
|
867
|
+
);
|
|
868
|
+
setTimeout(async () => {
|
|
869
|
+
for (const [routeHandler, consumerGroups2] of handlersMap.entries()) {
|
|
870
|
+
for (const consumerGroup of consumerGroups2) {
|
|
871
|
+
try {
|
|
872
|
+
const request = createMockCloudEventRequest(
|
|
873
|
+
topicName,
|
|
874
|
+
consumerGroup,
|
|
875
|
+
messageId
|
|
876
|
+
);
|
|
877
|
+
const response = await routeHandler(request);
|
|
878
|
+
if (response.ok) {
|
|
879
|
+
try {
|
|
880
|
+
const responseData = await response.json();
|
|
881
|
+
if (responseData.status === "success") {
|
|
882
|
+
console.log(
|
|
883
|
+
`[Dev Mode] Message processed for ${topicName}/${consumerGroup}`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
} catch (jsonError) {
|
|
887
|
+
console.error(
|
|
888
|
+
`[Dev Mode] Failed to parse success response for ${topicName}/${consumerGroup}:`,
|
|
889
|
+
jsonError
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
try {
|
|
894
|
+
const errorData = await response.json();
|
|
895
|
+
console.error(
|
|
896
|
+
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
897
|
+
errorData.error || response.statusText
|
|
898
|
+
);
|
|
899
|
+
} catch (jsonError) {
|
|
900
|
+
console.error(
|
|
901
|
+
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
902
|
+
response.statusText
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
} catch (error) {
|
|
907
|
+
console.error(
|
|
908
|
+
`[Dev Mode] Error triggering callback for ${topicName}/${consumerGroup}:`,
|
|
909
|
+
error
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}, DEV_CALLBACK_DELAY);
|
|
915
|
+
}
|
|
916
|
+
function clearDevHandlers() {
|
|
917
|
+
devRouteHandlers.clear();
|
|
918
|
+
wildcardRouteHandlers.clear();
|
|
919
|
+
}
|
|
920
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
921
|
+
globalThis.__clearDevHandlers = clearDevHandlers;
|
|
922
|
+
}
|
|
800
923
|
|
|
801
924
|
// src/consumer-group.ts
|
|
802
925
|
var ConsumerGroup = class {
|
|
@@ -897,7 +1020,13 @@ var ConsumerGroup = class {
|
|
|
897
1020
|
message.ticket
|
|
898
1021
|
);
|
|
899
1022
|
try {
|
|
900
|
-
const result = await handler(message
|
|
1023
|
+
const result = await handler(message.payload, {
|
|
1024
|
+
messageId: message.messageId,
|
|
1025
|
+
deliveryCount: message.deliveryCount,
|
|
1026
|
+
createdAt: message.createdAt,
|
|
1027
|
+
topicName: this.topicName,
|
|
1028
|
+
consumerGroup: this.consumerGroupName
|
|
1029
|
+
});
|
|
901
1030
|
await stopExtension();
|
|
902
1031
|
if (result && "timeoutSeconds" in result) {
|
|
903
1032
|
await this.client.changeVisibility({
|
|
@@ -907,6 +1036,13 @@ var ConsumerGroup = class {
|
|
|
907
1036
|
ticket: message.ticket,
|
|
908
1037
|
visibilityTimeoutSeconds: result.timeoutSeconds
|
|
909
1038
|
});
|
|
1039
|
+
if (isDevMode()) {
|
|
1040
|
+
scheduleDevTimeout(
|
|
1041
|
+
this.topicName,
|
|
1042
|
+
message.messageId,
|
|
1043
|
+
result.timeoutSeconds
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
910
1046
|
} else {
|
|
911
1047
|
await this.client.deleteMessage({
|
|
912
1048
|
queueName: this.topicName,
|
|
@@ -927,219 +1063,58 @@ var ConsumerGroup = class {
|
|
|
927
1063
|
throw error;
|
|
928
1064
|
}
|
|
929
1065
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
* @param options Processing options
|
|
935
|
-
* @returns Promise that resolves when processing stops (due to signal or error)
|
|
936
|
-
*/
|
|
937
|
-
async subscribe(signal, handler, options = {}) {
|
|
938
|
-
const pollingInterval = options.pollingInterval || 1e3;
|
|
939
|
-
while (!signal.aborted) {
|
|
940
|
-
try {
|
|
941
|
-
for await (const message of this.client.receiveMessages(
|
|
1066
|
+
async consume(handler, options) {
|
|
1067
|
+
if (options?.messageId) {
|
|
1068
|
+
if (options.skipPayload) {
|
|
1069
|
+
const response = await this.client.receiveMessageById(
|
|
942
1070
|
{
|
|
943
1071
|
queueName: this.topicName,
|
|
944
1072
|
consumerGroup: this.consumerGroupName,
|
|
1073
|
+
messageId: options.messageId,
|
|
945
1074
|
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
946
|
-
|
|
947
|
-
// Always process one message at a time
|
|
1075
|
+
skipPayload: true
|
|
948
1076
|
},
|
|
949
1077
|
this.transport
|
|
950
|
-
)
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
988
|
-
continue;
|
|
989
|
-
}
|
|
990
|
-
if (error instanceof MessageLockedError) {
|
|
991
|
-
const waitTime = error.retryAfter ? error.retryAfter * 1e3 : pollingInterval;
|
|
992
|
-
if (!signal.aborted) {
|
|
993
|
-
await new Promise((resolve) => {
|
|
994
|
-
const timeoutId = setTimeout(resolve, waitTime);
|
|
995
|
-
signal.addEventListener(
|
|
996
|
-
"abort",
|
|
997
|
-
() => {
|
|
998
|
-
clearTimeout(timeoutId);
|
|
999
|
-
resolve();
|
|
1000
|
-
},
|
|
1001
|
-
{ once: true }
|
|
1002
|
-
);
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
console.error("Error polling topic:", error);
|
|
1008
|
-
throw error;
|
|
1078
|
+
);
|
|
1079
|
+
await this.processMessage(
|
|
1080
|
+
response.message,
|
|
1081
|
+
handler
|
|
1082
|
+
);
|
|
1083
|
+
} else {
|
|
1084
|
+
const response = await this.client.receiveMessageById(
|
|
1085
|
+
{
|
|
1086
|
+
queueName: this.topicName,
|
|
1087
|
+
consumerGroup: this.consumerGroupName,
|
|
1088
|
+
messageId: options.messageId,
|
|
1089
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1090
|
+
},
|
|
1091
|
+
this.transport
|
|
1092
|
+
);
|
|
1093
|
+
await this.processMessage(
|
|
1094
|
+
response.message,
|
|
1095
|
+
handler
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
} else {
|
|
1099
|
+
let messageFound = false;
|
|
1100
|
+
for await (const message of this.client.receiveMessages(
|
|
1101
|
+
{
|
|
1102
|
+
queueName: this.topicName,
|
|
1103
|
+
consumerGroup: this.consumerGroupName,
|
|
1104
|
+
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1105
|
+
limit: 1
|
|
1106
|
+
},
|
|
1107
|
+
this.transport
|
|
1108
|
+
)) {
|
|
1109
|
+
messageFound = true;
|
|
1110
|
+
await this.processMessage(message, handler);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
if (!messageFound) {
|
|
1114
|
+
throw new Error("No messages available");
|
|
1009
1115
|
}
|
|
1010
1116
|
}
|
|
1011
1117
|
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Receive and process a specific message by its ID with full payload
|
|
1014
|
-
* @param messageId The ID of the message to receive and process
|
|
1015
|
-
* @param handler Function to process the message with full payload
|
|
1016
|
-
* @returns Promise that resolves when the message is processed or rejects with specific errors
|
|
1017
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
1018
|
-
* @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
|
|
1019
|
-
* @throws {MessageLockedError} When the message is temporarily locked (423)
|
|
1020
|
-
* @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
|
|
1021
|
-
* @throws {FailedDependencyError} When FIFO ordering is violated (424)
|
|
1022
|
-
* @throws {MessageCorruptedError} When the message data is corrupted
|
|
1023
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
1024
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
1025
|
-
* @throws {ForbiddenError} When access is denied
|
|
1026
|
-
* @throws {InternalServerError} When server encounters an error
|
|
1027
|
-
*/
|
|
1028
|
-
async receiveMessage(messageId, handler) {
|
|
1029
|
-
const response = await this.client.receiveMessageById(
|
|
1030
|
-
{
|
|
1031
|
-
queueName: this.topicName,
|
|
1032
|
-
consumerGroup: this.consumerGroupName,
|
|
1033
|
-
messageId,
|
|
1034
|
-
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1035
|
-
},
|
|
1036
|
-
this.transport
|
|
1037
|
-
);
|
|
1038
|
-
await this.processMessage(response.message, handler);
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Receive and process the next available message from the queue
|
|
1042
|
-
* @param handler Function to process the message
|
|
1043
|
-
* @returns Promise that resolves when the message is processed or rejects with specific errors
|
|
1044
|
-
* @throws {QueueEmptyError} When no messages are available in the queue (204)
|
|
1045
|
-
* @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
|
|
1046
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
1047
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
1048
|
-
* @throws {ForbiddenError} When access is denied
|
|
1049
|
-
* @throws {InternalServerError} When server encounters an error
|
|
1050
|
-
*/
|
|
1051
|
-
async receiveNextMessage(handler) {
|
|
1052
|
-
let messageFound = false;
|
|
1053
|
-
for await (const message of this.client.receiveMessages(
|
|
1054
|
-
{
|
|
1055
|
-
queueName: this.topicName,
|
|
1056
|
-
consumerGroup: this.consumerGroupName,
|
|
1057
|
-
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1058
|
-
limit: 1
|
|
1059
|
-
},
|
|
1060
|
-
this.transport
|
|
1061
|
-
)) {
|
|
1062
|
-
messageFound = true;
|
|
1063
|
-
await this.processMessage(message, handler);
|
|
1064
|
-
break;
|
|
1065
|
-
}
|
|
1066
|
-
if (!messageFound) {
|
|
1067
|
-
throw new Error("No messages available");
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Receive and process multiple next available messages from the queue
|
|
1072
|
-
* @param limit Number of messages to process (1-10)
|
|
1073
|
-
* @param handler Function to process each message
|
|
1074
|
-
* @returns Promise that resolves to an array of PromiseSettledResult (same as Promise.allSettled)
|
|
1075
|
-
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
1076
|
-
* @throws {QueueEmptyError} When no messages are available in the queue (204)
|
|
1077
|
-
* @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
|
|
1078
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
1079
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
1080
|
-
* @throws {ForbiddenError} When access is denied
|
|
1081
|
-
* @throws {InternalServerError} When server encounters an error
|
|
1082
|
-
*/
|
|
1083
|
-
async receiveNextMessages(limit, handler) {
|
|
1084
|
-
if (limit < 1 || limit > 10) {
|
|
1085
|
-
throw new InvalidLimitError(limit);
|
|
1086
|
-
}
|
|
1087
|
-
const processingPromises = [];
|
|
1088
|
-
let messageCount = 0;
|
|
1089
|
-
for await (const message of this.client.receiveMessages(
|
|
1090
|
-
{
|
|
1091
|
-
queueName: this.topicName,
|
|
1092
|
-
consumerGroup: this.consumerGroupName,
|
|
1093
|
-
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1094
|
-
limit
|
|
1095
|
-
},
|
|
1096
|
-
this.transport
|
|
1097
|
-
)) {
|
|
1098
|
-
messageCount++;
|
|
1099
|
-
const wrappedPromise = this.processMessage(message, handler).then(
|
|
1100
|
-
(value) => ({
|
|
1101
|
-
status: "fulfilled",
|
|
1102
|
-
value
|
|
1103
|
-
}),
|
|
1104
|
-
(reason) => ({ status: "rejected", reason })
|
|
1105
|
-
);
|
|
1106
|
-
processingPromises.push(wrappedPromise);
|
|
1107
|
-
}
|
|
1108
|
-
if (messageCount === 0) {
|
|
1109
|
-
throw new Error("No messages available");
|
|
1110
|
-
}
|
|
1111
|
-
const results = await Promise.all(processingPromises);
|
|
1112
|
-
return results;
|
|
1113
|
-
}
|
|
1114
|
-
/**
|
|
1115
|
-
* Handle a specific message by its ID without downloading the payload (metadata only)
|
|
1116
|
-
* @param messageId The ID of the message to handle
|
|
1117
|
-
* @param handler Function to process the message metadata (payload will be void)
|
|
1118
|
-
* @returns Promise that resolves when the message is handled or rejects with specific errors
|
|
1119
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
1120
|
-
* @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
|
|
1121
|
-
* @throws {MessageLockedError} When the message is temporarily locked (423)
|
|
1122
|
-
* @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
|
|
1123
|
-
* @throws {FailedDependencyError} When FIFO ordering is violated (424)
|
|
1124
|
-
* @throws {MessageCorruptedError} When the message data is corrupted
|
|
1125
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
1126
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
1127
|
-
* @throws {ForbiddenError} When access is denied
|
|
1128
|
-
* @throws {InternalServerError} When server encounters an error
|
|
1129
|
-
*/
|
|
1130
|
-
async handleMessage(messageId, handler) {
|
|
1131
|
-
const response = await this.client.receiveMessageById(
|
|
1132
|
-
{
|
|
1133
|
-
queueName: this.topicName,
|
|
1134
|
-
consumerGroup: this.consumerGroupName,
|
|
1135
|
-
messageId,
|
|
1136
|
-
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1137
|
-
skipPayload: true
|
|
1138
|
-
},
|
|
1139
|
-
this.transport
|
|
1140
|
-
);
|
|
1141
|
-
await this.processMessage(response.message, handler);
|
|
1142
|
-
}
|
|
1143
1118
|
/**
|
|
1144
1119
|
* Get the consumer group name
|
|
1145
1120
|
*/
|
|
@@ -1186,11 +1161,13 @@ var Topic = class {
|
|
|
1186
1161
|
queueName: this.topicName,
|
|
1187
1162
|
payload,
|
|
1188
1163
|
idempotencyKey: options?.idempotencyKey,
|
|
1189
|
-
retentionSeconds: options?.retentionSeconds
|
|
1190
|
-
callbacks: options?.callbacks
|
|
1164
|
+
retentionSeconds: options?.retentionSeconds
|
|
1191
1165
|
},
|
|
1192
1166
|
this.transport
|
|
1193
1167
|
);
|
|
1168
|
+
if (isDevMode()) {
|
|
1169
|
+
triggerDevCallbacks(this.topicName, result.messageId);
|
|
1170
|
+
}
|
|
1194
1171
|
return { messageId: result.messageId };
|
|
1195
1172
|
}
|
|
1196
1173
|
/**
|
|
@@ -1226,53 +1203,59 @@ var Topic = class {
|
|
|
1226
1203
|
};
|
|
1227
1204
|
|
|
1228
1205
|
// src/factory.ts
|
|
1229
|
-
function
|
|
1230
|
-
|
|
1206
|
+
async function send(topicName, payload, options) {
|
|
1207
|
+
const transport = options?.transport || new JsonTransport();
|
|
1208
|
+
const client = new QueueClient();
|
|
1209
|
+
const result = await client.sendMessage(
|
|
1210
|
+
{
|
|
1211
|
+
queueName: topicName,
|
|
1212
|
+
payload,
|
|
1213
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1214
|
+
retentionSeconds: options?.retentionSeconds
|
|
1215
|
+
},
|
|
1216
|
+
transport
|
|
1217
|
+
);
|
|
1218
|
+
if (isDevMode()) {
|
|
1219
|
+
triggerDevCallbacks(topicName, result.messageId);
|
|
1220
|
+
}
|
|
1221
|
+
return { messageId: result.messageId };
|
|
1231
1222
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
const
|
|
1236
|
-
const messageId =
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1223
|
+
async function receive(topicName, consumerGroup, handler, options) {
|
|
1224
|
+
const transport = options?.transport || new JsonTransport();
|
|
1225
|
+
const client = new QueueClient();
|
|
1226
|
+
const topic = new Topic(client, topicName, transport);
|
|
1227
|
+
const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
|
|
1228
|
+
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
1229
|
+
if (messageId) {
|
|
1230
|
+
if (skipPayload) {
|
|
1231
|
+
return consumer.consume(handler, {
|
|
1232
|
+
messageId,
|
|
1233
|
+
skipPayload: true
|
|
1234
|
+
});
|
|
1235
|
+
} else {
|
|
1236
|
+
return consumer.consume(handler, { messageId });
|
|
1237
|
+
}
|
|
1238
|
+
} else {
|
|
1239
|
+
return consumer.consume(handler);
|
|
1247
1240
|
}
|
|
1248
|
-
return {
|
|
1249
|
-
messageId,
|
|
1250
|
-
queueName,
|
|
1251
|
-
consumerGroup
|
|
1252
|
-
};
|
|
1253
1241
|
}
|
|
1254
1242
|
export {
|
|
1255
1243
|
BadRequestError,
|
|
1256
1244
|
BufferTransport,
|
|
1257
|
-
ConsumerGroup,
|
|
1258
|
-
FailedDependencyError,
|
|
1259
|
-
FifoOrderingViolationError,
|
|
1260
1245
|
ForbiddenError,
|
|
1261
1246
|
InternalServerError,
|
|
1262
|
-
InvalidCallbackError,
|
|
1263
1247
|
InvalidLimitError,
|
|
1264
1248
|
JsonTransport,
|
|
1265
1249
|
MessageCorruptedError,
|
|
1266
1250
|
MessageLockedError,
|
|
1267
1251
|
MessageNotAvailableError,
|
|
1268
1252
|
MessageNotFoundError,
|
|
1269
|
-
QueueClient,
|
|
1270
1253
|
QueueEmptyError,
|
|
1271
1254
|
StreamTransport,
|
|
1272
|
-
Topic,
|
|
1273
1255
|
UnauthorizedError,
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1256
|
+
handleCallback,
|
|
1257
|
+
parseCallback,
|
|
1258
|
+
receive,
|
|
1259
|
+
send
|
|
1277
1260
|
};
|
|
1278
1261
|
//# sourceMappingURL=index.mjs.map
|