@vercel/queue 0.0.0-alpha.2 → 0.0.0-alpha.20
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 +238 -754
- package/bin/local-discover.js +168 -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.js
CHANGED
|
@@ -22,154 +22,100 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BadRequestError: () => BadRequestError,
|
|
24
24
|
BufferTransport: () => BufferTransport,
|
|
25
|
-
ConsumerGroup: () => ConsumerGroup,
|
|
26
|
-
FailedDependencyError: () => FailedDependencyError,
|
|
27
|
-
FifoOrderingViolationError: () => FifoOrderingViolationError,
|
|
28
25
|
ForbiddenError: () => ForbiddenError,
|
|
29
26
|
InternalServerError: () => InternalServerError,
|
|
30
|
-
InvalidCallbackError: () => InvalidCallbackError,
|
|
31
27
|
InvalidLimitError: () => InvalidLimitError,
|
|
32
28
|
JsonTransport: () => JsonTransport,
|
|
33
29
|
MessageCorruptedError: () => MessageCorruptedError,
|
|
34
30
|
MessageLockedError: () => MessageLockedError,
|
|
35
31
|
MessageNotAvailableError: () => MessageNotAvailableError,
|
|
36
32
|
MessageNotFoundError: () => MessageNotFoundError,
|
|
37
|
-
QueueClient: () => QueueClient,
|
|
38
33
|
QueueEmptyError: () => QueueEmptyError,
|
|
39
34
|
StreamTransport: () => StreamTransport,
|
|
40
|
-
Topic: () => Topic,
|
|
41
35
|
UnauthorizedError: () => UnauthorizedError,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
handleCallback: () => handleCallback,
|
|
37
|
+
parseCallback: () => parseCallback,
|
|
38
|
+
receive: () => receive,
|
|
39
|
+
send: () => send
|
|
45
40
|
});
|
|
46
41
|
module.exports = __toCommonJS(index_exports);
|
|
47
42
|
|
|
48
|
-
// src/
|
|
49
|
-
async function
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
|
|
54
|
-
if (!token) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
return token;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// src/client.ts
|
|
63
|
-
var import_mixpart = require("mixpart");
|
|
64
|
-
|
|
65
|
-
// src/local.ts
|
|
66
|
-
var import_node_child_process = require("child_process");
|
|
67
|
-
function isLocalhostWithPort(url) {
|
|
43
|
+
// src/transports.ts
|
|
44
|
+
async function streamToBuffer(stream) {
|
|
45
|
+
let totalLength = 0;
|
|
46
|
+
const reader = stream.getReader();
|
|
47
|
+
const chunks = [];
|
|
68
48
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
49
|
+
while (true) {
|
|
50
|
+
const { done, value } = await reader.read();
|
|
51
|
+
if (done) break;
|
|
52
|
+
chunks.push(value);
|
|
53
|
+
totalLength += value.length;
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
reader.releaseLock();
|
|
75
57
|
}
|
|
58
|
+
return Buffer.concat(chunks, totalLength);
|
|
76
59
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return [];
|
|
60
|
+
var JsonTransport = class {
|
|
61
|
+
contentType = "application/json";
|
|
62
|
+
replacer;
|
|
63
|
+
reviver;
|
|
64
|
+
constructor(options = {}) {
|
|
65
|
+
this.replacer = options.replacer;
|
|
66
|
+
this.reviver = options.reviver;
|
|
85
67
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const { isLocalhost } = isLocalhostWithPort(config.url);
|
|
89
|
-
return isLocalhost;
|
|
90
|
-
});
|
|
91
|
-
if (hasLocalhostCallbacks) {
|
|
92
|
-
console.warn(
|
|
93
|
-
`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.`
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
return [];
|
|
68
|
+
serialize(value) {
|
|
69
|
+
return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
|
|
97
70
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (isLocalhost && port && port > 0) {
|
|
102
|
-
localhostCallbacks.push({ group, config, port });
|
|
103
|
-
} else {
|
|
104
|
-
console.warn(
|
|
105
|
-
`Queue Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
return localhostCallbacks;
|
|
110
|
-
}
|
|
111
|
-
function fireLocalhostCallbacks(localhostCallbacks, queueName, responseData) {
|
|
112
|
-
localhostCallbacks.forEach(({ group, config, port }) => {
|
|
113
|
-
const callbackHeaders = new Headers();
|
|
114
|
-
callbackHeaders.set("Vqs-Message-Id", responseData.messageId);
|
|
115
|
-
callbackHeaders.set("Vqs-Queue-Name", queueName);
|
|
116
|
-
callbackHeaders.set("Vqs-Consumer-Group", group);
|
|
117
|
-
fireAndForgetWaitForHttpReady(
|
|
118
|
-
config.url,
|
|
119
|
-
port,
|
|
120
|
-
config.delay || 0,
|
|
121
|
-
3,
|
|
122
|
-
// Default retry frequency
|
|
123
|
-
callbackHeaders
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
function fireAndForgetWaitForHttpReady(url, port, initialDelaySeconds = 0, retryFrequencySeconds = 3, headers) {
|
|
128
|
-
if (!isSupportedPlatform()) {
|
|
129
|
-
console.warn(
|
|
130
|
-
`Queue: fireAndForgetWaitForHttpReady is not supported on ${process.platform}. This function requires bash, nc, and curl which are available on macOS and Linux only.`
|
|
131
|
-
);
|
|
132
|
-
return;
|
|
71
|
+
async deserialize(stream) {
|
|
72
|
+
const buffer = await streamToBuffer(stream);
|
|
73
|
+
return JSON.parse(buffer.toString("utf8"), this.reviver);
|
|
133
74
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
75
|
+
};
|
|
76
|
+
var BufferTransport = class {
|
|
77
|
+
contentType = "application/octet-stream";
|
|
78
|
+
serialize(value) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
async deserialize(stream) {
|
|
82
|
+
return await streamToBuffer(stream);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var StreamTransport = class {
|
|
86
|
+
contentType = "application/octet-stream";
|
|
87
|
+
serialize(value) {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
async deserialize(stream) {
|
|
91
|
+
return stream;
|
|
141
92
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
93
|
+
async finalize(payload) {
|
|
94
|
+
const reader = payload.getReader();
|
|
95
|
+
try {
|
|
96
|
+
while (true) {
|
|
97
|
+
const { done } = await reader.read();
|
|
98
|
+
if (done) break;
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
reader.releaseLock();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
145
105
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
# If closed twice in a row, give up immediately
|
|
160
|
-
if [ "$missed" -ge 2 ]; then
|
|
161
|
-
exit 1
|
|
162
|
-
fi
|
|
163
|
-
fi
|
|
164
|
-
# Wait before next cycle
|
|
165
|
-
sleep ${retryFrequencySeconds}
|
|
166
|
-
done
|
|
167
|
-
`;
|
|
168
|
-
const childProcess = (0, import_node_child_process.spawn)("bash", ["-c", bashScript], {
|
|
169
|
-
stdio: "ignore",
|
|
170
|
-
detached: true
|
|
171
|
-
});
|
|
172
|
-
childProcess.unref();
|
|
106
|
+
// src/client.ts
|
|
107
|
+
var import_mixpart = require("mixpart");
|
|
108
|
+
|
|
109
|
+
// src/oidc.ts
|
|
110
|
+
function getVercelOidcToken() {
|
|
111
|
+
const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
|
|
112
|
+
const fromSymbol = globalThis;
|
|
113
|
+
const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
|
|
114
|
+
const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
|
|
115
|
+
if (!token) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return token;
|
|
173
119
|
}
|
|
174
120
|
|
|
175
121
|
// src/types.ts
|
|
@@ -187,16 +133,6 @@ var MessageNotAvailableError = class extends Error {
|
|
|
187
133
|
this.name = "MessageNotAvailableError";
|
|
188
134
|
}
|
|
189
135
|
};
|
|
190
|
-
var FifoOrderingViolationError = class extends Error {
|
|
191
|
-
nextMessageId;
|
|
192
|
-
constructor(messageId, nextMessageId, reason) {
|
|
193
|
-
super(
|
|
194
|
-
`FIFO ordering violation for message ${messageId}: ${reason}. Process message ${nextMessageId} first.`
|
|
195
|
-
);
|
|
196
|
-
this.name = "FifoOrderingViolationError";
|
|
197
|
-
this.nextMessageId = nextMessageId;
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
136
|
var MessageCorruptedError = class extends Error {
|
|
201
137
|
constructor(messageId, reason) {
|
|
202
138
|
super(`Message ${messageId} is corrupted: ${reason}`);
|
|
@@ -238,16 +174,6 @@ var BadRequestError = class extends Error {
|
|
|
238
174
|
this.name = "BadRequestError";
|
|
239
175
|
}
|
|
240
176
|
};
|
|
241
|
-
var FailedDependencyError = class extends Error {
|
|
242
|
-
nextMessageId;
|
|
243
|
-
constructor(messageId, nextMessageId) {
|
|
244
|
-
super(
|
|
245
|
-
`Failed dependency: FIFO ordering violation for message ${messageId}. Must process message ${nextMessageId} first.`
|
|
246
|
-
);
|
|
247
|
-
this.name = "FailedDependencyError";
|
|
248
|
-
this.nextMessageId = nextMessageId;
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
177
|
var InternalServerError = class extends Error {
|
|
252
178
|
constructor(message = "Unexpected server error") {
|
|
253
179
|
super(message);
|
|
@@ -260,12 +186,6 @@ var InvalidLimitError = class extends Error {
|
|
|
260
186
|
this.name = "InvalidLimitError";
|
|
261
187
|
}
|
|
262
188
|
};
|
|
263
|
-
var InvalidCallbackError = class extends Error {
|
|
264
|
-
constructor(message) {
|
|
265
|
-
super(message);
|
|
266
|
-
this.name = "InvalidCallbackError";
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
189
|
|
|
270
190
|
// src/client.ts
|
|
271
191
|
async function consumeStream(stream) {
|
|
@@ -295,40 +215,33 @@ function parseQueueHeaders(headers) {
|
|
|
295
215
|
return {
|
|
296
216
|
messageId,
|
|
297
217
|
deliveryCount,
|
|
298
|
-
timestamp,
|
|
218
|
+
createdAt: new Date(timestamp),
|
|
299
219
|
contentType,
|
|
300
220
|
ticket
|
|
301
221
|
};
|
|
302
222
|
}
|
|
303
|
-
var QueueClient = class
|
|
223
|
+
var QueueClient = class {
|
|
304
224
|
baseUrl;
|
|
225
|
+
basePath;
|
|
305
226
|
token;
|
|
306
227
|
/**
|
|
307
228
|
* Create a new Vercel Queue Service client
|
|
308
|
-
* @param options Client configuration options
|
|
229
|
+
* @param options Client configuration options (optional - will auto-detect Vercel Function environment)
|
|
309
230
|
*/
|
|
310
|
-
constructor(options) {
|
|
311
|
-
this.baseUrl = options.baseUrl || "https://
|
|
312
|
-
this.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (!token) {
|
|
324
|
-
throw new Error(
|
|
325
|
-
"Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment."
|
|
326
|
-
);
|
|
231
|
+
constructor(options = {}) {
|
|
232
|
+
this.baseUrl = options.baseUrl || "https://vercel-queue.com";
|
|
233
|
+
this.basePath = options.basePath || "/api/v2/messages";
|
|
234
|
+
if (options.token) {
|
|
235
|
+
this.token = options.token;
|
|
236
|
+
} else {
|
|
237
|
+
const token = getVercelOidcToken();
|
|
238
|
+
if (!token) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"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'"
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
this.token = token;
|
|
327
244
|
}
|
|
328
|
-
return new _QueueClient({
|
|
329
|
-
token,
|
|
330
|
-
baseUrl
|
|
331
|
-
});
|
|
332
245
|
}
|
|
333
246
|
/**
|
|
334
247
|
* Send a message to a queue
|
|
@@ -341,40 +254,23 @@ var QueueClient = class _QueueClient {
|
|
|
341
254
|
* @throws {InternalServerError} When server encounters an error
|
|
342
255
|
*/
|
|
343
256
|
async sendMessage(options, transport) {
|
|
344
|
-
const { queueName, payload, idempotencyKey, retentionSeconds
|
|
257
|
+
const { queueName, payload, idempotencyKey, retentionSeconds } = options;
|
|
345
258
|
const headers = new Headers({
|
|
346
259
|
Authorization: `Bearer ${this.token}`,
|
|
347
260
|
"Vqs-Queue-Name": queueName,
|
|
348
261
|
"Content-Type": transport.contentType
|
|
349
262
|
});
|
|
263
|
+
if (process.env.VERCEL_DEPLOYMENT_ID) {
|
|
264
|
+
headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
|
|
265
|
+
}
|
|
350
266
|
if (idempotencyKey) {
|
|
351
267
|
headers.set("Vqs-Idempotency-Key", idempotencyKey);
|
|
352
268
|
}
|
|
353
269
|
if (retentionSeconds !== void 0) {
|
|
354
270
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
355
271
|
}
|
|
356
|
-
let localhostCallbacks = [];
|
|
357
|
-
if (callbacks) {
|
|
358
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
359
|
-
if (isDevelopment) {
|
|
360
|
-
localhostCallbacks = processDevelopmentCallbacks(callbacks);
|
|
361
|
-
} else {
|
|
362
|
-
const endpoints = Object.entries(callbacks).map(
|
|
363
|
-
([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
|
|
364
|
-
).join(",");
|
|
365
|
-
headers.set("Vqs-Callback-Url", endpoints);
|
|
366
|
-
const delays = Object.entries(callbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
|
|
367
|
-
if (delays) {
|
|
368
|
-
headers.set("Vqs-Callback-Delay", delays);
|
|
369
|
-
}
|
|
370
|
-
const frequencies = Object.entries(callbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
|
|
371
|
-
if (frequencies) {
|
|
372
|
-
headers.set("Vqs-Callback-Frequency", frequencies);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
272
|
const body = transport.serialize(payload);
|
|
377
|
-
const response = await fetch(`${this.baseUrl}
|
|
273
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
378
274
|
method: "POST",
|
|
379
275
|
headers,
|
|
380
276
|
body
|
|
@@ -403,9 +299,6 @@ var QueueClient = class _QueueClient {
|
|
|
403
299
|
);
|
|
404
300
|
}
|
|
405
301
|
const responseData = await response.json();
|
|
406
|
-
if (localhostCallbacks.length > 0) {
|
|
407
|
-
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
408
|
-
}
|
|
409
302
|
return responseData;
|
|
410
303
|
}
|
|
411
304
|
/**
|
|
@@ -415,7 +308,7 @@ var QueueClient = class _QueueClient {
|
|
|
415
308
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
416
309
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
417
310
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
418
|
-
* @throws {MessageLockedError} When
|
|
311
|
+
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
419
312
|
* @throws {BadRequestError} When request parameters are invalid
|
|
420
313
|
* @throws {UnauthorizedError} When authentication fails
|
|
421
314
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -441,7 +334,7 @@ var QueueClient = class _QueueClient {
|
|
|
441
334
|
if (limit !== void 0) {
|
|
442
335
|
headers.set("Vqs-Limit", limit.toString());
|
|
443
336
|
}
|
|
444
|
-
const response = await fetch(`${this.baseUrl}
|
|
337
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
445
338
|
method: "GET",
|
|
446
339
|
headers
|
|
447
340
|
});
|
|
@@ -466,7 +359,7 @@ var QueueClient = class _QueueClient {
|
|
|
466
359
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
467
360
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
468
361
|
}
|
|
469
|
-
throw new MessageLockedError("next message
|
|
362
|
+
throw new MessageLockedError("next message", retryAfter);
|
|
470
363
|
}
|
|
471
364
|
if (response.status >= 500) {
|
|
472
365
|
throw new InternalServerError(
|
|
@@ -523,7 +416,7 @@ var QueueClient = class _QueueClient {
|
|
|
523
416
|
headers.set("Vqs-Skip-Payload", "1");
|
|
524
417
|
}
|
|
525
418
|
const response = await fetch(
|
|
526
|
-
`${this.baseUrl}
|
|
419
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
527
420
|
{
|
|
528
421
|
method: "GET",
|
|
529
422
|
headers
|
|
@@ -552,37 +445,7 @@ var QueueClient = class _QueueClient {
|
|
|
552
445
|
}
|
|
553
446
|
throw new MessageLockedError(messageId, retryAfter);
|
|
554
447
|
}
|
|
555
|
-
if (response.status === 424) {
|
|
556
|
-
try {
|
|
557
|
-
const errorData = await response.json();
|
|
558
|
-
if (errorData.meta?.nextMessageId) {
|
|
559
|
-
throw new FailedDependencyError(
|
|
560
|
-
messageId,
|
|
561
|
-
errorData.meta.nextMessageId
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
} catch (parseError) {
|
|
565
|
-
if (parseError instanceof FailedDependencyError) {
|
|
566
|
-
throw parseError;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
throw new MessageNotAvailableError(
|
|
570
|
-
messageId,
|
|
571
|
-
"FIFO ordering violation"
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
448
|
if (response.status === 409) {
|
|
575
|
-
try {
|
|
576
|
-
const errorData = await response.json();
|
|
577
|
-
if (errorData.nextMessageId) {
|
|
578
|
-
throw new FifoOrderingViolationError(
|
|
579
|
-
messageId,
|
|
580
|
-
errorData.nextMessageId,
|
|
581
|
-
errorData.error
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
} catch (parseError) {
|
|
585
|
-
}
|
|
586
449
|
throw new MessageNotAvailableError(messageId);
|
|
587
450
|
}
|
|
588
451
|
if (response.status >= 500) {
|
|
@@ -662,7 +525,7 @@ var QueueClient = class _QueueClient {
|
|
|
662
525
|
async deleteMessage(options) {
|
|
663
526
|
const { queueName, consumerGroup, messageId, ticket } = options;
|
|
664
527
|
const response = await fetch(
|
|
665
|
-
`${this.baseUrl}
|
|
528
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
666
529
|
{
|
|
667
530
|
method: "DELETE",
|
|
668
531
|
headers: new Headers({
|
|
@@ -723,7 +586,7 @@ var QueueClient = class _QueueClient {
|
|
|
723
586
|
visibilityTimeoutSeconds
|
|
724
587
|
} = options;
|
|
725
588
|
const response = await fetch(
|
|
726
|
-
`${this.baseUrl}
|
|
589
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
727
590
|
{
|
|
728
591
|
method: "PATCH",
|
|
729
592
|
headers: new Headers({
|
|
@@ -769,81 +632,336 @@ var QueueClient = class _QueueClient {
|
|
|
769
632
|
}
|
|
770
633
|
};
|
|
771
634
|
|
|
772
|
-
// src/
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
635
|
+
// src/callback.ts
|
|
636
|
+
function validateWildcardPattern(pattern) {
|
|
637
|
+
const firstIndex = pattern.indexOf("*");
|
|
638
|
+
const lastIndex = pattern.lastIndexOf("*");
|
|
639
|
+
if (firstIndex !== lastIndex) {
|
|
640
|
+
return false;
|
|
777
641
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
642
|
+
if (firstIndex === -1) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
if (firstIndex !== pattern.length - 1) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
function matchesWildcardPattern(topicName, pattern) {
|
|
651
|
+
const prefix = pattern.slice(0, -1);
|
|
652
|
+
return topicName.startsWith(prefix);
|
|
653
|
+
}
|
|
654
|
+
function findTopicHandler(queueName, handlers) {
|
|
655
|
+
const exactHandler = handlers[queueName];
|
|
656
|
+
if (exactHandler) {
|
|
657
|
+
return exactHandler;
|
|
658
|
+
}
|
|
659
|
+
for (const pattern in handlers) {
|
|
660
|
+
if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
|
|
661
|
+
return handlers[pattern];
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
async function parseCallback(request) {
|
|
667
|
+
const contentType = request.headers.get("content-type");
|
|
668
|
+
if (!contentType || !contentType.includes("application/cloudevents+json")) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
"Invalid content type: expected 'application/cloudevents+json'"
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
let cloudEvent;
|
|
674
|
+
try {
|
|
675
|
+
cloudEvent = await request.json();
|
|
676
|
+
} catch (error) {
|
|
677
|
+
throw new Error("Failed to parse CloudEvent from request body");
|
|
678
|
+
}
|
|
679
|
+
if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
|
|
680
|
+
throw new Error("Invalid CloudEvent: missing required fields");
|
|
681
|
+
}
|
|
682
|
+
if (cloudEvent.type !== "com.vercel.queue.v1beta") {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
const missingFields = [];
|
|
688
|
+
if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
|
|
689
|
+
if (!("consumerGroup" in cloudEvent.data))
|
|
690
|
+
missingFields.push("consumerGroup");
|
|
691
|
+
if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
|
|
692
|
+
if (missingFields.length > 0) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Missing required CloudEvent data fields: ${missingFields.join(", ")}`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
const { messageId, queueName, consumerGroup } = cloudEvent.data;
|
|
698
|
+
return {
|
|
699
|
+
queueName,
|
|
700
|
+
consumerGroup,
|
|
701
|
+
messageId
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function handleCallback(handlers) {
|
|
705
|
+
for (const topicPattern in handlers) {
|
|
706
|
+
if (topicPattern.includes("*")) {
|
|
707
|
+
if (!validateWildcardPattern(topicPattern)) {
|
|
708
|
+
throw new Error(
|
|
709
|
+
`Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
|
|
710
|
+
);
|
|
786
711
|
}
|
|
787
|
-
} finally {
|
|
788
|
-
reader.releaseLock();
|
|
789
712
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
713
|
+
}
|
|
714
|
+
const routeHandler = async (request) => {
|
|
715
|
+
try {
|
|
716
|
+
const { queueName, consumerGroup, messageId } = await parseCallback(request);
|
|
717
|
+
const topicHandler = findTopicHandler(queueName, handlers);
|
|
718
|
+
if (!topicHandler) {
|
|
719
|
+
const availableTopics = Object.keys(handlers).join(", ");
|
|
720
|
+
return Response.json(
|
|
721
|
+
{
|
|
722
|
+
error: `No handler found for topic: ${queueName}`,
|
|
723
|
+
availableTopics
|
|
724
|
+
},
|
|
725
|
+
{ status: 404 }
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
729
|
+
if (!consumerGroupHandler) {
|
|
730
|
+
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
731
|
+
return Response.json(
|
|
732
|
+
{
|
|
733
|
+
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
734
|
+
availableGroups
|
|
735
|
+
},
|
|
736
|
+
{ status: 404 }
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
const client = new QueueClient();
|
|
740
|
+
const topic = new Topic(client, queueName);
|
|
741
|
+
const cg = topic.consumerGroup(consumerGroup);
|
|
742
|
+
await cg.consume(consumerGroupHandler, { messageId });
|
|
743
|
+
return Response.json({ status: "success" });
|
|
744
|
+
} catch (error) {
|
|
745
|
+
console.error("Queue callback error:", error);
|
|
746
|
+
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"))) {
|
|
747
|
+
return Response.json({ error: error.message }, { status: 400 });
|
|
748
|
+
}
|
|
749
|
+
return Response.json(
|
|
750
|
+
{ error: "Failed to process queue message" },
|
|
751
|
+
{ status: 500 }
|
|
752
|
+
);
|
|
796
753
|
}
|
|
797
|
-
|
|
754
|
+
};
|
|
755
|
+
if (isDevMode()) {
|
|
756
|
+
registerDevRouteHandler(routeHandler, handlers);
|
|
798
757
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
758
|
+
return routeHandler;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/dev.ts
|
|
762
|
+
var devRouteHandlers = /* @__PURE__ */ new Map();
|
|
763
|
+
var wildcardRouteHandlers = /* @__PURE__ */ new Map();
|
|
764
|
+
var routeHandlerKeys = /* @__PURE__ */ new WeakMap();
|
|
765
|
+
function cleanupDeadRefs(key, refs) {
|
|
766
|
+
const aliveRefs = refs.filter((ref) => ref.deref() !== void 0);
|
|
767
|
+
if (aliveRefs.length === 0) {
|
|
768
|
+
wildcardRouteHandlers.delete(key);
|
|
769
|
+
} else if (aliveRefs.length < refs.length) {
|
|
770
|
+
wildcardRouteHandlers.set(key, aliveRefs);
|
|
804
771
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
772
|
+
}
|
|
773
|
+
function isDevMode() {
|
|
774
|
+
return process.env.NODE_ENV === "development";
|
|
775
|
+
}
|
|
776
|
+
function registerDevRouteHandler(routeHandler, handlers) {
|
|
777
|
+
const existingKeys = routeHandlerKeys.get(routeHandler);
|
|
778
|
+
if (existingKeys) {
|
|
779
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
780
|
+
for (const topicName in handlers) {
|
|
781
|
+
for (const consumerGroup in handlers[topicName]) {
|
|
782
|
+
newKeys.add(`${topicName}:${consumerGroup}`);
|
|
813
783
|
}
|
|
814
|
-
} finally {
|
|
815
|
-
reader.releaseLock();
|
|
816
784
|
}
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
785
|
+
for (const key of existingKeys) {
|
|
786
|
+
if (!newKeys.has(key)) {
|
|
787
|
+
const [topicPattern] = key.split(":");
|
|
788
|
+
if (topicPattern.includes("*")) {
|
|
789
|
+
const refs = wildcardRouteHandlers.get(key);
|
|
790
|
+
if (refs) {
|
|
791
|
+
const filteredRefs = refs.filter(
|
|
792
|
+
(ref) => ref.deref() !== routeHandler
|
|
793
|
+
);
|
|
794
|
+
if (filteredRefs.length === 0) {
|
|
795
|
+
wildcardRouteHandlers.delete(key);
|
|
796
|
+
} else {
|
|
797
|
+
wildcardRouteHandlers.set(key, filteredRefs);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
} else {
|
|
801
|
+
devRouteHandlers.delete(key);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
823
804
|
}
|
|
824
|
-
return Buffer.from(buffer);
|
|
825
805
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
806
|
+
const keys = /* @__PURE__ */ new Set();
|
|
807
|
+
for (const topicName in handlers) {
|
|
808
|
+
for (const consumerGroup in handlers[topicName]) {
|
|
809
|
+
const key = `${topicName}:${consumerGroup}`;
|
|
810
|
+
keys.add(key);
|
|
811
|
+
if (topicName.includes("*")) {
|
|
812
|
+
const weakRef = new WeakRef(routeHandler);
|
|
813
|
+
const existing = wildcardRouteHandlers.get(key) || [];
|
|
814
|
+
cleanupDeadRefs(key, existing);
|
|
815
|
+
const cleanedRefs = wildcardRouteHandlers.get(key) || [];
|
|
816
|
+
cleanedRefs.push(weakRef);
|
|
817
|
+
wildcardRouteHandlers.set(key, cleanedRefs);
|
|
818
|
+
} else {
|
|
819
|
+
devRouteHandlers.set(key, {
|
|
820
|
+
routeHandler,
|
|
821
|
+
topicPattern: topicName
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
831
825
|
}
|
|
832
|
-
|
|
833
|
-
|
|
826
|
+
routeHandlerKeys.set(routeHandler, keys);
|
|
827
|
+
}
|
|
828
|
+
function findRouteHandlersForTopic(topicName) {
|
|
829
|
+
const handlersMap = /* @__PURE__ */ new Map();
|
|
830
|
+
for (const [
|
|
831
|
+
key,
|
|
832
|
+
{ routeHandler, topicPattern }
|
|
833
|
+
] of devRouteHandlers.entries()) {
|
|
834
|
+
const [_, consumerGroup] = key.split(":");
|
|
835
|
+
if (topicPattern === topicName) {
|
|
836
|
+
if (!handlersMap.has(routeHandler)) {
|
|
837
|
+
handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
|
|
838
|
+
}
|
|
839
|
+
handlersMap.get(routeHandler).add(consumerGroup);
|
|
840
|
+
}
|
|
834
841
|
}
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
842
|
+
for (const [key, refs] of wildcardRouteHandlers.entries()) {
|
|
843
|
+
const [pattern, consumerGroup] = key.split(":");
|
|
844
|
+
if (matchesWildcardPattern(topicName, pattern)) {
|
|
845
|
+
cleanupDeadRefs(key, refs);
|
|
846
|
+
const cleanedRefs = wildcardRouteHandlers.get(key) || [];
|
|
847
|
+
for (const ref of cleanedRefs) {
|
|
848
|
+
const routeHandler = ref.deref();
|
|
849
|
+
if (routeHandler) {
|
|
850
|
+
if (!handlersMap.has(routeHandler)) {
|
|
851
|
+
handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
|
|
852
|
+
}
|
|
853
|
+
handlersMap.get(routeHandler).add(consumerGroup);
|
|
854
|
+
}
|
|
841
855
|
}
|
|
842
|
-
} finally {
|
|
843
|
-
reader.releaseLock();
|
|
844
856
|
}
|
|
845
857
|
}
|
|
846
|
-
|
|
858
|
+
return handlersMap;
|
|
859
|
+
}
|
|
860
|
+
function createMockCloudEventRequest(topicName, consumerGroup, messageId) {
|
|
861
|
+
const cloudEvent = {
|
|
862
|
+
type: "com.vercel.queue.v1beta",
|
|
863
|
+
source: `/topic/${topicName}/consumer/${consumerGroup}`,
|
|
864
|
+
id: messageId,
|
|
865
|
+
datacontenttype: "application/json",
|
|
866
|
+
data: {
|
|
867
|
+
messageId,
|
|
868
|
+
queueName: topicName,
|
|
869
|
+
consumerGroup
|
|
870
|
+
},
|
|
871
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
872
|
+
specversion: "1.0"
|
|
873
|
+
};
|
|
874
|
+
return new Request("https://localhost/api/queue/callback", {
|
|
875
|
+
method: "POST",
|
|
876
|
+
headers: {
|
|
877
|
+
"Content-Type": "application/cloudevents+json"
|
|
878
|
+
},
|
|
879
|
+
body: JSON.stringify(cloudEvent)
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
var DEV_CALLBACK_DELAY = 1e3;
|
|
883
|
+
function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
|
|
884
|
+
console.log(
|
|
885
|
+
`[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
|
|
886
|
+
);
|
|
887
|
+
setTimeout(
|
|
888
|
+
() => {
|
|
889
|
+
console.log(
|
|
890
|
+
`[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
|
|
891
|
+
);
|
|
892
|
+
triggerDevCallbacks(topicName, messageId);
|
|
893
|
+
},
|
|
894
|
+
timeoutSeconds * 1e3 + DEV_CALLBACK_DELAY
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
function triggerDevCallbacks(topicName, messageId) {
|
|
898
|
+
const handlersMap = findRouteHandlersForTopic(topicName);
|
|
899
|
+
if (handlersMap.size === 0) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const consumerGroups = Array.from(
|
|
903
|
+
new Set(
|
|
904
|
+
Array.from(handlersMap.values()).flatMap((groups) => Array.from(groups))
|
|
905
|
+
)
|
|
906
|
+
);
|
|
907
|
+
console.log(
|
|
908
|
+
`[Dev Mode] Triggering local callbacks for topic "${topicName}" \u2192 consumers: ${consumerGroups.join(", ")}`
|
|
909
|
+
);
|
|
910
|
+
setTimeout(async () => {
|
|
911
|
+
for (const [routeHandler, consumerGroups2] of handlersMap.entries()) {
|
|
912
|
+
for (const consumerGroup of consumerGroups2) {
|
|
913
|
+
try {
|
|
914
|
+
const request = createMockCloudEventRequest(
|
|
915
|
+
topicName,
|
|
916
|
+
consumerGroup,
|
|
917
|
+
messageId
|
|
918
|
+
);
|
|
919
|
+
const response = await routeHandler(request);
|
|
920
|
+
if (response.ok) {
|
|
921
|
+
try {
|
|
922
|
+
const responseData = await response.json();
|
|
923
|
+
if (responseData.status === "success") {
|
|
924
|
+
console.log(
|
|
925
|
+
`[Dev Mode] Message processed for ${topicName}/${consumerGroup}`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
} catch (jsonError) {
|
|
929
|
+
console.error(
|
|
930
|
+
`[Dev Mode] Failed to parse success response for ${topicName}/${consumerGroup}:`,
|
|
931
|
+
jsonError
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
try {
|
|
936
|
+
const errorData = await response.json();
|
|
937
|
+
console.error(
|
|
938
|
+
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
939
|
+
errorData.error || response.statusText
|
|
940
|
+
);
|
|
941
|
+
} catch (jsonError) {
|
|
942
|
+
console.error(
|
|
943
|
+
`[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
|
|
944
|
+
response.statusText
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
} catch (error) {
|
|
949
|
+
console.error(
|
|
950
|
+
`[Dev Mode] Error triggering callback for ${topicName}/${consumerGroup}:`,
|
|
951
|
+
error
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}, DEV_CALLBACK_DELAY);
|
|
957
|
+
}
|
|
958
|
+
function clearDevHandlers() {
|
|
959
|
+
devRouteHandlers.clear();
|
|
960
|
+
wildcardRouteHandlers.clear();
|
|
961
|
+
}
|
|
962
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
963
|
+
globalThis.__clearDevHandlers = clearDevHandlers;
|
|
964
|
+
}
|
|
847
965
|
|
|
848
966
|
// src/consumer-group.ts
|
|
849
967
|
var ConsumerGroup = class {
|
|
@@ -944,7 +1062,13 @@ var ConsumerGroup = class {
|
|
|
944
1062
|
message.ticket
|
|
945
1063
|
);
|
|
946
1064
|
try {
|
|
947
|
-
const result = await handler(message
|
|
1065
|
+
const result = await handler(message.payload, {
|
|
1066
|
+
messageId: message.messageId,
|
|
1067
|
+
deliveryCount: message.deliveryCount,
|
|
1068
|
+
createdAt: message.createdAt,
|
|
1069
|
+
topicName: this.topicName,
|
|
1070
|
+
consumerGroup: this.consumerGroupName
|
|
1071
|
+
});
|
|
948
1072
|
await stopExtension();
|
|
949
1073
|
if (result && "timeoutSeconds" in result) {
|
|
950
1074
|
await this.client.changeVisibility({
|
|
@@ -954,6 +1078,13 @@ var ConsumerGroup = class {
|
|
|
954
1078
|
ticket: message.ticket,
|
|
955
1079
|
visibilityTimeoutSeconds: result.timeoutSeconds
|
|
956
1080
|
});
|
|
1081
|
+
if (isDevMode()) {
|
|
1082
|
+
scheduleDevTimeout(
|
|
1083
|
+
this.topicName,
|
|
1084
|
+
message.messageId,
|
|
1085
|
+
result.timeoutSeconds
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
957
1088
|
} else {
|
|
958
1089
|
await this.client.deleteMessage({
|
|
959
1090
|
queueName: this.topicName,
|
|
@@ -974,219 +1105,58 @@ var ConsumerGroup = class {
|
|
|
974
1105
|
throw error;
|
|
975
1106
|
}
|
|
976
1107
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
* @param options Processing options
|
|
982
|
-
* @returns Promise that resolves when processing stops (due to signal or error)
|
|
983
|
-
*/
|
|
984
|
-
async subscribe(signal, handler, options = {}) {
|
|
985
|
-
const pollingInterval = options.pollingInterval || 1e3;
|
|
986
|
-
while (!signal.aborted) {
|
|
987
|
-
try {
|
|
988
|
-
for await (const message of this.client.receiveMessages(
|
|
1108
|
+
async consume(handler, options) {
|
|
1109
|
+
if (options?.messageId) {
|
|
1110
|
+
if (options.skipPayload) {
|
|
1111
|
+
const response = await this.client.receiveMessageById(
|
|
989
1112
|
{
|
|
990
1113
|
queueName: this.topicName,
|
|
991
1114
|
consumerGroup: this.consumerGroupName,
|
|
1115
|
+
messageId: options.messageId,
|
|
992
1116
|
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
993
|
-
|
|
994
|
-
// Always process one message at a time
|
|
1117
|
+
skipPayload: true
|
|
995
1118
|
},
|
|
996
1119
|
this.transport
|
|
997
|
-
)
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1035
|
-
continue;
|
|
1036
|
-
}
|
|
1037
|
-
if (error instanceof MessageLockedError) {
|
|
1038
|
-
const waitTime = error.retryAfter ? error.retryAfter * 1e3 : pollingInterval;
|
|
1039
|
-
if (!signal.aborted) {
|
|
1040
|
-
await new Promise((resolve) => {
|
|
1041
|
-
const timeoutId = setTimeout(resolve, waitTime);
|
|
1042
|
-
signal.addEventListener(
|
|
1043
|
-
"abort",
|
|
1044
|
-
() => {
|
|
1045
|
-
clearTimeout(timeoutId);
|
|
1046
|
-
resolve();
|
|
1047
|
-
},
|
|
1048
|
-
{ once: true }
|
|
1049
|
-
);
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
continue;
|
|
1053
|
-
}
|
|
1054
|
-
console.error("Error polling topic:", error);
|
|
1055
|
-
throw error;
|
|
1120
|
+
);
|
|
1121
|
+
await this.processMessage(
|
|
1122
|
+
response.message,
|
|
1123
|
+
handler
|
|
1124
|
+
);
|
|
1125
|
+
} else {
|
|
1126
|
+
const response = await this.client.receiveMessageById(
|
|
1127
|
+
{
|
|
1128
|
+
queueName: this.topicName,
|
|
1129
|
+
consumerGroup: this.consumerGroupName,
|
|
1130
|
+
messageId: options.messageId,
|
|
1131
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1132
|
+
},
|
|
1133
|
+
this.transport
|
|
1134
|
+
);
|
|
1135
|
+
await this.processMessage(
|
|
1136
|
+
response.message,
|
|
1137
|
+
handler
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
} else {
|
|
1141
|
+
let messageFound = false;
|
|
1142
|
+
for await (const message of this.client.receiveMessages(
|
|
1143
|
+
{
|
|
1144
|
+
queueName: this.topicName,
|
|
1145
|
+
consumerGroup: this.consumerGroupName,
|
|
1146
|
+
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1147
|
+
limit: 1
|
|
1148
|
+
},
|
|
1149
|
+
this.transport
|
|
1150
|
+
)) {
|
|
1151
|
+
messageFound = true;
|
|
1152
|
+
await this.processMessage(message, handler);
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
if (!messageFound) {
|
|
1156
|
+
throw new Error("No messages available");
|
|
1056
1157
|
}
|
|
1057
1158
|
}
|
|
1058
1159
|
}
|
|
1059
|
-
/**
|
|
1060
|
-
* Receive and process a specific message by its ID with full payload
|
|
1061
|
-
* @param messageId The ID of the message to receive and process
|
|
1062
|
-
* @param handler Function to process the message with full payload
|
|
1063
|
-
* @returns Promise that resolves when the message is processed or rejects with specific errors
|
|
1064
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
1065
|
-
* @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
|
|
1066
|
-
* @throws {MessageLockedError} When the message is temporarily locked (423)
|
|
1067
|
-
* @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
|
|
1068
|
-
* @throws {FailedDependencyError} When FIFO ordering is violated (424)
|
|
1069
|
-
* @throws {MessageCorruptedError} When the message data is corrupted
|
|
1070
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
1071
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
1072
|
-
* @throws {ForbiddenError} When access is denied
|
|
1073
|
-
* @throws {InternalServerError} When server encounters an error
|
|
1074
|
-
*/
|
|
1075
|
-
async receiveMessage(messageId, handler) {
|
|
1076
|
-
const response = await this.client.receiveMessageById(
|
|
1077
|
-
{
|
|
1078
|
-
queueName: this.topicName,
|
|
1079
|
-
consumerGroup: this.consumerGroupName,
|
|
1080
|
-
messageId,
|
|
1081
|
-
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1082
|
-
},
|
|
1083
|
-
this.transport
|
|
1084
|
-
);
|
|
1085
|
-
await this.processMessage(response.message, handler);
|
|
1086
|
-
}
|
|
1087
|
-
/**
|
|
1088
|
-
* Receive and process the next available message from the queue
|
|
1089
|
-
* @param handler Function to process the message
|
|
1090
|
-
* @returns Promise that resolves when the message is processed or rejects with specific errors
|
|
1091
|
-
* @throws {QueueEmptyError} When no messages are available in the queue (204)
|
|
1092
|
-
* @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
|
|
1093
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
1094
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
1095
|
-
* @throws {ForbiddenError} When access is denied
|
|
1096
|
-
* @throws {InternalServerError} When server encounters an error
|
|
1097
|
-
*/
|
|
1098
|
-
async receiveNextMessage(handler) {
|
|
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");
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Receive and process multiple next available messages from the queue
|
|
1119
|
-
* @param limit Number of messages to process (1-10)
|
|
1120
|
-
* @param handler Function to process each message
|
|
1121
|
-
* @returns Promise that resolves to an array of PromiseSettledResult (same as Promise.allSettled)
|
|
1122
|
-
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
1123
|
-
* @throws {QueueEmptyError} When no messages are available in the queue (204)
|
|
1124
|
-
* @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
|
|
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 receiveNextMessages(limit, handler) {
|
|
1131
|
-
if (limit < 1 || limit > 10) {
|
|
1132
|
-
throw new InvalidLimitError(limit);
|
|
1133
|
-
}
|
|
1134
|
-
const processingPromises = [];
|
|
1135
|
-
let messageCount = 0;
|
|
1136
|
-
for await (const message of this.client.receiveMessages(
|
|
1137
|
-
{
|
|
1138
|
-
queueName: this.topicName,
|
|
1139
|
-
consumerGroup: this.consumerGroupName,
|
|
1140
|
-
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1141
|
-
limit
|
|
1142
|
-
},
|
|
1143
|
-
this.transport
|
|
1144
|
-
)) {
|
|
1145
|
-
messageCount++;
|
|
1146
|
-
const wrappedPromise = this.processMessage(message, handler).then(
|
|
1147
|
-
(value) => ({
|
|
1148
|
-
status: "fulfilled",
|
|
1149
|
-
value
|
|
1150
|
-
}),
|
|
1151
|
-
(reason) => ({ status: "rejected", reason })
|
|
1152
|
-
);
|
|
1153
|
-
processingPromises.push(wrappedPromise);
|
|
1154
|
-
}
|
|
1155
|
-
if (messageCount === 0) {
|
|
1156
|
-
throw new Error("No messages available");
|
|
1157
|
-
}
|
|
1158
|
-
const results = await Promise.all(processingPromises);
|
|
1159
|
-
return results;
|
|
1160
|
-
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Handle a specific message by its ID without downloading the payload (metadata only)
|
|
1163
|
-
* @param messageId The ID of the message to handle
|
|
1164
|
-
* @param handler Function to process the message metadata (payload will be void)
|
|
1165
|
-
* @returns Promise that resolves when the message is handled or rejects with specific errors
|
|
1166
|
-
* @throws {MessageNotFoundError} When the message doesn't exist (404)
|
|
1167
|
-
* @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
|
|
1168
|
-
* @throws {MessageLockedError} When the message is temporarily locked (423)
|
|
1169
|
-
* @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
|
|
1170
|
-
* @throws {FailedDependencyError} When FIFO ordering is violated (424)
|
|
1171
|
-
* @throws {MessageCorruptedError} When the message data is corrupted
|
|
1172
|
-
* @throws {BadRequestError} When request parameters are invalid
|
|
1173
|
-
* @throws {UnauthorizedError} When authentication fails
|
|
1174
|
-
* @throws {ForbiddenError} When access is denied
|
|
1175
|
-
* @throws {InternalServerError} When server encounters an error
|
|
1176
|
-
*/
|
|
1177
|
-
async handleMessage(messageId, handler) {
|
|
1178
|
-
const response = await this.client.receiveMessageById(
|
|
1179
|
-
{
|
|
1180
|
-
queueName: this.topicName,
|
|
1181
|
-
consumerGroup: this.consumerGroupName,
|
|
1182
|
-
messageId,
|
|
1183
|
-
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1184
|
-
skipPayload: true
|
|
1185
|
-
},
|
|
1186
|
-
this.transport
|
|
1187
|
-
);
|
|
1188
|
-
await this.processMessage(response.message, handler);
|
|
1189
|
-
}
|
|
1190
1160
|
/**
|
|
1191
1161
|
* Get the consumer group name
|
|
1192
1162
|
*/
|
|
@@ -1233,11 +1203,13 @@ var Topic = class {
|
|
|
1233
1203
|
queueName: this.topicName,
|
|
1234
1204
|
payload,
|
|
1235
1205
|
idempotencyKey: options?.idempotencyKey,
|
|
1236
|
-
retentionSeconds: options?.retentionSeconds
|
|
1237
|
-
callbacks: options?.callbacks
|
|
1206
|
+
retentionSeconds: options?.retentionSeconds
|
|
1238
1207
|
},
|
|
1239
1208
|
this.transport
|
|
1240
1209
|
);
|
|
1210
|
+
if (isDevMode()) {
|
|
1211
|
+
triggerDevCallbacks(this.topicName, result.messageId);
|
|
1212
|
+
}
|
|
1241
1213
|
return { messageId: result.messageId };
|
|
1242
1214
|
}
|
|
1243
1215
|
/**
|
|
@@ -1273,54 +1245,60 @@ var Topic = class {
|
|
|
1273
1245
|
};
|
|
1274
1246
|
|
|
1275
1247
|
// src/factory.ts
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1248
|
+
async function send(topicName, payload, options) {
|
|
1249
|
+
const transport = options?.transport || new JsonTransport();
|
|
1250
|
+
const client = new QueueClient();
|
|
1251
|
+
const result = await client.sendMessage(
|
|
1252
|
+
{
|
|
1253
|
+
queueName: topicName,
|
|
1254
|
+
payload,
|
|
1255
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1256
|
+
retentionSeconds: options?.retentionSeconds
|
|
1257
|
+
},
|
|
1258
|
+
transport
|
|
1259
|
+
);
|
|
1260
|
+
if (isDevMode()) {
|
|
1261
|
+
triggerDevCallbacks(topicName, result.messageId);
|
|
1262
|
+
}
|
|
1263
|
+
return { messageId: result.messageId };
|
|
1278
1264
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
const
|
|
1283
|
-
const messageId =
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1265
|
+
async function receive(topicName, consumerGroup, handler, options) {
|
|
1266
|
+
const transport = options?.transport || new JsonTransport();
|
|
1267
|
+
const client = new QueueClient();
|
|
1268
|
+
const topic = new Topic(client, topicName, transport);
|
|
1269
|
+
const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
|
|
1270
|
+
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
1271
|
+
if (messageId) {
|
|
1272
|
+
if (skipPayload) {
|
|
1273
|
+
return consumer.consume(handler, {
|
|
1274
|
+
messageId,
|
|
1275
|
+
skipPayload: true
|
|
1276
|
+
});
|
|
1277
|
+
} else {
|
|
1278
|
+
return consumer.consume(handler, { messageId });
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
return consumer.consume(handler);
|
|
1294
1282
|
}
|
|
1295
|
-
return {
|
|
1296
|
-
messageId,
|
|
1297
|
-
queueName,
|
|
1298
|
-
consumerGroup
|
|
1299
|
-
};
|
|
1300
1283
|
}
|
|
1301
1284
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1302
1285
|
0 && (module.exports = {
|
|
1303
1286
|
BadRequestError,
|
|
1304
1287
|
BufferTransport,
|
|
1305
|
-
ConsumerGroup,
|
|
1306
|
-
FailedDependencyError,
|
|
1307
|
-
FifoOrderingViolationError,
|
|
1308
1288
|
ForbiddenError,
|
|
1309
1289
|
InternalServerError,
|
|
1310
|
-
InvalidCallbackError,
|
|
1311
1290
|
InvalidLimitError,
|
|
1312
1291
|
JsonTransport,
|
|
1313
1292
|
MessageCorruptedError,
|
|
1314
1293
|
MessageLockedError,
|
|
1315
1294
|
MessageNotAvailableError,
|
|
1316
1295
|
MessageNotFoundError,
|
|
1317
|
-
QueueClient,
|
|
1318
1296
|
QueueEmptyError,
|
|
1319
1297
|
StreamTransport,
|
|
1320
|
-
Topic,
|
|
1321
1298
|
UnauthorizedError,
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1299
|
+
handleCallback,
|
|
1300
|
+
parseCallback,
|
|
1301
|
+
receive,
|
|
1302
|
+
send
|
|
1325
1303
|
});
|
|
1326
1304
|
//# sourceMappingURL=index.js.map
|