@vercel/queue 0.0.0-alpha.1 → 0.0.0-alpha.11
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 +226 -564
- package/dist/index.d.mts +115 -478
- package/dist/index.d.ts +115 -478
- package/dist/index.js +276 -554
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +273 -545
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,128 +1,88 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
);
|
|
1
|
+
// src/transports.ts
|
|
2
|
+
var JsonTransport = class {
|
|
3
|
+
contentType = "application/json";
|
|
4
|
+
serialize(value) {
|
|
5
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
11
6
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
7
|
+
async deserialize(stream) {
|
|
8
|
+
const reader = stream.getReader();
|
|
9
|
+
let totalLength = 0;
|
|
10
|
+
const chunks = [];
|
|
11
|
+
try {
|
|
12
|
+
while (true) {
|
|
13
|
+
const { done, value } = await reader.read();
|
|
14
|
+
if (done) break;
|
|
15
|
+
chunks.push(value);
|
|
16
|
+
totalLength += value.length;
|
|
17
|
+
}
|
|
18
|
+
} finally {
|
|
19
|
+
reader.releaseLock();
|
|
20
|
+
}
|
|
21
|
+
const buffer = Buffer.concat(chunks, totalLength);
|
|
22
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
28
23
|
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
function processDevelopmentCallbacks(callbacks) {
|
|
35
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
36
|
-
if (!isDevelopment) {
|
|
37
|
-
return [];
|
|
24
|
+
};
|
|
25
|
+
var BufferTransport = class {
|
|
26
|
+
contentType = "application/octet-stream";
|
|
27
|
+
serialize(value) {
|
|
28
|
+
return value;
|
|
38
29
|
}
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
30
|
+
async deserialize(stream) {
|
|
31
|
+
const reader = stream.getReader();
|
|
32
|
+
const chunks = [];
|
|
33
|
+
try {
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read();
|
|
36
|
+
if (done) break;
|
|
37
|
+
chunks.push(value);
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
reader.releaseLock();
|
|
48
41
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
localhostCallbacks.push({ group, config, port });
|
|
56
|
-
} else {
|
|
57
|
-
console.warn(
|
|
58
|
-
`VQS Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
|
|
59
|
-
);
|
|
42
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
43
|
+
const buffer = new Uint8Array(totalLength);
|
|
44
|
+
let offset = 0;
|
|
45
|
+
for (const chunk of chunks) {
|
|
46
|
+
buffer.set(chunk, offset);
|
|
47
|
+
offset += chunk.length;
|
|
60
48
|
}
|
|
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
|
-
`VQS: 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;
|
|
49
|
+
return Buffer.from(buffer);
|
|
86
50
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
93
|
-
headerArgs = headerArray.join(" ");
|
|
51
|
+
};
|
|
52
|
+
var StreamTransport = class {
|
|
53
|
+
contentType = "application/octet-stream";
|
|
54
|
+
serialize(value) {
|
|
55
|
+
return value;
|
|
94
56
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
57
|
+
async deserialize(stream) {
|
|
58
|
+
return stream;
|
|
59
|
+
}
|
|
60
|
+
async finalize(payload) {
|
|
61
|
+
const reader = payload.getReader();
|
|
62
|
+
try {
|
|
63
|
+
while (true) {
|
|
64
|
+
const { done } = await reader.read();
|
|
65
|
+
if (done) break;
|
|
66
|
+
}
|
|
67
|
+
} finally {
|
|
68
|
+
reader.releaseLock();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/client.ts
|
|
74
|
+
import { parseMultipartStream } from "mixpart";
|
|
98
75
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else
|
|
110
|
-
# Port was closed\u2014increment miss counter
|
|
111
|
-
((missed+=1))
|
|
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();
|
|
76
|
+
// src/oidc.ts
|
|
77
|
+
function getVercelOidcToken() {
|
|
78
|
+
const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
|
|
79
|
+
const fromSymbol = globalThis;
|
|
80
|
+
const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
|
|
81
|
+
const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
|
|
82
|
+
if (!token) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return token;
|
|
126
86
|
}
|
|
127
87
|
|
|
128
88
|
// src/types.ts
|
|
@@ -140,16 +100,6 @@ var MessageNotAvailableError = class extends Error {
|
|
|
140
100
|
this.name = "MessageNotAvailableError";
|
|
141
101
|
}
|
|
142
102
|
};
|
|
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
103
|
var MessageCorruptedError = class extends Error {
|
|
154
104
|
constructor(messageId, reason) {
|
|
155
105
|
super(`Message ${messageId} is corrupted: ${reason}`);
|
|
@@ -191,16 +141,6 @@ var BadRequestError = class extends Error {
|
|
|
191
141
|
this.name = "BadRequestError";
|
|
192
142
|
}
|
|
193
143
|
};
|
|
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
144
|
var InternalServerError = class extends Error {
|
|
205
145
|
constructor(message = "Unexpected server error") {
|
|
206
146
|
super(message);
|
|
@@ -213,12 +153,6 @@ var InvalidLimitError = class extends Error {
|
|
|
213
153
|
this.name = "InvalidLimitError";
|
|
214
154
|
}
|
|
215
155
|
};
|
|
216
|
-
var InvalidCallbackError = class extends Error {
|
|
217
|
-
constructor(message) {
|
|
218
|
-
super(message);
|
|
219
|
-
this.name = "InvalidCallbackError";
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
156
|
|
|
223
157
|
// src/client.ts
|
|
224
158
|
async function consumeStream(stream) {
|
|
@@ -232,7 +166,7 @@ async function consumeStream(stream) {
|
|
|
232
166
|
reader.releaseLock();
|
|
233
167
|
}
|
|
234
168
|
}
|
|
235
|
-
function
|
|
169
|
+
function parseQueueHeaders(headers) {
|
|
236
170
|
const messageId = headers.get("Vqs-Message-Id");
|
|
237
171
|
const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
|
|
238
172
|
const timestamp = headers.get("Vqs-Timestamp");
|
|
@@ -248,40 +182,33 @@ function parseVQSHeaders(headers) {
|
|
|
248
182
|
return {
|
|
249
183
|
messageId,
|
|
250
184
|
deliveryCount,
|
|
251
|
-
timestamp,
|
|
185
|
+
createdAt: new Date(timestamp),
|
|
252
186
|
contentType,
|
|
253
187
|
ticket
|
|
254
188
|
};
|
|
255
189
|
}
|
|
256
|
-
var
|
|
190
|
+
var QueueClient = class {
|
|
257
191
|
baseUrl;
|
|
192
|
+
basePath;
|
|
258
193
|
token;
|
|
259
194
|
/**
|
|
260
195
|
* Create a new Vercel Queue Service client
|
|
261
|
-
* @param options Client configuration options
|
|
196
|
+
* @param options Client configuration options (optional - will auto-detect Vercel Function environment)
|
|
262
197
|
*/
|
|
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
|
-
);
|
|
198
|
+
constructor(options = {}) {
|
|
199
|
+
this.baseUrl = options.baseUrl || "https://api.vercel.com";
|
|
200
|
+
this.basePath = options.basePath || "/v1/queues/messages";
|
|
201
|
+
if (options.token) {
|
|
202
|
+
this.token = options.token;
|
|
203
|
+
} else {
|
|
204
|
+
const token = getVercelOidcToken();
|
|
205
|
+
if (!token) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"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'"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
this.token = token;
|
|
280
211
|
}
|
|
281
|
-
return new _VQSClient({
|
|
282
|
-
token,
|
|
283
|
-
baseUrl
|
|
284
|
-
});
|
|
285
212
|
}
|
|
286
213
|
/**
|
|
287
214
|
* Send a message to a queue
|
|
@@ -294,40 +221,23 @@ var VQSClient = class _VQSClient {
|
|
|
294
221
|
* @throws {InternalServerError} When server encounters an error
|
|
295
222
|
*/
|
|
296
223
|
async sendMessage(options, transport) {
|
|
297
|
-
const { queueName, payload, idempotencyKey, retentionSeconds
|
|
224
|
+
const { queueName, payload, idempotencyKey, retentionSeconds } = options;
|
|
298
225
|
const headers = new Headers({
|
|
299
226
|
Authorization: `Bearer ${this.token}`,
|
|
300
227
|
"Vqs-Queue-Name": queueName,
|
|
301
228
|
"Content-Type": transport.contentType
|
|
302
229
|
});
|
|
230
|
+
if (process.env.VERCEL_DEPLOYMENT_ID) {
|
|
231
|
+
headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
|
|
232
|
+
}
|
|
303
233
|
if (idempotencyKey) {
|
|
304
234
|
headers.set("Vqs-Idempotency-Key", idempotencyKey);
|
|
305
235
|
}
|
|
306
236
|
if (retentionSeconds !== void 0) {
|
|
307
237
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
308
238
|
}
|
|
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
239
|
const body = transport.serialize(payload);
|
|
330
|
-
const response = await fetch(`${this.baseUrl}
|
|
240
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
331
241
|
method: "POST",
|
|
332
242
|
headers,
|
|
333
243
|
body
|
|
@@ -356,9 +266,6 @@ var VQSClient = class _VQSClient {
|
|
|
356
266
|
);
|
|
357
267
|
}
|
|
358
268
|
const responseData = await response.json();
|
|
359
|
-
if (localhostCallbacks.length > 0) {
|
|
360
|
-
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
361
|
-
}
|
|
362
269
|
return responseData;
|
|
363
270
|
}
|
|
364
271
|
/**
|
|
@@ -368,7 +275,7 @@ var VQSClient = class _VQSClient {
|
|
|
368
275
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
369
276
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
370
277
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
371
|
-
* @throws {MessageLockedError} When
|
|
278
|
+
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
372
279
|
* @throws {BadRequestError} When request parameters are invalid
|
|
373
280
|
* @throws {UnauthorizedError} When authentication fails
|
|
374
281
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -394,7 +301,7 @@ var VQSClient = class _VQSClient {
|
|
|
394
301
|
if (limit !== void 0) {
|
|
395
302
|
headers.set("Vqs-Limit", limit.toString());
|
|
396
303
|
}
|
|
397
|
-
const response = await fetch(`${this.baseUrl}
|
|
304
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
398
305
|
method: "GET",
|
|
399
306
|
headers
|
|
400
307
|
});
|
|
@@ -419,7 +326,7 @@ var VQSClient = class _VQSClient {
|
|
|
419
326
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
420
327
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
421
328
|
}
|
|
422
|
-
throw new MessageLockedError("next message
|
|
329
|
+
throw new MessageLockedError("next message", retryAfter);
|
|
423
330
|
}
|
|
424
331
|
if (response.status >= 500) {
|
|
425
332
|
throw new InternalServerError(
|
|
@@ -432,9 +339,9 @@ var VQSClient = class _VQSClient {
|
|
|
432
339
|
}
|
|
433
340
|
for await (const multipartMessage of parseMultipartStream(response)) {
|
|
434
341
|
try {
|
|
435
|
-
const parsedHeaders =
|
|
342
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
436
343
|
if (!parsedHeaders) {
|
|
437
|
-
console.warn("Missing required
|
|
344
|
+
console.warn("Missing required queue headers in multipart part");
|
|
438
345
|
await consumeStream(multipartMessage.payload);
|
|
439
346
|
continue;
|
|
440
347
|
}
|
|
@@ -476,7 +383,7 @@ var VQSClient = class _VQSClient {
|
|
|
476
383
|
headers.set("Vqs-Skip-Payload", "1");
|
|
477
384
|
}
|
|
478
385
|
const response = await fetch(
|
|
479
|
-
`${this.baseUrl}
|
|
386
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
480
387
|
{
|
|
481
388
|
method: "GET",
|
|
482
389
|
headers
|
|
@@ -505,37 +412,7 @@ var VQSClient = class _VQSClient {
|
|
|
505
412
|
}
|
|
506
413
|
throw new MessageLockedError(messageId, retryAfter);
|
|
507
414
|
}
|
|
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
415
|
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
416
|
throw new MessageNotAvailableError(messageId);
|
|
540
417
|
}
|
|
541
418
|
if (response.status >= 500) {
|
|
@@ -548,11 +425,11 @@ var VQSClient = class _VQSClient {
|
|
|
548
425
|
);
|
|
549
426
|
}
|
|
550
427
|
if (skipPayload && response.status === 204) {
|
|
551
|
-
const parsedHeaders =
|
|
428
|
+
const parsedHeaders = parseQueueHeaders(response.headers);
|
|
552
429
|
if (!parsedHeaders) {
|
|
553
430
|
throw new MessageCorruptedError(
|
|
554
431
|
messageId,
|
|
555
|
-
"Missing required
|
|
432
|
+
"Missing required queue headers in 204 response"
|
|
556
433
|
);
|
|
557
434
|
}
|
|
558
435
|
const message = {
|
|
@@ -567,9 +444,9 @@ var VQSClient = class _VQSClient {
|
|
|
567
444
|
try {
|
|
568
445
|
for await (const multipartMessage of parseMultipartStream(response)) {
|
|
569
446
|
try {
|
|
570
|
-
const parsedHeaders =
|
|
447
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
571
448
|
if (!parsedHeaders) {
|
|
572
|
-
console.warn("Missing required
|
|
449
|
+
console.warn("Missing required queue headers in multipart part");
|
|
573
450
|
await consumeStream(multipartMessage.payload);
|
|
574
451
|
continue;
|
|
575
452
|
}
|
|
@@ -615,7 +492,7 @@ var VQSClient = class _VQSClient {
|
|
|
615
492
|
async deleteMessage(options) {
|
|
616
493
|
const { queueName, consumerGroup, messageId, ticket } = options;
|
|
617
494
|
const response = await fetch(
|
|
618
|
-
`${this.baseUrl}
|
|
495
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
619
496
|
{
|
|
620
497
|
method: "DELETE",
|
|
621
498
|
headers: new Headers({
|
|
@@ -676,7 +553,7 @@ var VQSClient = class _VQSClient {
|
|
|
676
553
|
visibilityTimeoutSeconds
|
|
677
554
|
} = options;
|
|
678
555
|
const response = await fetch(
|
|
679
|
-
`${this.baseUrl}
|
|
556
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
680
557
|
{
|
|
681
558
|
method: "PATCH",
|
|
682
559
|
headers: new Headers({
|
|
@@ -722,82 +599,6 @@ var VQSClient = class _VQSClient {
|
|
|
722
599
|
}
|
|
723
600
|
};
|
|
724
601
|
|
|
725
|
-
// src/transports.ts
|
|
726
|
-
var JsonTransport = class {
|
|
727
|
-
contentType = "application/json";
|
|
728
|
-
serialize(value) {
|
|
729
|
-
return Buffer.from(JSON.stringify(value), "utf8");
|
|
730
|
-
}
|
|
731
|
-
async deserialize(stream) {
|
|
732
|
-
const reader = stream.getReader();
|
|
733
|
-
const chunks = [];
|
|
734
|
-
try {
|
|
735
|
-
while (true) {
|
|
736
|
-
const { done, value } = await reader.read();
|
|
737
|
-
if (done) break;
|
|
738
|
-
chunks.push(value);
|
|
739
|
-
}
|
|
740
|
-
} finally {
|
|
741
|
-
reader.releaseLock();
|
|
742
|
-
}
|
|
743
|
-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
744
|
-
const buffer = new Uint8Array(totalLength);
|
|
745
|
-
let offset = 0;
|
|
746
|
-
for (const chunk of chunks) {
|
|
747
|
-
buffer.set(chunk, offset);
|
|
748
|
-
offset += chunk.length;
|
|
749
|
-
}
|
|
750
|
-
return JSON.parse(Buffer.from(buffer).toString("utf8"));
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
var BufferTransport = class {
|
|
754
|
-
contentType = "application/octet-stream";
|
|
755
|
-
serialize(value) {
|
|
756
|
-
return value;
|
|
757
|
-
}
|
|
758
|
-
async deserialize(stream) {
|
|
759
|
-
const reader = stream.getReader();
|
|
760
|
-
const chunks = [];
|
|
761
|
-
try {
|
|
762
|
-
while (true) {
|
|
763
|
-
const { done, value } = await reader.read();
|
|
764
|
-
if (done) break;
|
|
765
|
-
chunks.push(value);
|
|
766
|
-
}
|
|
767
|
-
} finally {
|
|
768
|
-
reader.releaseLock();
|
|
769
|
-
}
|
|
770
|
-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
771
|
-
const buffer = new Uint8Array(totalLength);
|
|
772
|
-
let offset = 0;
|
|
773
|
-
for (const chunk of chunks) {
|
|
774
|
-
buffer.set(chunk, offset);
|
|
775
|
-
offset += chunk.length;
|
|
776
|
-
}
|
|
777
|
-
return Buffer.from(buffer);
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
var StreamTransport = class {
|
|
781
|
-
contentType = "application/octet-stream";
|
|
782
|
-
serialize(value) {
|
|
783
|
-
return value;
|
|
784
|
-
}
|
|
785
|
-
async deserialize(stream) {
|
|
786
|
-
return stream;
|
|
787
|
-
}
|
|
788
|
-
async finalize(payload) {
|
|
789
|
-
const reader = payload.getReader();
|
|
790
|
-
try {
|
|
791
|
-
while (true) {
|
|
792
|
-
const { done } = await reader.read();
|
|
793
|
-
if (done) break;
|
|
794
|
-
}
|
|
795
|
-
} finally {
|
|
796
|
-
reader.releaseLock();
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
};
|
|
800
|
-
|
|
801
602
|
// src/consumer-group.ts
|
|
802
603
|
var ConsumerGroup = class {
|
|
803
604
|
client;
|
|
@@ -808,7 +609,7 @@ var ConsumerGroup = class {
|
|
|
808
609
|
transport;
|
|
809
610
|
/**
|
|
810
611
|
* Create a new ConsumerGroup instance
|
|
811
|
-
* @param client
|
|
612
|
+
* @param client QueueClient instance to use for API calls
|
|
812
613
|
* @param topicName Name of the topic to consume from
|
|
813
614
|
* @param consumerGroupName Name of the consumer group
|
|
814
615
|
* @param options Optional configuration
|
|
@@ -897,7 +698,11 @@ var ConsumerGroup = class {
|
|
|
897
698
|
message.ticket
|
|
898
699
|
);
|
|
899
700
|
try {
|
|
900
|
-
const result = await handler(message
|
|
701
|
+
const result = await handler(message.payload, {
|
|
702
|
+
messageId: message.messageId,
|
|
703
|
+
deliveryCount: message.deliveryCount,
|
|
704
|
+
createdAt: message.createdAt
|
|
705
|
+
});
|
|
901
706
|
await stopExtension();
|
|
902
707
|
if (result && "timeoutSeconds" in result) {
|
|
903
708
|
await this.client.changeVisibility({
|
|
@@ -927,219 +732,58 @@ var ConsumerGroup = class {
|
|
|
927
732
|
throw error;
|
|
928
733
|
}
|
|
929
734
|
}
|
|
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(
|
|
735
|
+
async consume(handler, options) {
|
|
736
|
+
if (options?.messageId) {
|
|
737
|
+
if (options.skipPayload) {
|
|
738
|
+
const response = await this.client.receiveMessageById(
|
|
942
739
|
{
|
|
943
740
|
queueName: this.topicName,
|
|
944
741
|
consumerGroup: this.consumerGroupName,
|
|
742
|
+
messageId: options.messageId,
|
|
945
743
|
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
946
|
-
|
|
947
|
-
// Always process one message at a time
|
|
744
|
+
skipPayload: true
|
|
948
745
|
},
|
|
949
746
|
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;
|
|
747
|
+
);
|
|
748
|
+
await this.processMessage(
|
|
749
|
+
response.message,
|
|
750
|
+
handler
|
|
751
|
+
);
|
|
752
|
+
} else {
|
|
753
|
+
const response = await this.client.receiveMessageById(
|
|
754
|
+
{
|
|
755
|
+
queueName: this.topicName,
|
|
756
|
+
consumerGroup: this.consumerGroupName,
|
|
757
|
+
messageId: options.messageId,
|
|
758
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
759
|
+
},
|
|
760
|
+
this.transport
|
|
761
|
+
);
|
|
762
|
+
await this.processMessage(
|
|
763
|
+
response.message,
|
|
764
|
+
handler
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
let messageFound = false;
|
|
769
|
+
for await (const message of this.client.receiveMessages(
|
|
770
|
+
{
|
|
771
|
+
queueName: this.topicName,
|
|
772
|
+
consumerGroup: this.consumerGroupName,
|
|
773
|
+
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
774
|
+
limit: 1
|
|
775
|
+
},
|
|
776
|
+
this.transport
|
|
777
|
+
)) {
|
|
778
|
+
messageFound = true;
|
|
779
|
+
await this.processMessage(message, handler);
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
if (!messageFound) {
|
|
783
|
+
throw new Error("No messages available");
|
|
1009
784
|
}
|
|
1010
785
|
}
|
|
1011
786
|
}
|
|
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
787
|
/**
|
|
1144
788
|
* Get the consumer group name
|
|
1145
789
|
*/
|
|
@@ -1161,7 +805,7 @@ var Topic = class {
|
|
|
1161
805
|
transport;
|
|
1162
806
|
/**
|
|
1163
807
|
* Create a new Topic instance
|
|
1164
|
-
* @param client
|
|
808
|
+
* @param client QueueClient instance to use for API calls
|
|
1165
809
|
* @param topicName Name of the topic to work with
|
|
1166
810
|
* @param transport Optional serializer/deserializer for the payload (defaults to JSON)
|
|
1167
811
|
*/
|
|
@@ -1186,8 +830,7 @@ var Topic = class {
|
|
|
1186
830
|
queueName: this.topicName,
|
|
1187
831
|
payload,
|
|
1188
832
|
idempotencyKey: options?.idempotencyKey,
|
|
1189
|
-
retentionSeconds: options?.retentionSeconds
|
|
1190
|
-
callbacks: options?.callbacks
|
|
833
|
+
retentionSeconds: options?.retentionSeconds
|
|
1191
834
|
},
|
|
1192
835
|
this.transport
|
|
1193
836
|
);
|
|
@@ -1226,40 +869,127 @@ var Topic = class {
|
|
|
1226
869
|
};
|
|
1227
870
|
|
|
1228
871
|
// src/factory.ts
|
|
1229
|
-
function
|
|
1230
|
-
|
|
872
|
+
async function send(topicName, payload, options) {
|
|
873
|
+
const transport = options?.transport || new JsonTransport();
|
|
874
|
+
const client = new QueueClient();
|
|
875
|
+
const result = await client.sendMessage(
|
|
876
|
+
{
|
|
877
|
+
queueName: topicName,
|
|
878
|
+
payload,
|
|
879
|
+
idempotencyKey: options?.idempotencyKey,
|
|
880
|
+
retentionSeconds: options?.retentionSeconds
|
|
881
|
+
},
|
|
882
|
+
transport
|
|
883
|
+
);
|
|
884
|
+
return { messageId: result.messageId };
|
|
885
|
+
}
|
|
886
|
+
async function receive(topicName, consumerGroup, handler, options) {
|
|
887
|
+
const transport = options?.transport || new JsonTransport();
|
|
888
|
+
const client = new QueueClient();
|
|
889
|
+
const topic = new Topic(client, topicName, transport);
|
|
890
|
+
const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
|
|
891
|
+
const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
|
|
892
|
+
if (messageId) {
|
|
893
|
+
if (skipPayload) {
|
|
894
|
+
return consumer.consume(handler, {
|
|
895
|
+
messageId,
|
|
896
|
+
skipPayload: true
|
|
897
|
+
});
|
|
898
|
+
} else {
|
|
899
|
+
return consumer.consume(handler, { messageId });
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
return consumer.consume(handler);
|
|
903
|
+
}
|
|
1231
904
|
}
|
|
1232
905
|
|
|
1233
906
|
// src/callback.ts
|
|
1234
|
-
function parseCallbackRequest(request) {
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
const missingHeaders = [];
|
|
1240
|
-
if (!messageId) missingHeaders.push("Vqs-Message-Id");
|
|
1241
|
-
if (!queueName) missingHeaders.push("Vqs-Queue-Name");
|
|
1242
|
-
if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
|
|
1243
|
-
if (missingHeaders.length > 0) {
|
|
1244
|
-
throw new InvalidCallbackError(
|
|
1245
|
-
`Missing required VQS callback headers: ${missingHeaders.join(", ")}`
|
|
907
|
+
async function parseCallbackRequest(request) {
|
|
908
|
+
const contentType = request.headers.get("content-type");
|
|
909
|
+
if (!contentType || !contentType.includes("application/cloudevents+json")) {
|
|
910
|
+
throw new Error(
|
|
911
|
+
"Invalid content type: expected 'application/cloudevents+json'"
|
|
1246
912
|
);
|
|
1247
913
|
}
|
|
914
|
+
let cloudEvent;
|
|
915
|
+
try {
|
|
916
|
+
cloudEvent = await request.json();
|
|
917
|
+
} catch (error) {
|
|
918
|
+
throw new Error("Failed to parse CloudEvent from request body");
|
|
919
|
+
}
|
|
920
|
+
if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
|
|
921
|
+
throw new Error("Invalid CloudEvent: missing required fields");
|
|
922
|
+
}
|
|
923
|
+
if (cloudEvent.type !== "com.vercel.queue.v1beta") {
|
|
924
|
+
throw new Error(
|
|
925
|
+
`Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
const missingFields = [];
|
|
929
|
+
if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
|
|
930
|
+
if (!("consumerGroup" in cloudEvent.data))
|
|
931
|
+
missingFields.push("consumerGroup");
|
|
932
|
+
if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
|
|
933
|
+
if (missingFields.length > 0) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`Missing required CloudEvent data fields: ${missingFields.join(", ")}`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
const { messageId, queueName, consumerGroup } = cloudEvent.data;
|
|
1248
939
|
return {
|
|
1249
|
-
messageId,
|
|
1250
940
|
queueName,
|
|
1251
|
-
consumerGroup
|
|
941
|
+
consumerGroup,
|
|
942
|
+
messageId
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function handleCallback(handlers) {
|
|
946
|
+
return async (request) => {
|
|
947
|
+
try {
|
|
948
|
+
const { queueName, consumerGroup, messageId } = await parseCallbackRequest(request);
|
|
949
|
+
const topicHandler = handlers[queueName];
|
|
950
|
+
if (!topicHandler) {
|
|
951
|
+
const availableTopics = Object.keys(handlers).join(", ");
|
|
952
|
+
return Response.json(
|
|
953
|
+
{
|
|
954
|
+
error: `No handler found for topic: ${queueName}`,
|
|
955
|
+
availableTopics
|
|
956
|
+
},
|
|
957
|
+
{ status: 404 }
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
961
|
+
if (!consumerGroupHandler) {
|
|
962
|
+
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
963
|
+
return Response.json(
|
|
964
|
+
{
|
|
965
|
+
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
966
|
+
availableGroups
|
|
967
|
+
},
|
|
968
|
+
{ status: 404 }
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
const client = new QueueClient();
|
|
972
|
+
const topic = new Topic(client, queueName);
|
|
973
|
+
const cg = topic.consumerGroup(consumerGroup);
|
|
974
|
+
await cg.consume(consumerGroupHandler, { messageId });
|
|
975
|
+
return Response.json({ status: "success" });
|
|
976
|
+
} catch (error) {
|
|
977
|
+
console.error("Queue callback error:", error);
|
|
978
|
+
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"))) {
|
|
979
|
+
return Response.json({ error: error.message }, { status: 400 });
|
|
980
|
+
}
|
|
981
|
+
return Response.json(
|
|
982
|
+
{ error: "Failed to process queue message" },
|
|
983
|
+
{ status: 500 }
|
|
984
|
+
);
|
|
985
|
+
}
|
|
1252
986
|
};
|
|
1253
987
|
}
|
|
1254
988
|
export {
|
|
1255
989
|
BadRequestError,
|
|
1256
990
|
BufferTransport,
|
|
1257
|
-
ConsumerGroup,
|
|
1258
|
-
FailedDependencyError,
|
|
1259
|
-
FifoOrderingViolationError,
|
|
1260
991
|
ForbiddenError,
|
|
1261
992
|
InternalServerError,
|
|
1262
|
-
InvalidCallbackError,
|
|
1263
993
|
InvalidLimitError,
|
|
1264
994
|
JsonTransport,
|
|
1265
995
|
MessageCorruptedError,
|
|
@@ -1268,11 +998,9 @@ export {
|
|
|
1268
998
|
MessageNotFoundError,
|
|
1269
999
|
QueueEmptyError,
|
|
1270
1000
|
StreamTransport,
|
|
1271
|
-
Topic,
|
|
1272
1001
|
UnauthorizedError,
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
parseCallbackRequest
|
|
1002
|
+
handleCallback,
|
|
1003
|
+
receive,
|
|
1004
|
+
send
|
|
1277
1005
|
};
|
|
1278
1006
|
//# sourceMappingURL=index.mjs.map
|