@vercel/queue 0.0.0-alpha.11 → 0.0.0-alpha.2
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 +767 -208
- package/dist/index.d.mts +477 -114
- package/dist/index.d.ts +477 -114
- package/dist/index.js +545 -267
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +536 -264
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -22,110 +22,156 @@ 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,
|
|
25
28
|
ForbiddenError: () => ForbiddenError,
|
|
26
29
|
InternalServerError: () => InternalServerError,
|
|
30
|
+
InvalidCallbackError: () => InvalidCallbackError,
|
|
27
31
|
InvalidLimitError: () => InvalidLimitError,
|
|
28
32
|
JsonTransport: () => JsonTransport,
|
|
29
33
|
MessageCorruptedError: () => MessageCorruptedError,
|
|
30
34
|
MessageLockedError: () => MessageLockedError,
|
|
31
35
|
MessageNotAvailableError: () => MessageNotAvailableError,
|
|
32
36
|
MessageNotFoundError: () => MessageNotFoundError,
|
|
37
|
+
QueueClient: () => QueueClient,
|
|
33
38
|
QueueEmptyError: () => QueueEmptyError,
|
|
34
39
|
StreamTransport: () => StreamTransport,
|
|
40
|
+
Topic: () => Topic,
|
|
35
41
|
UnauthorizedError: () => UnauthorizedError,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
createTopic: () => createTopic,
|
|
43
|
+
getVercelOidcToken: () => getVercelOidcToken,
|
|
44
|
+
parseCallbackRequest: () => parseCallbackRequest
|
|
39
45
|
});
|
|
40
46
|
module.exports = __toCommonJS(index_exports);
|
|
41
47
|
|
|
42
|
-
// src/transports.ts
|
|
43
|
-
var JsonTransport = class {
|
|
44
|
-
contentType = "application/json";
|
|
45
|
-
serialize(value) {
|
|
46
|
-
return Buffer.from(JSON.stringify(value), "utf8");
|
|
47
|
-
}
|
|
48
|
-
async deserialize(stream) {
|
|
49
|
-
const reader = stream.getReader();
|
|
50
|
-
let totalLength = 0;
|
|
51
|
-
const chunks = [];
|
|
52
|
-
try {
|
|
53
|
-
while (true) {
|
|
54
|
-
const { done, value } = await reader.read();
|
|
55
|
-
if (done) break;
|
|
56
|
-
chunks.push(value);
|
|
57
|
-
totalLength += value.length;
|
|
58
|
-
}
|
|
59
|
-
} finally {
|
|
60
|
-
reader.releaseLock();
|
|
61
|
-
}
|
|
62
|
-
const buffer = Buffer.concat(chunks, totalLength);
|
|
63
|
-
return JSON.parse(buffer.toString("utf8"));
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
var BufferTransport = class {
|
|
67
|
-
contentType = "application/octet-stream";
|
|
68
|
-
serialize(value) {
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
async deserialize(stream) {
|
|
72
|
-
const reader = stream.getReader();
|
|
73
|
-
const chunks = [];
|
|
74
|
-
try {
|
|
75
|
-
while (true) {
|
|
76
|
-
const { done, value } = await reader.read();
|
|
77
|
-
if (done) break;
|
|
78
|
-
chunks.push(value);
|
|
79
|
-
}
|
|
80
|
-
} finally {
|
|
81
|
-
reader.releaseLock();
|
|
82
|
-
}
|
|
83
|
-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
84
|
-
const buffer = new Uint8Array(totalLength);
|
|
85
|
-
let offset = 0;
|
|
86
|
-
for (const chunk of chunks) {
|
|
87
|
-
buffer.set(chunk, offset);
|
|
88
|
-
offset += chunk.length;
|
|
89
|
-
}
|
|
90
|
-
return Buffer.from(buffer);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
var StreamTransport = class {
|
|
94
|
-
contentType = "application/octet-stream";
|
|
95
|
-
serialize(value) {
|
|
96
|
-
return value;
|
|
97
|
-
}
|
|
98
|
-
async deserialize(stream) {
|
|
99
|
-
return stream;
|
|
100
|
-
}
|
|
101
|
-
async finalize(payload) {
|
|
102
|
-
const reader = payload.getReader();
|
|
103
|
-
try {
|
|
104
|
-
while (true) {
|
|
105
|
-
const { done } = await reader.read();
|
|
106
|
-
if (done) break;
|
|
107
|
-
}
|
|
108
|
-
} finally {
|
|
109
|
-
reader.releaseLock();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// src/client.ts
|
|
115
|
-
var import_mixpart = require("mixpart");
|
|
116
|
-
|
|
117
48
|
// src/oidc.ts
|
|
118
|
-
function getVercelOidcToken() {
|
|
49
|
+
async function getVercelOidcToken() {
|
|
119
50
|
const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
|
|
120
51
|
const fromSymbol = globalThis;
|
|
121
52
|
const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
|
|
122
53
|
const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
|
|
123
54
|
if (!token) {
|
|
124
|
-
|
|
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
|
+
);
|
|
125
58
|
}
|
|
126
59
|
return token;
|
|
127
60
|
}
|
|
128
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) {
|
|
68
|
+
try {
|
|
69
|
+
const parsedUrl = new URL(url);
|
|
70
|
+
const isLocalhost = parsedUrl.hostname === "localhost";
|
|
71
|
+
const port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 0;
|
|
72
|
+
return { isLocalhost, port };
|
|
73
|
+
} catch {
|
|
74
|
+
return { isLocalhost: false };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isSupportedPlatform() {
|
|
78
|
+
const platform = process.platform;
|
|
79
|
+
return platform === "darwin" || platform === "linux";
|
|
80
|
+
}
|
|
81
|
+
function processDevelopmentCallbacks(callbacks) {
|
|
82
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
83
|
+
if (!isDevelopment) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
if (!isSupportedPlatform()) {
|
|
87
|
+
const hasLocalhostCallbacks = Object.values(callbacks).some((config) => {
|
|
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 [];
|
|
97
|
+
}
|
|
98
|
+
const localhostCallbacks = [];
|
|
99
|
+
Object.entries(callbacks).forEach(([group, config]) => {
|
|
100
|
+
const { isLocalhost, port } = isLocalhostWithPort(config.url);
|
|
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;
|
|
133
|
+
}
|
|
134
|
+
let headerArgs = "";
|
|
135
|
+
if (headers) {
|
|
136
|
+
const headerArray = [];
|
|
137
|
+
headers.forEach((value, key) => {
|
|
138
|
+
headerArray.push(`-H '${key}: ${value}'`);
|
|
139
|
+
});
|
|
140
|
+
headerArgs = headerArray.join(" ");
|
|
141
|
+
}
|
|
142
|
+
const bashScript = `
|
|
143
|
+
# Wait for any initial boot time
|
|
144
|
+
sleep ${initialDelaySeconds}
|
|
145
|
+
|
|
146
|
+
missed=0
|
|
147
|
+
while true; do
|
|
148
|
+
# 1) Check if TCP port is listening
|
|
149
|
+
if nc -z localhost ${port} 2>/dev/null; then
|
|
150
|
+
missed=0
|
|
151
|
+
# 2) If port is open, try HTTP POST check
|
|
152
|
+
if curl -sSL --fail -o /dev/null -X POST ${headerArgs} "${url}"; then
|
|
153
|
+
# Success: port is up AND HTTP returned 2xx (following redirects)
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
else
|
|
157
|
+
# Port was closed\u2014increment miss counter
|
|
158
|
+
((missed+=1))
|
|
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();
|
|
173
|
+
}
|
|
174
|
+
|
|
129
175
|
// src/types.ts
|
|
130
176
|
var MessageNotFoundError = class extends Error {
|
|
131
177
|
constructor(messageId) {
|
|
@@ -141,6 +187,16 @@ var MessageNotAvailableError = class extends Error {
|
|
|
141
187
|
this.name = "MessageNotAvailableError";
|
|
142
188
|
}
|
|
143
189
|
};
|
|
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
|
+
};
|
|
144
200
|
var MessageCorruptedError = class extends Error {
|
|
145
201
|
constructor(messageId, reason) {
|
|
146
202
|
super(`Message ${messageId} is corrupted: ${reason}`);
|
|
@@ -182,6 +238,16 @@ var BadRequestError = class extends Error {
|
|
|
182
238
|
this.name = "BadRequestError";
|
|
183
239
|
}
|
|
184
240
|
};
|
|
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
|
+
};
|
|
185
251
|
var InternalServerError = class extends Error {
|
|
186
252
|
constructor(message = "Unexpected server error") {
|
|
187
253
|
super(message);
|
|
@@ -194,6 +260,12 @@ var InvalidLimitError = class extends Error {
|
|
|
194
260
|
this.name = "InvalidLimitError";
|
|
195
261
|
}
|
|
196
262
|
};
|
|
263
|
+
var InvalidCallbackError = class extends Error {
|
|
264
|
+
constructor(message) {
|
|
265
|
+
super(message);
|
|
266
|
+
this.name = "InvalidCallbackError";
|
|
267
|
+
}
|
|
268
|
+
};
|
|
197
269
|
|
|
198
270
|
// src/client.ts
|
|
199
271
|
async function consumeStream(stream) {
|
|
@@ -223,33 +295,40 @@ function parseQueueHeaders(headers) {
|
|
|
223
295
|
return {
|
|
224
296
|
messageId,
|
|
225
297
|
deliveryCount,
|
|
226
|
-
|
|
298
|
+
timestamp,
|
|
227
299
|
contentType,
|
|
228
300
|
ticket
|
|
229
301
|
};
|
|
230
302
|
}
|
|
231
|
-
var QueueClient = class {
|
|
303
|
+
var QueueClient = class _QueueClient {
|
|
232
304
|
baseUrl;
|
|
233
|
-
basePath;
|
|
234
305
|
token;
|
|
235
306
|
/**
|
|
236
307
|
* Create a new Vercel Queue Service client
|
|
237
|
-
* @param options Client configuration options
|
|
308
|
+
* @param options Client configuration options
|
|
238
309
|
*/
|
|
239
|
-
constructor(options
|
|
240
|
-
this.baseUrl = options.baseUrl || "https://
|
|
241
|
-
this.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
310
|
+
constructor(options) {
|
|
311
|
+
this.baseUrl = options.baseUrl || "https://vqs.vercel.sh";
|
|
312
|
+
this.token = options.token;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Create a QueueClient automatically configured for Vercel Functions
|
|
316
|
+
* This method automatically retrieves the OIDC token from the Vercel Function environment
|
|
317
|
+
* Always creates a fresh instance since OIDC tokens expire after 15 minutes
|
|
318
|
+
* @param baseUrl Optional base URL override
|
|
319
|
+
* @returns Promise resolving to a new QueueClient instance
|
|
320
|
+
*/
|
|
321
|
+
static async fromVercelFunction(baseUrl) {
|
|
322
|
+
const token = await getVercelOidcToken();
|
|
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
|
+
);
|
|
252
327
|
}
|
|
328
|
+
return new _QueueClient({
|
|
329
|
+
token,
|
|
330
|
+
baseUrl
|
|
331
|
+
});
|
|
253
332
|
}
|
|
254
333
|
/**
|
|
255
334
|
* Send a message to a queue
|
|
@@ -262,23 +341,40 @@ var QueueClient = class {
|
|
|
262
341
|
* @throws {InternalServerError} When server encounters an error
|
|
263
342
|
*/
|
|
264
343
|
async sendMessage(options, transport) {
|
|
265
|
-
const { queueName, payload, idempotencyKey, retentionSeconds } = options;
|
|
344
|
+
const { queueName, payload, idempotencyKey, retentionSeconds, callbacks } = options;
|
|
266
345
|
const headers = new Headers({
|
|
267
346
|
Authorization: `Bearer ${this.token}`,
|
|
268
347
|
"Vqs-Queue-Name": queueName,
|
|
269
348
|
"Content-Type": transport.contentType
|
|
270
349
|
});
|
|
271
|
-
if (process.env.VERCEL_DEPLOYMENT_ID) {
|
|
272
|
-
headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
|
|
273
|
-
}
|
|
274
350
|
if (idempotencyKey) {
|
|
275
351
|
headers.set("Vqs-Idempotency-Key", idempotencyKey);
|
|
276
352
|
}
|
|
277
353
|
if (retentionSeconds !== void 0) {
|
|
278
354
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
279
355
|
}
|
|
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
|
+
}
|
|
280
376
|
const body = transport.serialize(payload);
|
|
281
|
-
const response = await fetch(`${this.baseUrl}
|
|
377
|
+
const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
|
|
282
378
|
method: "POST",
|
|
283
379
|
headers,
|
|
284
380
|
body
|
|
@@ -307,6 +403,9 @@ var QueueClient = class {
|
|
|
307
403
|
);
|
|
308
404
|
}
|
|
309
405
|
const responseData = await response.json();
|
|
406
|
+
if (localhostCallbacks.length > 0) {
|
|
407
|
+
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
408
|
+
}
|
|
310
409
|
return responseData;
|
|
311
410
|
}
|
|
312
411
|
/**
|
|
@@ -316,7 +415,7 @@ var QueueClient = class {
|
|
|
316
415
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
317
416
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
318
417
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
319
|
-
* @throws {MessageLockedError} When
|
|
418
|
+
* @throws {MessageLockedError} When FIFO queue has locked messages (423)
|
|
320
419
|
* @throws {BadRequestError} When request parameters are invalid
|
|
321
420
|
* @throws {UnauthorizedError} When authentication fails
|
|
322
421
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -342,7 +441,7 @@ var QueueClient = class {
|
|
|
342
441
|
if (limit !== void 0) {
|
|
343
442
|
headers.set("Vqs-Limit", limit.toString());
|
|
344
443
|
}
|
|
345
|
-
const response = await fetch(`${this.baseUrl}
|
|
444
|
+
const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
|
|
346
445
|
method: "GET",
|
|
347
446
|
headers
|
|
348
447
|
});
|
|
@@ -367,7 +466,7 @@ var QueueClient = class {
|
|
|
367
466
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
368
467
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
369
468
|
}
|
|
370
|
-
throw new MessageLockedError("next message", retryAfter);
|
|
469
|
+
throw new MessageLockedError("next message in FIFO queue", retryAfter);
|
|
371
470
|
}
|
|
372
471
|
if (response.status >= 500) {
|
|
373
472
|
throw new InternalServerError(
|
|
@@ -424,7 +523,7 @@ var QueueClient = class {
|
|
|
424
523
|
headers.set("Vqs-Skip-Payload", "1");
|
|
425
524
|
}
|
|
426
525
|
const response = await fetch(
|
|
427
|
-
`${this.baseUrl}
|
|
526
|
+
`${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
|
|
428
527
|
{
|
|
429
528
|
method: "GET",
|
|
430
529
|
headers
|
|
@@ -453,7 +552,37 @@ var QueueClient = class {
|
|
|
453
552
|
}
|
|
454
553
|
throw new MessageLockedError(messageId, retryAfter);
|
|
455
554
|
}
|
|
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
|
+
}
|
|
456
574
|
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
|
+
}
|
|
457
586
|
throw new MessageNotAvailableError(messageId);
|
|
458
587
|
}
|
|
459
588
|
if (response.status >= 500) {
|
|
@@ -533,7 +662,7 @@ var QueueClient = class {
|
|
|
533
662
|
async deleteMessage(options) {
|
|
534
663
|
const { queueName, consumerGroup, messageId, ticket } = options;
|
|
535
664
|
const response = await fetch(
|
|
536
|
-
`${this.baseUrl}
|
|
665
|
+
`${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
|
|
537
666
|
{
|
|
538
667
|
method: "DELETE",
|
|
539
668
|
headers: new Headers({
|
|
@@ -594,7 +723,7 @@ var QueueClient = class {
|
|
|
594
723
|
visibilityTimeoutSeconds
|
|
595
724
|
} = options;
|
|
596
725
|
const response = await fetch(
|
|
597
|
-
`${this.baseUrl}
|
|
726
|
+
`${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
|
|
598
727
|
{
|
|
599
728
|
method: "PATCH",
|
|
600
729
|
headers: new Headers({
|
|
@@ -640,6 +769,82 @@ var QueueClient = class {
|
|
|
640
769
|
}
|
|
641
770
|
};
|
|
642
771
|
|
|
772
|
+
// src/transports.ts
|
|
773
|
+
var JsonTransport = class {
|
|
774
|
+
contentType = "application/json";
|
|
775
|
+
serialize(value) {
|
|
776
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
777
|
+
}
|
|
778
|
+
async deserialize(stream) {
|
|
779
|
+
const reader = stream.getReader();
|
|
780
|
+
const chunks = [];
|
|
781
|
+
try {
|
|
782
|
+
while (true) {
|
|
783
|
+
const { done, value } = await reader.read();
|
|
784
|
+
if (done) break;
|
|
785
|
+
chunks.push(value);
|
|
786
|
+
}
|
|
787
|
+
} finally {
|
|
788
|
+
reader.releaseLock();
|
|
789
|
+
}
|
|
790
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
791
|
+
const buffer = new Uint8Array(totalLength);
|
|
792
|
+
let offset = 0;
|
|
793
|
+
for (const chunk of chunks) {
|
|
794
|
+
buffer.set(chunk, offset);
|
|
795
|
+
offset += chunk.length;
|
|
796
|
+
}
|
|
797
|
+
return JSON.parse(Buffer.from(buffer).toString("utf8"));
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
var BufferTransport = class {
|
|
801
|
+
contentType = "application/octet-stream";
|
|
802
|
+
serialize(value) {
|
|
803
|
+
return value;
|
|
804
|
+
}
|
|
805
|
+
async deserialize(stream) {
|
|
806
|
+
const reader = stream.getReader();
|
|
807
|
+
const chunks = [];
|
|
808
|
+
try {
|
|
809
|
+
while (true) {
|
|
810
|
+
const { done, value } = await reader.read();
|
|
811
|
+
if (done) break;
|
|
812
|
+
chunks.push(value);
|
|
813
|
+
}
|
|
814
|
+
} finally {
|
|
815
|
+
reader.releaseLock();
|
|
816
|
+
}
|
|
817
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
818
|
+
const buffer = new Uint8Array(totalLength);
|
|
819
|
+
let offset = 0;
|
|
820
|
+
for (const chunk of chunks) {
|
|
821
|
+
buffer.set(chunk, offset);
|
|
822
|
+
offset += chunk.length;
|
|
823
|
+
}
|
|
824
|
+
return Buffer.from(buffer);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
var StreamTransport = class {
|
|
828
|
+
contentType = "application/octet-stream";
|
|
829
|
+
serialize(value) {
|
|
830
|
+
return value;
|
|
831
|
+
}
|
|
832
|
+
async deserialize(stream) {
|
|
833
|
+
return stream;
|
|
834
|
+
}
|
|
835
|
+
async finalize(payload) {
|
|
836
|
+
const reader = payload.getReader();
|
|
837
|
+
try {
|
|
838
|
+
while (true) {
|
|
839
|
+
const { done } = await reader.read();
|
|
840
|
+
if (done) break;
|
|
841
|
+
}
|
|
842
|
+
} finally {
|
|
843
|
+
reader.releaseLock();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
643
848
|
// src/consumer-group.ts
|
|
644
849
|
var ConsumerGroup = class {
|
|
645
850
|
client;
|
|
@@ -739,11 +944,7 @@ var ConsumerGroup = class {
|
|
|
739
944
|
message.ticket
|
|
740
945
|
);
|
|
741
946
|
try {
|
|
742
|
-
const result = await handler(message
|
|
743
|
-
messageId: message.messageId,
|
|
744
|
-
deliveryCount: message.deliveryCount,
|
|
745
|
-
createdAt: message.createdAt
|
|
746
|
-
});
|
|
947
|
+
const result = await handler(message);
|
|
747
948
|
await stopExtension();
|
|
748
949
|
if (result && "timeoutSeconds" in result) {
|
|
749
950
|
await this.client.changeVisibility({
|
|
@@ -773,58 +974,219 @@ var ConsumerGroup = class {
|
|
|
773
974
|
throw error;
|
|
774
975
|
}
|
|
775
976
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
977
|
+
/**
|
|
978
|
+
* Start continuous processing of messages from the topic
|
|
979
|
+
* @param signal AbortSignal to control when to stop processing
|
|
980
|
+
* @param handler Function to process each message
|
|
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(
|
|
780
989
|
{
|
|
781
990
|
queueName: this.topicName,
|
|
782
991
|
consumerGroup: this.consumerGroupName,
|
|
783
|
-
messageId: options.messageId,
|
|
784
992
|
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
785
|
-
|
|
993
|
+
limit: 1
|
|
994
|
+
// Always process one message at a time
|
|
786
995
|
},
|
|
787
996
|
this.transport
|
|
788
|
-
)
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
997
|
+
)) {
|
|
998
|
+
if (signal.aborted) {
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
await this.processMessage(message, handler);
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
console.error("Error processing message:", error);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (!signal.aborted) {
|
|
1008
|
+
await new Promise((resolve) => {
|
|
1009
|
+
const timeoutId = setTimeout(resolve, pollingInterval);
|
|
1010
|
+
signal.addEventListener(
|
|
1011
|
+
"abort",
|
|
1012
|
+
() => {
|
|
1013
|
+
clearTimeout(timeoutId);
|
|
1014
|
+
resolve();
|
|
1015
|
+
},
|
|
1016
|
+
{ once: true }
|
|
1017
|
+
);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (error instanceof QueueEmptyError) {
|
|
1022
|
+
if (!signal.aborted) {
|
|
1023
|
+
await new Promise((resolve) => {
|
|
1024
|
+
const timeoutId = setTimeout(resolve, pollingInterval);
|
|
1025
|
+
signal.addEventListener(
|
|
1026
|
+
"abort",
|
|
1027
|
+
() => {
|
|
1028
|
+
clearTimeout(timeoutId);
|
|
1029
|
+
resolve();
|
|
1030
|
+
},
|
|
1031
|
+
{ once: true }
|
|
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;
|
|
825
1056
|
}
|
|
826
1057
|
}
|
|
827
1058
|
}
|
|
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
|
+
}
|
|
828
1190
|
/**
|
|
829
1191
|
* Get the consumer group name
|
|
830
1192
|
*/
|
|
@@ -871,7 +1233,8 @@ var Topic = class {
|
|
|
871
1233
|
queueName: this.topicName,
|
|
872
1234
|
payload,
|
|
873
1235
|
idempotencyKey: options?.idempotencyKey,
|
|
874
|
-
retentionSeconds: options?.retentionSeconds
|
|
1236
|
+
retentionSeconds: options?.retentionSeconds,
|
|
1237
|
+
callbacks: options?.callbacks
|
|
875
1238
|
},
|
|
876
1239
|
this.transport
|
|
877
1240
|
);
|
|
@@ -910,139 +1273,54 @@ var Topic = class {
|
|
|
910
1273
|
};
|
|
911
1274
|
|
|
912
1275
|
// src/factory.ts
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const client = new QueueClient();
|
|
916
|
-
const result = await client.sendMessage(
|
|
917
|
-
{
|
|
918
|
-
queueName: topicName,
|
|
919
|
-
payload,
|
|
920
|
-
idempotencyKey: options?.idempotencyKey,
|
|
921
|
-
retentionSeconds: options?.retentionSeconds
|
|
922
|
-
},
|
|
923
|
-
transport
|
|
924
|
-
);
|
|
925
|
-
return { messageId: result.messageId };
|
|
926
|
-
}
|
|
927
|
-
async function receive(topicName, consumerGroup, handler, options) {
|
|
928
|
-
const transport = options?.transport || new JsonTransport();
|
|
929
|
-
const client = new QueueClient();
|
|
930
|
-
const topic = new Topic(client, topicName, transport);
|
|
931
|
-
const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
|
|
932
|
-
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
933
|
-
if (messageId) {
|
|
934
|
-
if (skipPayload) {
|
|
935
|
-
return consumer.consume(handler, {
|
|
936
|
-
messageId,
|
|
937
|
-
skipPayload: true
|
|
938
|
-
});
|
|
939
|
-
} else {
|
|
940
|
-
return consumer.consume(handler, { messageId });
|
|
941
|
-
}
|
|
942
|
-
} else {
|
|
943
|
-
return consumer.consume(handler);
|
|
944
|
-
}
|
|
1276
|
+
function createTopic(client, topicName, transport) {
|
|
1277
|
+
return new Topic(client, topicName, transport);
|
|
945
1278
|
}
|
|
946
1279
|
|
|
947
1280
|
// src/callback.ts
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}
|
|
961
|
-
if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
|
|
962
|
-
throw new Error("Invalid CloudEvent: missing required fields");
|
|
963
|
-
}
|
|
964
|
-
if (cloudEvent.type !== "com.vercel.queue.v1beta") {
|
|
965
|
-
throw new Error(
|
|
966
|
-
`Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
const missingFields = [];
|
|
970
|
-
if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
|
|
971
|
-
if (!("consumerGroup" in cloudEvent.data))
|
|
972
|
-
missingFields.push("consumerGroup");
|
|
973
|
-
if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
|
|
974
|
-
if (missingFields.length > 0) {
|
|
975
|
-
throw new Error(
|
|
976
|
-
`Missing required CloudEvent data fields: ${missingFields.join(", ")}`
|
|
1281
|
+
function parseCallbackRequest(request) {
|
|
1282
|
+
const headers = request.headers;
|
|
1283
|
+
const messageId = headers.get("Vqs-Message-Id");
|
|
1284
|
+
const queueName = headers.get("Vqs-Queue-Name");
|
|
1285
|
+
const consumerGroup = headers.get("Vqs-Consumer-Group");
|
|
1286
|
+
const missingHeaders = [];
|
|
1287
|
+
if (!messageId) missingHeaders.push("Vqs-Message-Id");
|
|
1288
|
+
if (!queueName) missingHeaders.push("Vqs-Queue-Name");
|
|
1289
|
+
if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
|
|
1290
|
+
if (missingHeaders.length > 0) {
|
|
1291
|
+
throw new InvalidCallbackError(
|
|
1292
|
+
`Missing required queue callback headers: ${missingHeaders.join(", ")}`
|
|
977
1293
|
);
|
|
978
1294
|
}
|
|
979
|
-
const { messageId, queueName, consumerGroup } = cloudEvent.data;
|
|
980
1295
|
return {
|
|
1296
|
+
messageId,
|
|
981
1297
|
queueName,
|
|
982
|
-
consumerGroup
|
|
983
|
-
messageId
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
function handleCallback(handlers) {
|
|
987
|
-
return async (request) => {
|
|
988
|
-
try {
|
|
989
|
-
const { queueName, consumerGroup, messageId } = await parseCallbackRequest(request);
|
|
990
|
-
const topicHandler = handlers[queueName];
|
|
991
|
-
if (!topicHandler) {
|
|
992
|
-
const availableTopics = Object.keys(handlers).join(", ");
|
|
993
|
-
return Response.json(
|
|
994
|
-
{
|
|
995
|
-
error: `No handler found for topic: ${queueName}`,
|
|
996
|
-
availableTopics
|
|
997
|
-
},
|
|
998
|
-
{ status: 404 }
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1001
|
-
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
1002
|
-
if (!consumerGroupHandler) {
|
|
1003
|
-
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
1004
|
-
return Response.json(
|
|
1005
|
-
{
|
|
1006
|
-
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
1007
|
-
availableGroups
|
|
1008
|
-
},
|
|
1009
|
-
{ status: 404 }
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
const client = new QueueClient();
|
|
1013
|
-
const topic = new Topic(client, queueName);
|
|
1014
|
-
const cg = topic.consumerGroup(consumerGroup);
|
|
1015
|
-
await cg.consume(consumerGroupHandler, { messageId });
|
|
1016
|
-
return Response.json({ status: "success" });
|
|
1017
|
-
} catch (error) {
|
|
1018
|
-
console.error("Queue callback error:", error);
|
|
1019
|
-
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"))) {
|
|
1020
|
-
return Response.json({ error: error.message }, { status: 400 });
|
|
1021
|
-
}
|
|
1022
|
-
return Response.json(
|
|
1023
|
-
{ error: "Failed to process queue message" },
|
|
1024
|
-
{ status: 500 }
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1298
|
+
consumerGroup
|
|
1027
1299
|
};
|
|
1028
1300
|
}
|
|
1029
1301
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1030
1302
|
0 && (module.exports = {
|
|
1031
1303
|
BadRequestError,
|
|
1032
1304
|
BufferTransport,
|
|
1305
|
+
ConsumerGroup,
|
|
1306
|
+
FailedDependencyError,
|
|
1307
|
+
FifoOrderingViolationError,
|
|
1033
1308
|
ForbiddenError,
|
|
1034
1309
|
InternalServerError,
|
|
1310
|
+
InvalidCallbackError,
|
|
1035
1311
|
InvalidLimitError,
|
|
1036
1312
|
JsonTransport,
|
|
1037
1313
|
MessageCorruptedError,
|
|
1038
1314
|
MessageLockedError,
|
|
1039
1315
|
MessageNotAvailableError,
|
|
1040
1316
|
MessageNotFoundError,
|
|
1317
|
+
QueueClient,
|
|
1041
1318
|
QueueEmptyError,
|
|
1042
1319
|
StreamTransport,
|
|
1320
|
+
Topic,
|
|
1043
1321
|
UnauthorizedError,
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1322
|
+
createTopic,
|
|
1323
|
+
getVercelOidcToken,
|
|
1324
|
+
parseCallbackRequest
|
|
1047
1325
|
});
|
|
1048
1326
|
//# sourceMappingURL=index.js.map
|