@vercel/queue 0.0.0-alpha.1 → 0.0.0-alpha.10
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 +221 -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 +1 -1
package/dist/index.js
CHANGED
|
@@ -22,12 +22,8 @@ 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,
|
|
@@ -36,140 +32,98 @@ __export(index_exports, {
|
|
|
36
32
|
MessageNotFoundError: () => MessageNotFoundError,
|
|
37
33
|
QueueEmptyError: () => QueueEmptyError,
|
|
38
34
|
StreamTransport: () => StreamTransport,
|
|
39
|
-
Topic: () => Topic,
|
|
40
35
|
UnauthorizedError: () => UnauthorizedError,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
parseCallbackRequest: () => parseCallbackRequest
|
|
36
|
+
handleCallback: () => handleCallback,
|
|
37
|
+
receive: () => receive,
|
|
38
|
+
send: () => send
|
|
45
39
|
});
|
|
46
40
|
module.exports = __toCommonJS(index_exports);
|
|
47
41
|
|
|
48
|
-
// src/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
);
|
|
42
|
+
// src/transports.ts
|
|
43
|
+
var JsonTransport = class {
|
|
44
|
+
contentType = "application/json";
|
|
45
|
+
serialize(value) {
|
|
46
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
58
47
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return
|
|
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"));
|
|
75
64
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
function processDevelopmentCallbacks(callbacks) {
|
|
82
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
83
|
-
if (!isDevelopment) {
|
|
84
|
-
return [];
|
|
65
|
+
};
|
|
66
|
+
var BufferTransport = class {
|
|
67
|
+
contentType = "application/octet-stream";
|
|
68
|
+
serialize(value) {
|
|
69
|
+
return value;
|
|
85
70
|
}
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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();
|
|
95
82
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
localhostCallbacks.push({ group, config, port });
|
|
103
|
-
} else {
|
|
104
|
-
console.warn(
|
|
105
|
-
`VQS Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
|
|
106
|
-
);
|
|
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;
|
|
107
89
|
}
|
|
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
|
-
`VQS: 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;
|
|
90
|
+
return Buffer.from(buffer);
|
|
133
91
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
});
|
|
140
|
-
headerArgs = headerArray.join(" ");
|
|
92
|
+
};
|
|
93
|
+
var StreamTransport = class {
|
|
94
|
+
contentType = "application/octet-stream";
|
|
95
|
+
serialize(value) {
|
|
96
|
+
return value;
|
|
141
97
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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");
|
|
145
116
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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();
|
|
117
|
+
// src/oidc.ts
|
|
118
|
+
function getVercelOidcToken() {
|
|
119
|
+
const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
|
|
120
|
+
const fromSymbol = globalThis;
|
|
121
|
+
const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
|
|
122
|
+
const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
|
|
123
|
+
if (!token) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return token;
|
|
173
127
|
}
|
|
174
128
|
|
|
175
129
|
// src/types.ts
|
|
@@ -187,16 +141,6 @@ var MessageNotAvailableError = class extends Error {
|
|
|
187
141
|
this.name = "MessageNotAvailableError";
|
|
188
142
|
}
|
|
189
143
|
};
|
|
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
144
|
var MessageCorruptedError = class extends Error {
|
|
201
145
|
constructor(messageId, reason) {
|
|
202
146
|
super(`Message ${messageId} is corrupted: ${reason}`);
|
|
@@ -238,16 +182,6 @@ var BadRequestError = class extends Error {
|
|
|
238
182
|
this.name = "BadRequestError";
|
|
239
183
|
}
|
|
240
184
|
};
|
|
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
185
|
var InternalServerError = class extends Error {
|
|
252
186
|
constructor(message = "Unexpected server error") {
|
|
253
187
|
super(message);
|
|
@@ -260,12 +194,6 @@ var InvalidLimitError = class extends Error {
|
|
|
260
194
|
this.name = "InvalidLimitError";
|
|
261
195
|
}
|
|
262
196
|
};
|
|
263
|
-
var InvalidCallbackError = class extends Error {
|
|
264
|
-
constructor(message) {
|
|
265
|
-
super(message);
|
|
266
|
-
this.name = "InvalidCallbackError";
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
197
|
|
|
270
198
|
// src/client.ts
|
|
271
199
|
async function consumeStream(stream) {
|
|
@@ -279,7 +207,7 @@ async function consumeStream(stream) {
|
|
|
279
207
|
reader.releaseLock();
|
|
280
208
|
}
|
|
281
209
|
}
|
|
282
|
-
function
|
|
210
|
+
function parseQueueHeaders(headers) {
|
|
283
211
|
const messageId = headers.get("Vqs-Message-Id");
|
|
284
212
|
const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
|
|
285
213
|
const timestamp = headers.get("Vqs-Timestamp");
|
|
@@ -295,40 +223,33 @@ function parseVQSHeaders(headers) {
|
|
|
295
223
|
return {
|
|
296
224
|
messageId,
|
|
297
225
|
deliveryCount,
|
|
298
|
-
timestamp,
|
|
226
|
+
createdAt: new Date(timestamp),
|
|
299
227
|
contentType,
|
|
300
228
|
ticket
|
|
301
229
|
};
|
|
302
230
|
}
|
|
303
|
-
var
|
|
231
|
+
var QueueClient = class {
|
|
304
232
|
baseUrl;
|
|
233
|
+
basePath;
|
|
305
234
|
token;
|
|
306
235
|
/**
|
|
307
236
|
* Create a new Vercel Queue Service client
|
|
308
|
-
* @param options Client configuration options
|
|
237
|
+
* @param options Client configuration options (optional - will auto-detect Vercel Function environment)
|
|
309
238
|
*/
|
|
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
|
-
);
|
|
239
|
+
constructor(options = {}) {
|
|
240
|
+
this.baseUrl = options.baseUrl || "https://api.vercel.com";
|
|
241
|
+
this.basePath = options.basePath || "/v1/queues/messages";
|
|
242
|
+
if (options.token) {
|
|
243
|
+
this.token = options.token;
|
|
244
|
+
} else {
|
|
245
|
+
const token = getVercelOidcToken();
|
|
246
|
+
if (!token) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"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'"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
this.token = token;
|
|
327
252
|
}
|
|
328
|
-
return new _VQSClient({
|
|
329
|
-
token,
|
|
330
|
-
baseUrl
|
|
331
|
-
});
|
|
332
253
|
}
|
|
333
254
|
/**
|
|
334
255
|
* Send a message to a queue
|
|
@@ -341,40 +262,23 @@ var VQSClient = class _VQSClient {
|
|
|
341
262
|
* @throws {InternalServerError} When server encounters an error
|
|
342
263
|
*/
|
|
343
264
|
async sendMessage(options, transport) {
|
|
344
|
-
const { queueName, payload, idempotencyKey, retentionSeconds
|
|
265
|
+
const { queueName, payload, idempotencyKey, retentionSeconds } = options;
|
|
345
266
|
const headers = new Headers({
|
|
346
267
|
Authorization: `Bearer ${this.token}`,
|
|
347
268
|
"Vqs-Queue-Name": queueName,
|
|
348
269
|
"Content-Type": transport.contentType
|
|
349
270
|
});
|
|
271
|
+
if (process.env.VERCEL_DEPLOYMENT_ID) {
|
|
272
|
+
headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
|
|
273
|
+
}
|
|
350
274
|
if (idempotencyKey) {
|
|
351
275
|
headers.set("Vqs-Idempotency-Key", idempotencyKey);
|
|
352
276
|
}
|
|
353
277
|
if (retentionSeconds !== void 0) {
|
|
354
278
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
355
279
|
}
|
|
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
280
|
const body = transport.serialize(payload);
|
|
377
|
-
const response = await fetch(`${this.baseUrl}
|
|
281
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
378
282
|
method: "POST",
|
|
379
283
|
headers,
|
|
380
284
|
body
|
|
@@ -403,9 +307,6 @@ var VQSClient = class _VQSClient {
|
|
|
403
307
|
);
|
|
404
308
|
}
|
|
405
309
|
const responseData = await response.json();
|
|
406
|
-
if (localhostCallbacks.length > 0) {
|
|
407
|
-
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
408
|
-
}
|
|
409
310
|
return responseData;
|
|
410
311
|
}
|
|
411
312
|
/**
|
|
@@ -415,7 +316,7 @@ var VQSClient = class _VQSClient {
|
|
|
415
316
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
416
317
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
417
318
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
418
|
-
* @throws {MessageLockedError} When
|
|
319
|
+
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
419
320
|
* @throws {BadRequestError} When request parameters are invalid
|
|
420
321
|
* @throws {UnauthorizedError} When authentication fails
|
|
421
322
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -441,7 +342,7 @@ var VQSClient = class _VQSClient {
|
|
|
441
342
|
if (limit !== void 0) {
|
|
442
343
|
headers.set("Vqs-Limit", limit.toString());
|
|
443
344
|
}
|
|
444
|
-
const response = await fetch(`${this.baseUrl}
|
|
345
|
+
const response = await fetch(`${this.baseUrl}${this.basePath}`, {
|
|
445
346
|
method: "GET",
|
|
446
347
|
headers
|
|
447
348
|
});
|
|
@@ -466,7 +367,7 @@ var VQSClient = class _VQSClient {
|
|
|
466
367
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
467
368
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
468
369
|
}
|
|
469
|
-
throw new MessageLockedError("next message
|
|
370
|
+
throw new MessageLockedError("next message", retryAfter);
|
|
470
371
|
}
|
|
471
372
|
if (response.status >= 500) {
|
|
472
373
|
throw new InternalServerError(
|
|
@@ -479,9 +380,9 @@ var VQSClient = class _VQSClient {
|
|
|
479
380
|
}
|
|
480
381
|
for await (const multipartMessage of (0, import_mixpart.parseMultipartStream)(response)) {
|
|
481
382
|
try {
|
|
482
|
-
const parsedHeaders =
|
|
383
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
483
384
|
if (!parsedHeaders) {
|
|
484
|
-
console.warn("Missing required
|
|
385
|
+
console.warn("Missing required queue headers in multipart part");
|
|
485
386
|
await consumeStream(multipartMessage.payload);
|
|
486
387
|
continue;
|
|
487
388
|
}
|
|
@@ -523,7 +424,7 @@ var VQSClient = class _VQSClient {
|
|
|
523
424
|
headers.set("Vqs-Skip-Payload", "1");
|
|
524
425
|
}
|
|
525
426
|
const response = await fetch(
|
|
526
|
-
`${this.baseUrl}
|
|
427
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
527
428
|
{
|
|
528
429
|
method: "GET",
|
|
529
430
|
headers
|
|
@@ -552,37 +453,7 @@ var VQSClient = class _VQSClient {
|
|
|
552
453
|
}
|
|
553
454
|
throw new MessageLockedError(messageId, retryAfter);
|
|
554
455
|
}
|
|
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
456
|
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
457
|
throw new MessageNotAvailableError(messageId);
|
|
587
458
|
}
|
|
588
459
|
if (response.status >= 500) {
|
|
@@ -595,11 +466,11 @@ var VQSClient = class _VQSClient {
|
|
|
595
466
|
);
|
|
596
467
|
}
|
|
597
468
|
if (skipPayload && response.status === 204) {
|
|
598
|
-
const parsedHeaders =
|
|
469
|
+
const parsedHeaders = parseQueueHeaders(response.headers);
|
|
599
470
|
if (!parsedHeaders) {
|
|
600
471
|
throw new MessageCorruptedError(
|
|
601
472
|
messageId,
|
|
602
|
-
"Missing required
|
|
473
|
+
"Missing required queue headers in 204 response"
|
|
603
474
|
);
|
|
604
475
|
}
|
|
605
476
|
const message = {
|
|
@@ -614,9 +485,9 @@ var VQSClient = class _VQSClient {
|
|
|
614
485
|
try {
|
|
615
486
|
for await (const multipartMessage of (0, import_mixpart.parseMultipartStream)(response)) {
|
|
616
487
|
try {
|
|
617
|
-
const parsedHeaders =
|
|
488
|
+
const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
|
|
618
489
|
if (!parsedHeaders) {
|
|
619
|
-
console.warn("Missing required
|
|
490
|
+
console.warn("Missing required queue headers in multipart part");
|
|
620
491
|
await consumeStream(multipartMessage.payload);
|
|
621
492
|
continue;
|
|
622
493
|
}
|
|
@@ -662,7 +533,7 @@ var VQSClient = class _VQSClient {
|
|
|
662
533
|
async deleteMessage(options) {
|
|
663
534
|
const { queueName, consumerGroup, messageId, ticket } = options;
|
|
664
535
|
const response = await fetch(
|
|
665
|
-
`${this.baseUrl}
|
|
536
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
666
537
|
{
|
|
667
538
|
method: "DELETE",
|
|
668
539
|
headers: new Headers({
|
|
@@ -723,7 +594,7 @@ var VQSClient = class _VQSClient {
|
|
|
723
594
|
visibilityTimeoutSeconds
|
|
724
595
|
} = options;
|
|
725
596
|
const response = await fetch(
|
|
726
|
-
`${this.baseUrl}
|
|
597
|
+
`${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
|
|
727
598
|
{
|
|
728
599
|
method: "PATCH",
|
|
729
600
|
headers: new Headers({
|
|
@@ -769,82 +640,6 @@ var VQSClient = class _VQSClient {
|
|
|
769
640
|
}
|
|
770
641
|
};
|
|
771
642
|
|
|
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
|
-
|
|
848
643
|
// src/consumer-group.ts
|
|
849
644
|
var ConsumerGroup = class {
|
|
850
645
|
client;
|
|
@@ -855,7 +650,7 @@ var ConsumerGroup = class {
|
|
|
855
650
|
transport;
|
|
856
651
|
/**
|
|
857
652
|
* Create a new ConsumerGroup instance
|
|
858
|
-
* @param client
|
|
653
|
+
* @param client QueueClient instance to use for API calls
|
|
859
654
|
* @param topicName Name of the topic to consume from
|
|
860
655
|
* @param consumerGroupName Name of the consumer group
|
|
861
656
|
* @param options Optional configuration
|
|
@@ -944,7 +739,11 @@ var ConsumerGroup = class {
|
|
|
944
739
|
message.ticket
|
|
945
740
|
);
|
|
946
741
|
try {
|
|
947
|
-
const result = await handler(message
|
|
742
|
+
const result = await handler(message.payload, {
|
|
743
|
+
messageId: message.messageId,
|
|
744
|
+
deliveryCount: message.deliveryCount,
|
|
745
|
+
createdAt: message.createdAt
|
|
746
|
+
});
|
|
948
747
|
await stopExtension();
|
|
949
748
|
if (result && "timeoutSeconds" in result) {
|
|
950
749
|
await this.client.changeVisibility({
|
|
@@ -974,219 +773,58 @@ var ConsumerGroup = class {
|
|
|
974
773
|
throw error;
|
|
975
774
|
}
|
|
976
775
|
}
|
|
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(
|
|
776
|
+
async consume(handler, options) {
|
|
777
|
+
if (options?.messageId) {
|
|
778
|
+
if (options.skipPayload) {
|
|
779
|
+
const response = await this.client.receiveMessageById(
|
|
989
780
|
{
|
|
990
781
|
queueName: this.topicName,
|
|
991
782
|
consumerGroup: this.consumerGroupName,
|
|
783
|
+
messageId: options.messageId,
|
|
992
784
|
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
993
|
-
|
|
994
|
-
// Always process one message at a time
|
|
785
|
+
skipPayload: true
|
|
995
786
|
},
|
|
996
787
|
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;
|
|
788
|
+
);
|
|
789
|
+
await this.processMessage(
|
|
790
|
+
response.message,
|
|
791
|
+
handler
|
|
792
|
+
);
|
|
793
|
+
} else {
|
|
794
|
+
const response = await this.client.receiveMessageById(
|
|
795
|
+
{
|
|
796
|
+
queueName: this.topicName,
|
|
797
|
+
consumerGroup: this.consumerGroupName,
|
|
798
|
+
messageId: options.messageId,
|
|
799
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
800
|
+
},
|
|
801
|
+
this.transport
|
|
802
|
+
);
|
|
803
|
+
await this.processMessage(
|
|
804
|
+
response.message,
|
|
805
|
+
handler
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
} else {
|
|
809
|
+
let messageFound = false;
|
|
810
|
+
for await (const message of this.client.receiveMessages(
|
|
811
|
+
{
|
|
812
|
+
queueName: this.topicName,
|
|
813
|
+
consumerGroup: this.consumerGroupName,
|
|
814
|
+
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
815
|
+
limit: 1
|
|
816
|
+
},
|
|
817
|
+
this.transport
|
|
818
|
+
)) {
|
|
819
|
+
messageFound = true;
|
|
820
|
+
await this.processMessage(message, handler);
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
if (!messageFound) {
|
|
824
|
+
throw new Error("No messages available");
|
|
1056
825
|
}
|
|
1057
826
|
}
|
|
1058
827
|
}
|
|
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
828
|
/**
|
|
1191
829
|
* Get the consumer group name
|
|
1192
830
|
*/
|
|
@@ -1208,7 +846,7 @@ var Topic = class {
|
|
|
1208
846
|
transport;
|
|
1209
847
|
/**
|
|
1210
848
|
* Create a new Topic instance
|
|
1211
|
-
* @param client
|
|
849
|
+
* @param client QueueClient instance to use for API calls
|
|
1212
850
|
* @param topicName Name of the topic to work with
|
|
1213
851
|
* @param transport Optional serializer/deserializer for the payload (defaults to JSON)
|
|
1214
852
|
*/
|
|
@@ -1233,8 +871,7 @@ var Topic = class {
|
|
|
1233
871
|
queueName: this.topicName,
|
|
1234
872
|
payload,
|
|
1235
873
|
idempotencyKey: options?.idempotencyKey,
|
|
1236
|
-
retentionSeconds: options?.retentionSeconds
|
|
1237
|
-
callbacks: options?.callbacks
|
|
874
|
+
retentionSeconds: options?.retentionSeconds
|
|
1238
875
|
},
|
|
1239
876
|
this.transport
|
|
1240
877
|
);
|
|
@@ -1273,41 +910,128 @@ var Topic = class {
|
|
|
1273
910
|
};
|
|
1274
911
|
|
|
1275
912
|
// src/factory.ts
|
|
1276
|
-
function
|
|
1277
|
-
|
|
913
|
+
async function send(topicName, payload, options) {
|
|
914
|
+
const transport = options?.transport || new JsonTransport();
|
|
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
|
+
}
|
|
1278
945
|
}
|
|
1279
946
|
|
|
1280
947
|
// src/callback.ts
|
|
1281
|
-
function parseCallbackRequest(request) {
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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 VQS callback headers: ${missingHeaders.join(", ")}`
|
|
948
|
+
async function parseCallbackRequest(request) {
|
|
949
|
+
const contentType = request.headers.get("content-type");
|
|
950
|
+
if (!contentType || !contentType.includes("application/cloudevents+json")) {
|
|
951
|
+
throw new Error(
|
|
952
|
+
"Invalid content type: expected 'application/cloudevents+json'"
|
|
1293
953
|
);
|
|
1294
954
|
}
|
|
955
|
+
let cloudEvent;
|
|
956
|
+
try {
|
|
957
|
+
cloudEvent = await request.json();
|
|
958
|
+
} catch (error) {
|
|
959
|
+
throw new Error("Failed to parse CloudEvent from request body");
|
|
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(", ")}`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const { messageId, queueName, consumerGroup } = cloudEvent.data;
|
|
1295
980
|
return {
|
|
1296
|
-
messageId,
|
|
1297
981
|
queueName,
|
|
1298
|
-
consumerGroup
|
|
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
|
+
}
|
|
1299
1027
|
};
|
|
1300
1028
|
}
|
|
1301
1029
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1302
1030
|
0 && (module.exports = {
|
|
1303
1031
|
BadRequestError,
|
|
1304
1032
|
BufferTransport,
|
|
1305
|
-
ConsumerGroup,
|
|
1306
|
-
FailedDependencyError,
|
|
1307
|
-
FifoOrderingViolationError,
|
|
1308
1033
|
ForbiddenError,
|
|
1309
1034
|
InternalServerError,
|
|
1310
|
-
InvalidCallbackError,
|
|
1311
1035
|
InvalidLimitError,
|
|
1312
1036
|
JsonTransport,
|
|
1313
1037
|
MessageCorruptedError,
|
|
@@ -1316,11 +1040,9 @@ function parseCallbackRequest(request) {
|
|
|
1316
1040
|
MessageNotFoundError,
|
|
1317
1041
|
QueueEmptyError,
|
|
1318
1042
|
StreamTransport,
|
|
1319
|
-
Topic,
|
|
1320
1043
|
UnauthorizedError,
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
parseCallbackRequest
|
|
1044
|
+
handleCallback,
|
|
1045
|
+
receive,
|
|
1046
|
+
send
|
|
1325
1047
|
});
|
|
1326
1048
|
//# sourceMappingURL=index.js.map
|