@vercel/queue 0.0.0-alpha.37 → 0.0.0-alpha.38
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 +208 -168
- package/dist/{types-CAA8nT8x.d.mts → callback-lq_sorrn.d.mts} +249 -16
- package/dist/{types-CAA8nT8x.d.ts → callback-lq_sorrn.d.ts} +249 -16
- package/dist/index.d.mts +74 -333
- package/dist/index.d.ts +74 -333
- package/dist/index.js +713 -679
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +709 -678
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs-pages.d.mts +47 -27
- package/dist/nextjs-pages.d.ts +47 -27
- package/dist/nextjs-pages.js +320 -313
- package/dist/nextjs-pages.js.map +1 -1
- package/dist/nextjs-pages.mjs +320 -313
- package/dist/nextjs-pages.mjs.map +1 -1
- package/dist/web.d.mts +60 -0
- package/dist/web.d.ts +60 -0
- package/dist/web.js +1457 -0
- package/dist/web.js.map +1 -0
- package/dist/web.mjs +1420 -0
- package/dist/web.mjs.map +1 -0
- package/package.json +11 -1
package/dist/nextjs-pages.mjs
CHANGED
|
@@ -5,46 +5,6 @@ import { parseMultipartStream } from "mixpart";
|
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import * as path from "path";
|
|
7
7
|
|
|
8
|
-
// src/transports.ts
|
|
9
|
-
async function streamToBuffer(stream) {
|
|
10
|
-
let totalLength = 0;
|
|
11
|
-
const reader = stream.getReader();
|
|
12
|
-
const chunks = [];
|
|
13
|
-
try {
|
|
14
|
-
while (true) {
|
|
15
|
-
const { done, value } = await reader.read();
|
|
16
|
-
if (done) break;
|
|
17
|
-
chunks.push(value);
|
|
18
|
-
totalLength += value.length;
|
|
19
|
-
}
|
|
20
|
-
} finally {
|
|
21
|
-
reader.releaseLock();
|
|
22
|
-
}
|
|
23
|
-
return Buffer.concat(chunks, totalLength);
|
|
24
|
-
}
|
|
25
|
-
var JsonTransport = class {
|
|
26
|
-
contentType = "application/json";
|
|
27
|
-
replacer;
|
|
28
|
-
reviver;
|
|
29
|
-
/**
|
|
30
|
-
* Create a new JsonTransport.
|
|
31
|
-
* @param options - Optional JSON serialization options
|
|
32
|
-
* @param options.replacer - Custom replacer for JSON.stringify
|
|
33
|
-
* @param options.reviver - Custom reviver for JSON.parse
|
|
34
|
-
*/
|
|
35
|
-
constructor(options = {}) {
|
|
36
|
-
this.replacer = options.replacer;
|
|
37
|
-
this.reviver = options.reviver;
|
|
38
|
-
}
|
|
39
|
-
serialize(value) {
|
|
40
|
-
return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
|
|
41
|
-
}
|
|
42
|
-
async deserialize(stream) {
|
|
43
|
-
const buffer = await streamToBuffer(stream);
|
|
44
|
-
return JSON.parse(buffer.toString("utf8"), this.reviver);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
8
|
// src/types.ts
|
|
49
9
|
var MessageNotFoundError = class extends Error {
|
|
50
10
|
constructor(messageId) {
|
|
@@ -66,14 +26,6 @@ var MessageCorruptedError = class extends Error {
|
|
|
66
26
|
this.name = "MessageCorruptedError";
|
|
67
27
|
}
|
|
68
28
|
};
|
|
69
|
-
var QueueEmptyError = class extends Error {
|
|
70
|
-
constructor(queueName, consumerGroup) {
|
|
71
|
-
super(
|
|
72
|
-
`No messages available in queue "${queueName}" for consumer group "${consumerGroup}"`
|
|
73
|
-
);
|
|
74
|
-
this.name = "QueueEmptyError";
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
29
|
var UnauthorizedError = class extends Error {
|
|
78
30
|
constructor(message = "Missing or invalid authentication token") {
|
|
79
31
|
super(message);
|
|
@@ -136,12 +88,15 @@ var ConsumerRegistryNotConfiguredError = class extends Error {
|
|
|
136
88
|
// src/dev.ts
|
|
137
89
|
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
138
90
|
function filePathToUrlPath(filePath) {
|
|
139
|
-
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|js|tsx|jsx)$/, "").replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
91
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|mts|js|mjs|tsx|jsx)$/, "").replace(/\.(ts|mts|js|mjs|tsx|jsx)$/, "");
|
|
140
92
|
if (!urlPath.startsWith("/")) {
|
|
141
93
|
urlPath = "/" + urlPath;
|
|
142
94
|
}
|
|
143
95
|
return urlPath;
|
|
144
96
|
}
|
|
97
|
+
function filePathToConsumerGroup(filePath) {
|
|
98
|
+
return filePath.replace(/_/g, "__").replace(/\//g, "_S").replace(/\./g, "_D");
|
|
99
|
+
}
|
|
145
100
|
function getDevRouteMappings() {
|
|
146
101
|
const g = globalThis;
|
|
147
102
|
if (ROUTE_MAPPINGS_KEY in g) {
|
|
@@ -162,11 +117,11 @@ function getDevRouteMappings() {
|
|
|
162
117
|
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
163
118
|
if (!config.experimentalTriggers) continue;
|
|
164
119
|
for (const trigger of config.experimentalTriggers) {
|
|
165
|
-
if (trigger.type?.startsWith("queue/") && trigger.topic
|
|
120
|
+
if (trigger.type?.startsWith("queue/") && trigger.topic) {
|
|
166
121
|
mappings.push({
|
|
167
122
|
urlPath: filePathToUrlPath(filePath),
|
|
168
123
|
topic: trigger.topic,
|
|
169
|
-
consumer:
|
|
124
|
+
consumer: filePathToConsumerGroup(filePath)
|
|
170
125
|
});
|
|
171
126
|
}
|
|
172
127
|
}
|
|
@@ -199,20 +154,16 @@ var DEV_VISIBILITY_MAX_WAIT = 5e3;
|
|
|
199
154
|
var DEV_VISIBILITY_BACKOFF_MULTIPLIER = 2;
|
|
200
155
|
async function waitForMessageVisibility(topicName, consumerGroup, messageId) {
|
|
201
156
|
const client = new QueueClient();
|
|
202
|
-
const transport = new JsonTransport();
|
|
203
157
|
let elapsed = 0;
|
|
204
158
|
let interval = DEV_VISIBILITY_POLL_INTERVAL;
|
|
205
159
|
while (elapsed < DEV_VISIBILITY_MAX_WAIT) {
|
|
206
160
|
try {
|
|
207
|
-
await client.receiveMessageById(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
},
|
|
214
|
-
transport
|
|
215
|
-
);
|
|
161
|
+
await client.receiveMessageById({
|
|
162
|
+
queueName: topicName,
|
|
163
|
+
consumerGroup,
|
|
164
|
+
messageId,
|
|
165
|
+
visibilityTimeoutSeconds: 0
|
|
166
|
+
});
|
|
216
167
|
return true;
|
|
217
168
|
} catch (error) {
|
|
218
169
|
if (error instanceof MessageNotFoundError) {
|
|
@@ -286,26 +237,15 @@ function triggerDevCallbacks(topicName, messageId, delaySeconds) {
|
|
|
286
237
|
console.log(
|
|
287
238
|
`[Dev Mode] Invoking handler: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`
|
|
288
239
|
);
|
|
289
|
-
const cloudEvent = {
|
|
290
|
-
type: "com.vercel.queue.v1beta",
|
|
291
|
-
source: `/topic/${topicName}/consumer/${route.consumer}`,
|
|
292
|
-
id: messageId,
|
|
293
|
-
datacontenttype: "application/json",
|
|
294
|
-
data: {
|
|
295
|
-
messageId,
|
|
296
|
-
queueName: topicName,
|
|
297
|
-
consumerGroup: route.consumer
|
|
298
|
-
},
|
|
299
|
-
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
300
|
-
specversion: "1.0"
|
|
301
|
-
};
|
|
302
240
|
try {
|
|
303
241
|
const response = await fetch(url, {
|
|
304
242
|
method: "POST",
|
|
305
243
|
headers: {
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
|
|
244
|
+
"ce-type": CLOUD_EVENT_TYPE_V2BETA,
|
|
245
|
+
"ce-vqsqueuename": topicName,
|
|
246
|
+
"ce-vqsconsumergroup": route.consumer,
|
|
247
|
+
"ce-vqsmessageid": messageId
|
|
248
|
+
}
|
|
309
249
|
});
|
|
310
250
|
if (response.ok) {
|
|
311
251
|
try {
|
|
@@ -352,6 +292,46 @@ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
|
352
292
|
// src/oidc.ts
|
|
353
293
|
import { getVercelOidcToken } from "@vercel/oidc";
|
|
354
294
|
|
|
295
|
+
// src/transports.ts
|
|
296
|
+
async function streamToBuffer(stream) {
|
|
297
|
+
let totalLength = 0;
|
|
298
|
+
const reader = stream.getReader();
|
|
299
|
+
const chunks = [];
|
|
300
|
+
try {
|
|
301
|
+
while (true) {
|
|
302
|
+
const { done, value } = await reader.read();
|
|
303
|
+
if (done) break;
|
|
304
|
+
chunks.push(value);
|
|
305
|
+
totalLength += value.length;
|
|
306
|
+
}
|
|
307
|
+
} finally {
|
|
308
|
+
reader.releaseLock();
|
|
309
|
+
}
|
|
310
|
+
return Buffer.concat(chunks, totalLength);
|
|
311
|
+
}
|
|
312
|
+
var JsonTransport = class {
|
|
313
|
+
contentType = "application/json";
|
|
314
|
+
replacer;
|
|
315
|
+
reviver;
|
|
316
|
+
/**
|
|
317
|
+
* Create a new JsonTransport.
|
|
318
|
+
* @param options - Optional JSON serialization options
|
|
319
|
+
* @param options.replacer - Custom replacer for JSON.stringify
|
|
320
|
+
* @param options.reviver - Custom reviver for JSON.parse
|
|
321
|
+
*/
|
|
322
|
+
constructor(options = {}) {
|
|
323
|
+
this.replacer = options.replacer;
|
|
324
|
+
this.reviver = options.reviver;
|
|
325
|
+
}
|
|
326
|
+
serialize(value) {
|
|
327
|
+
return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
|
|
328
|
+
}
|
|
329
|
+
async deserialize(stream) {
|
|
330
|
+
const buffer = await streamToBuffer(stream);
|
|
331
|
+
return JSON.parse(buffer.toString("utf8"), this.reviver);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
355
335
|
// src/client.ts
|
|
356
336
|
function isDebugEnabled() {
|
|
357
337
|
return process.env.VERCEL_QUEUE_DEBUG === "1" || process.env.VERCEL_QUEUE_DEBUG === "true";
|
|
@@ -412,6 +392,7 @@ var QueueClient = class {
|
|
|
412
392
|
providedToken;
|
|
413
393
|
defaultDeploymentId;
|
|
414
394
|
pinToDeployment;
|
|
395
|
+
transport;
|
|
415
396
|
constructor(options = {}) {
|
|
416
397
|
this.baseUrl = options.baseUrl || process.env.VERCEL_QUEUE_BASE_URL || "https://vercel-queue.com";
|
|
417
398
|
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v3/topic";
|
|
@@ -419,6 +400,10 @@ var QueueClient = class {
|
|
|
419
400
|
this.providedToken = options.token;
|
|
420
401
|
this.defaultDeploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
|
|
421
402
|
this.pinToDeployment = options.pinToDeployment ?? true;
|
|
403
|
+
this.transport = options.transport || new JsonTransport();
|
|
404
|
+
}
|
|
405
|
+
getTransport() {
|
|
406
|
+
return this.transport;
|
|
422
407
|
}
|
|
423
408
|
getSendDeploymentId() {
|
|
424
409
|
if (isDevMode()) {
|
|
@@ -475,6 +460,8 @@ var QueueClient = class {
|
|
|
475
460
|
}
|
|
476
461
|
console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
|
|
477
462
|
}
|
|
463
|
+
init.headers.set("User-Agent", `@vercel/queue/${"0.0.0-alpha.38"}`);
|
|
464
|
+
init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
|
|
478
465
|
const response = await fetch(url, init);
|
|
479
466
|
if (isDebugEnabled()) {
|
|
480
467
|
const logData = {
|
|
@@ -497,7 +484,6 @@ var QueueClient = class {
|
|
|
497
484
|
* @param options.idempotencyKey - Optional deduplication key (dedup window: min(retention, 24h))
|
|
498
485
|
* @param options.retentionSeconds - Message TTL (default: 86400, min: 60, max: 86400)
|
|
499
486
|
* @param options.delaySeconds - Delivery delay (default: 0, max: retentionSeconds)
|
|
500
|
-
* @param transport - Serializer for the payload
|
|
501
487
|
* @returns Promise with the generated messageId
|
|
502
488
|
* @throws {DuplicateMessageError} When idempotency key was already used
|
|
503
489
|
* @throws {ConsumerDiscoveryError} When consumer discovery fails
|
|
@@ -507,7 +493,8 @@ var QueueClient = class {
|
|
|
507
493
|
* @throws {ForbiddenError} When access is denied
|
|
508
494
|
* @throws {InternalServerError} When server encounters an error
|
|
509
495
|
*/
|
|
510
|
-
async sendMessage(options
|
|
496
|
+
async sendMessage(options) {
|
|
497
|
+
const transport = this.transport;
|
|
511
498
|
const {
|
|
512
499
|
queueName,
|
|
513
500
|
payload,
|
|
@@ -589,21 +576,24 @@ var QueueClient = class {
|
|
|
589
576
|
/**
|
|
590
577
|
* Receive messages from a topic as an async generator.
|
|
591
578
|
*
|
|
579
|
+
* When the queue is empty, the generator completes without yielding any
|
|
580
|
+
* messages. Callers should handle the case where no messages are yielded.
|
|
581
|
+
*
|
|
592
582
|
* @param options - Receive options
|
|
593
583
|
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
594
584
|
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
595
585
|
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
596
586
|
* @param options.limit - Max messages to retrieve (default: 1, min: 1, max: 10)
|
|
597
|
-
* @param transport - Deserializer for message payloads
|
|
598
587
|
* @yields Message objects with payload, messageId, receiptHandle, etc.
|
|
599
|
-
*
|
|
588
|
+
* Yields nothing if queue is empty.
|
|
600
589
|
* @throws {InvalidLimitError} When limit is outside 1-10 range
|
|
601
590
|
* @throws {BadRequestError} When parameters are invalid
|
|
602
591
|
* @throws {UnauthorizedError} When authentication fails
|
|
603
592
|
* @throws {ForbiddenError} When access is denied
|
|
604
593
|
* @throws {InternalServerError} When server encounters an error
|
|
605
594
|
*/
|
|
606
|
-
async *receiveMessages(options
|
|
595
|
+
async *receiveMessages(options) {
|
|
596
|
+
const transport = this.transport;
|
|
607
597
|
const { queueName, consumerGroup, visibilityTimeoutSeconds, limit } = options;
|
|
608
598
|
if (limit !== void 0 && (limit < 1 || limit > 10)) {
|
|
609
599
|
throw new InvalidLimitError(limit);
|
|
@@ -634,7 +624,7 @@ var QueueClient = class {
|
|
|
634
624
|
}
|
|
635
625
|
);
|
|
636
626
|
if (response.status === 204) {
|
|
637
|
-
|
|
627
|
+
return;
|
|
638
628
|
}
|
|
639
629
|
if (!response.ok) {
|
|
640
630
|
const errorText = await response.text();
|
|
@@ -675,7 +665,6 @@ var QueueClient = class {
|
|
|
675
665
|
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
676
666
|
* @param options.messageId - Message ID to retrieve
|
|
677
667
|
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
678
|
-
* @param transport - Deserializer for the message payload
|
|
679
668
|
* @returns Promise with the message
|
|
680
669
|
* @throws {MessageNotFoundError} When message doesn't exist
|
|
681
670
|
* @throws {MessageNotAvailableError} When message is in wrong state or was a duplicate
|
|
@@ -685,7 +674,8 @@ var QueueClient = class {
|
|
|
685
674
|
* @throws {ForbiddenError} When access is denied
|
|
686
675
|
* @throws {InternalServerError} When server encounters an error
|
|
687
676
|
*/
|
|
688
|
-
async receiveMessageById(options
|
|
677
|
+
async receiveMessageById(options) {
|
|
678
|
+
const transport = this.transport;
|
|
689
679
|
const { queueName, consumerGroup, messageId, visibilityTimeoutSeconds } = options;
|
|
690
680
|
const headers = new Headers({
|
|
691
681
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
@@ -948,40 +938,90 @@ var QueueClient = class {
|
|
|
948
938
|
};
|
|
949
939
|
|
|
950
940
|
// src/consumer-group.ts
|
|
941
|
+
var DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 300;
|
|
942
|
+
var MIN_VISIBILITY_TIMEOUT_SECONDS = 30;
|
|
943
|
+
var MAX_RENEWAL_INTERVAL_SECONDS = 60;
|
|
944
|
+
var MIN_RENEWAL_INTERVAL_SECONDS = 10;
|
|
945
|
+
var RETRY_INTERVAL_MS = 3e3;
|
|
946
|
+
function calculateRenewalInterval(visibilityTimeoutSeconds) {
|
|
947
|
+
return Math.min(
|
|
948
|
+
MAX_RENEWAL_INTERVAL_SECONDS,
|
|
949
|
+
Math.max(MIN_RENEWAL_INTERVAL_SECONDS, visibilityTimeoutSeconds / 5)
|
|
950
|
+
);
|
|
951
|
+
}
|
|
951
952
|
var ConsumerGroup = class {
|
|
952
953
|
client;
|
|
953
954
|
topicName;
|
|
954
955
|
consumerGroupName;
|
|
955
956
|
visibilityTimeout;
|
|
956
|
-
refreshInterval;
|
|
957
|
-
transport;
|
|
958
957
|
/**
|
|
959
958
|
* Create a new ConsumerGroup instance.
|
|
960
959
|
*
|
|
961
|
-
* @param client - QueueClient instance to use for API calls
|
|
960
|
+
* @param client - QueueClient instance to use for API calls (transport is configured on the client)
|
|
962
961
|
* @param topicName - Name of the topic to consume from (pattern: `[A-Za-z0-9_-]+`)
|
|
963
962
|
* @param consumerGroupName - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
964
963
|
* @param options - Optional configuration
|
|
965
|
-
* @param options.
|
|
966
|
-
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
|
|
967
|
-
* @param options.visibilityRefreshInterval - Lock refresh interval in seconds (default: visibilityTimeout / 3)
|
|
964
|
+
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
|
|
968
965
|
*/
|
|
969
966
|
constructor(client, topicName, consumerGroupName, options = {}) {
|
|
970
967
|
this.client = client;
|
|
971
968
|
this.topicName = topicName;
|
|
972
969
|
this.consumerGroupName = consumerGroupName;
|
|
973
|
-
this.visibilityTimeout =
|
|
974
|
-
|
|
975
|
-
|
|
970
|
+
this.visibilityTimeout = Math.max(
|
|
971
|
+
MIN_VISIBILITY_TIMEOUT_SECONDS,
|
|
972
|
+
options.visibilityTimeoutSeconds ?? DEFAULT_VISIBILITY_TIMEOUT_SECONDS
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Check if an error is a 4xx client error that should stop retries.
|
|
977
|
+
* 4xx errors indicate the request is fundamentally invalid and retrying won't help.
|
|
978
|
+
* - 409: Ticket mismatch (lost ownership to another consumer)
|
|
979
|
+
* - 404: Message/receipt handle not found
|
|
980
|
+
* - 400, 401, 403: Other client errors
|
|
981
|
+
*/
|
|
982
|
+
isClientError(error) {
|
|
983
|
+
return error instanceof MessageNotAvailableError || // 409 - ticket mismatch, lost ownership
|
|
984
|
+
error instanceof MessageNotFoundError || // 404 - receipt handle not found
|
|
985
|
+
error instanceof BadRequestError || // 400 - invalid parameters
|
|
986
|
+
error instanceof UnauthorizedError || // 401 - auth failed
|
|
987
|
+
error instanceof ForbiddenError;
|
|
976
988
|
}
|
|
977
989
|
/**
|
|
978
990
|
* Starts a background loop that periodically extends the visibility timeout for a message.
|
|
991
|
+
*
|
|
992
|
+
* Timing strategy:
|
|
993
|
+
* - Renewal interval: min(60s, max(10s, visibilityTimeout/5))
|
|
994
|
+
* - Extensions request the same duration as the initial visibility timeout
|
|
995
|
+
* - When `visibilityDeadline` is provided (binary mode small body), the first
|
|
996
|
+
* extension delay is calculated from the time remaining until the deadline
|
|
997
|
+
* using the same renewal formula, ensuring the first extension fires before
|
|
998
|
+
* the server-assigned lease expires. Subsequent renewals use the standard interval.
|
|
999
|
+
*
|
|
1000
|
+
* Retry strategy:
|
|
1001
|
+
* - On transient failures (5xx, network errors): retry every 3 seconds
|
|
1002
|
+
* - On 4xx client errors: stop retrying (the lease is lost or invalid)
|
|
1003
|
+
*
|
|
1004
|
+
* @param receiptHandle - The receipt handle to extend visibility for
|
|
1005
|
+
* @param options - Optional configuration
|
|
1006
|
+
* @param options.visibilityDeadline - Absolute deadline (from server's `ce-vqsvisibilitydeadline`)
|
|
1007
|
+
* when the current visibility timeout expires. Used to calculate the first extension delay.
|
|
979
1008
|
*/
|
|
980
|
-
startVisibilityExtension(receiptHandle) {
|
|
1009
|
+
startVisibilityExtension(receiptHandle, options) {
|
|
981
1010
|
let isRunning = true;
|
|
982
1011
|
let isResolved = false;
|
|
983
1012
|
let resolveLifecycle;
|
|
984
1013
|
let timeoutId = null;
|
|
1014
|
+
const renewalIntervalMs = calculateRenewalInterval(this.visibilityTimeout) * 1e3;
|
|
1015
|
+
let firstDelayMs = renewalIntervalMs;
|
|
1016
|
+
if (options?.visibilityDeadline) {
|
|
1017
|
+
const timeRemainingMs = options.visibilityDeadline.getTime() - Date.now();
|
|
1018
|
+
if (timeRemainingMs > 0) {
|
|
1019
|
+
const timeRemainingSeconds = timeRemainingMs / 1e3;
|
|
1020
|
+
firstDelayMs = calculateRenewalInterval(timeRemainingSeconds) * 1e3;
|
|
1021
|
+
} else {
|
|
1022
|
+
firstDelayMs = 0;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
985
1025
|
const lifecyclePromise = new Promise((resolve) => {
|
|
986
1026
|
resolveLifecycle = resolve;
|
|
987
1027
|
});
|
|
@@ -1004,19 +1044,31 @@ var ConsumerGroup = class {
|
|
|
1004
1044
|
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1005
1045
|
});
|
|
1006
1046
|
if (isRunning) {
|
|
1007
|
-
timeoutId = setTimeout(() => extend(),
|
|
1047
|
+
timeoutId = setTimeout(() => extend(), renewalIntervalMs);
|
|
1008
1048
|
} else {
|
|
1009
1049
|
safeResolve();
|
|
1010
1050
|
}
|
|
1011
1051
|
} catch (error) {
|
|
1052
|
+
if (this.isClientError(error)) {
|
|
1053
|
+
console.error(
|
|
1054
|
+
`Visibility extension failed with client error for receipt handle ${receiptHandle} (stopping retries):`,
|
|
1055
|
+
error
|
|
1056
|
+
);
|
|
1057
|
+
safeResolve();
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1012
1060
|
console.error(
|
|
1013
|
-
`Failed to extend visibility for receipt handle ${receiptHandle}:`,
|
|
1061
|
+
`Failed to extend visibility for receipt handle ${receiptHandle} (will retry in ${RETRY_INTERVAL_MS / 1e3}s):`,
|
|
1014
1062
|
error
|
|
1015
1063
|
);
|
|
1016
|
-
|
|
1064
|
+
if (isRunning) {
|
|
1065
|
+
timeoutId = setTimeout(() => extend(), RETRY_INTERVAL_MS);
|
|
1066
|
+
} else {
|
|
1067
|
+
safeResolve();
|
|
1068
|
+
}
|
|
1017
1069
|
}
|
|
1018
1070
|
};
|
|
1019
|
-
timeoutId = setTimeout(() => extend(),
|
|
1071
|
+
timeoutId = setTimeout(() => extend(), firstDelayMs);
|
|
1020
1072
|
return async (waitForCompletion = false) => {
|
|
1021
1073
|
isRunning = false;
|
|
1022
1074
|
if (timeoutId) {
|
|
@@ -1030,8 +1082,11 @@ var ConsumerGroup = class {
|
|
|
1030
1082
|
}
|
|
1031
1083
|
};
|
|
1032
1084
|
}
|
|
1033
|
-
async processMessage(message, handler) {
|
|
1034
|
-
const stopExtension = this.startVisibilityExtension(
|
|
1085
|
+
async processMessage(message, handler, options) {
|
|
1086
|
+
const stopExtension = this.startVisibilityExtension(
|
|
1087
|
+
message.receiptHandle,
|
|
1088
|
+
options
|
|
1089
|
+
);
|
|
1035
1090
|
try {
|
|
1036
1091
|
await handler(message.payload, {
|
|
1037
1092
|
messageId: message.messageId,
|
|
@@ -1048,9 +1103,10 @@ var ConsumerGroup = class {
|
|
|
1048
1103
|
});
|
|
1049
1104
|
} catch (error) {
|
|
1050
1105
|
await stopExtension();
|
|
1051
|
-
|
|
1106
|
+
const transport = this.client.getTransport();
|
|
1107
|
+
if (transport.finalize && message.payload !== void 0 && message.payload !== null) {
|
|
1052
1108
|
try {
|
|
1053
|
-
await
|
|
1109
|
+
await transport.finalize(message.payload);
|
|
1054
1110
|
} catch (finalizeError) {
|
|
1055
1111
|
console.warn("Failed to finalize message payload:", finalizeError);
|
|
1056
1112
|
}
|
|
@@ -1058,35 +1114,49 @@ var ConsumerGroup = class {
|
|
|
1058
1114
|
throw error;
|
|
1059
1115
|
}
|
|
1060
1116
|
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Process a pre-fetched message directly, without calling `receiveMessageById`.
|
|
1119
|
+
*
|
|
1120
|
+
* Used by the binary mode (v2beta) small body fast path, where the server
|
|
1121
|
+
* pushes the full message payload in the callback request. The message is
|
|
1122
|
+
* processed with the same lifecycle guarantees as `consume()`:
|
|
1123
|
+
* - Visibility timeout is extended periodically during processing
|
|
1124
|
+
* - Message is deleted on successful handler completion
|
|
1125
|
+
* - Payload is finalized on error if the transport supports it
|
|
1126
|
+
*
|
|
1127
|
+
* @param handler - Function to process the message payload and metadata
|
|
1128
|
+
* @param message - The complete message including payload and receipt handle
|
|
1129
|
+
* @param options - Optional configuration
|
|
1130
|
+
* @param options.visibilityDeadline - Absolute deadline when the server-assigned
|
|
1131
|
+
* visibility timeout expires (from `ce-vqsvisibilitydeadline`). Used to
|
|
1132
|
+
* schedule the first visibility extension before the lease expires.
|
|
1133
|
+
*/
|
|
1134
|
+
async consumeMessage(handler, message, options) {
|
|
1135
|
+
await this.processMessage(message, handler, options);
|
|
1136
|
+
}
|
|
1061
1137
|
async consume(handler, options) {
|
|
1062
|
-
if (options
|
|
1063
|
-
const response = await this.client.receiveMessageById(
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
},
|
|
1070
|
-
this.transport
|
|
1071
|
-
);
|
|
1138
|
+
if (options && "messageId" in options) {
|
|
1139
|
+
const response = await this.client.receiveMessageById({
|
|
1140
|
+
queueName: this.topicName,
|
|
1141
|
+
consumerGroup: this.consumerGroupName,
|
|
1142
|
+
messageId: options.messageId,
|
|
1143
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1144
|
+
});
|
|
1072
1145
|
await this.processMessage(response.message, handler);
|
|
1073
1146
|
} else {
|
|
1147
|
+
const limit = options && "limit" in options ? options.limit : 1;
|
|
1074
1148
|
let messageFound = false;
|
|
1075
|
-
for await (const message of this.client.receiveMessages(
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
},
|
|
1082
|
-
this.transport
|
|
1083
|
-
)) {
|
|
1149
|
+
for await (const message of this.client.receiveMessages({
|
|
1150
|
+
queueName: this.topicName,
|
|
1151
|
+
consumerGroup: this.consumerGroupName,
|
|
1152
|
+
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1153
|
+
limit
|
|
1154
|
+
})) {
|
|
1084
1155
|
messageFound = true;
|
|
1085
1156
|
await this.processMessage(message, handler);
|
|
1086
|
-
break;
|
|
1087
1157
|
}
|
|
1088
1158
|
if (!messageFound) {
|
|
1089
|
-
|
|
1159
|
+
await handler(null, null);
|
|
1090
1160
|
}
|
|
1091
1161
|
}
|
|
1092
1162
|
}
|
|
@@ -1108,17 +1178,14 @@ var ConsumerGroup = class {
|
|
|
1108
1178
|
var Topic = class {
|
|
1109
1179
|
client;
|
|
1110
1180
|
topicName;
|
|
1111
|
-
transport;
|
|
1112
1181
|
/**
|
|
1113
1182
|
* Create a new Topic instance
|
|
1114
|
-
* @param client QueueClient instance to use for API calls
|
|
1183
|
+
* @param client QueueClient instance to use for API calls (transport is configured on the client)
|
|
1115
1184
|
* @param topicName Name of the topic to work with
|
|
1116
|
-
* @param transport Optional serializer/deserializer for the payload (defaults to JSON)
|
|
1117
1185
|
*/
|
|
1118
|
-
constructor(client, topicName
|
|
1186
|
+
constructor(client, topicName) {
|
|
1119
1187
|
this.client = client;
|
|
1120
1188
|
this.topicName = topicName;
|
|
1121
|
-
this.transport = transport || new JsonTransport();
|
|
1122
1189
|
}
|
|
1123
1190
|
/**
|
|
1124
1191
|
* Publish a message to the topic
|
|
@@ -1131,17 +1198,14 @@ var Topic = class {
|
|
|
1131
1198
|
* @throws {InternalServerError} When server encounters an error
|
|
1132
1199
|
*/
|
|
1133
1200
|
async publish(payload, options) {
|
|
1134
|
-
const result = await this.client.sendMessage(
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
},
|
|
1143
|
-
this.transport
|
|
1144
|
-
);
|
|
1201
|
+
const result = await this.client.sendMessage({
|
|
1202
|
+
queueName: this.topicName,
|
|
1203
|
+
payload,
|
|
1204
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1205
|
+
retentionSeconds: options?.retentionSeconds,
|
|
1206
|
+
delaySeconds: options?.delaySeconds,
|
|
1207
|
+
headers: options?.headers
|
|
1208
|
+
});
|
|
1145
1209
|
if (isDevMode()) {
|
|
1146
1210
|
triggerDevCallbacks(this.topicName, result.messageId);
|
|
1147
1211
|
}
|
|
@@ -1154,15 +1218,11 @@ var Topic = class {
|
|
|
1154
1218
|
* @returns A ConsumerGroup instance
|
|
1155
1219
|
*/
|
|
1156
1220
|
consumerGroup(consumerGroupName, options) {
|
|
1157
|
-
const consumerOptions = {
|
|
1158
|
-
...options,
|
|
1159
|
-
transport: options?.transport || this.transport
|
|
1160
|
-
};
|
|
1161
1221
|
return new ConsumerGroup(
|
|
1162
1222
|
this.client,
|
|
1163
1223
|
this.topicName,
|
|
1164
1224
|
consumerGroupName,
|
|
1165
|
-
|
|
1225
|
+
options
|
|
1166
1226
|
);
|
|
1167
1227
|
}
|
|
1168
1228
|
/**
|
|
@@ -1171,220 +1231,167 @@ var Topic = class {
|
|
|
1171
1231
|
get name() {
|
|
1172
1232
|
return this.topicName;
|
|
1173
1233
|
}
|
|
1174
|
-
/**
|
|
1175
|
-
* Get the transport used by this topic
|
|
1176
|
-
*/
|
|
1177
|
-
get serializer() {
|
|
1178
|
-
return this.transport;
|
|
1179
|
-
}
|
|
1180
1234
|
};
|
|
1181
1235
|
|
|
1182
1236
|
// src/callback.ts
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const lastIndex = pattern.lastIndexOf("*");
|
|
1186
|
-
if (firstIndex !== lastIndex) {
|
|
1187
|
-
return false;
|
|
1188
|
-
}
|
|
1189
|
-
if (firstIndex === -1) {
|
|
1190
|
-
return false;
|
|
1191
|
-
}
|
|
1192
|
-
if (firstIndex !== pattern.length - 1) {
|
|
1193
|
-
return false;
|
|
1194
|
-
}
|
|
1195
|
-
return true;
|
|
1196
|
-
}
|
|
1237
|
+
var CLOUD_EVENT_TYPE_V1BETA = "com.vercel.queue.v1beta";
|
|
1238
|
+
var CLOUD_EVENT_TYPE_V2BETA = "com.vercel.queue.v2beta";
|
|
1197
1239
|
function matchesWildcardPattern(topicName, pattern) {
|
|
1198
1240
|
const prefix = pattern.slice(0, -1);
|
|
1199
1241
|
return topicName.startsWith(prefix);
|
|
1200
1242
|
}
|
|
1201
|
-
function
|
|
1202
|
-
|
|
1203
|
-
if (exactHandler) {
|
|
1204
|
-
return exactHandler;
|
|
1205
|
-
}
|
|
1206
|
-
for (const pattern in handlers) {
|
|
1207
|
-
if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
|
|
1208
|
-
return handlers[pattern];
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
return null;
|
|
1243
|
+
function isRecord(value) {
|
|
1244
|
+
return typeof value === "object" && value !== null;
|
|
1212
1245
|
}
|
|
1213
|
-
|
|
1214
|
-
const contentType = request.headers.get("content-type");
|
|
1246
|
+
function parseV1StructuredBody(body, contentType) {
|
|
1215
1247
|
if (!contentType || !contentType.includes("application/cloudevents+json")) {
|
|
1216
1248
|
throw new Error(
|
|
1217
1249
|
"Invalid content type: expected 'application/cloudevents+json'"
|
|
1218
1250
|
);
|
|
1219
1251
|
}
|
|
1220
|
-
|
|
1221
|
-
try {
|
|
1222
|
-
cloudEvent = await request.json();
|
|
1223
|
-
} catch (error) {
|
|
1224
|
-
throw new Error("Failed to parse CloudEvent from request body");
|
|
1225
|
-
}
|
|
1226
|
-
if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
|
|
1252
|
+
if (!isRecord(body) || !body.type || !body.source || !body.id || !isRecord(body.data)) {
|
|
1227
1253
|
throw new Error("Invalid CloudEvent: missing required fields");
|
|
1228
1254
|
}
|
|
1229
|
-
if (
|
|
1255
|
+
if (body.type !== CLOUD_EVENT_TYPE_V1BETA) {
|
|
1230
1256
|
throw new Error(
|
|
1231
|
-
`Invalid CloudEvent type: expected '
|
|
1257
|
+
`Invalid CloudEvent type: expected '${CLOUD_EVENT_TYPE_V1BETA}', got '${String(body.type)}'`
|
|
1232
1258
|
);
|
|
1233
1259
|
}
|
|
1260
|
+
const { data } = body;
|
|
1234
1261
|
const missingFields = [];
|
|
1235
|
-
if (!("queueName" in
|
|
1236
|
-
if (!("consumerGroup" in
|
|
1237
|
-
|
|
1238
|
-
if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
|
|
1262
|
+
if (!("queueName" in data)) missingFields.push("queueName");
|
|
1263
|
+
if (!("consumerGroup" in data)) missingFields.push("consumerGroup");
|
|
1264
|
+
if (!("messageId" in data)) missingFields.push("messageId");
|
|
1239
1265
|
if (missingFields.length > 0) {
|
|
1240
1266
|
throw new Error(
|
|
1241
1267
|
`Missing required CloudEvent data fields: ${missingFields.join(", ")}`
|
|
1242
1268
|
);
|
|
1243
1269
|
}
|
|
1244
|
-
const { messageId, queueName, consumerGroup } = cloudEvent.data;
|
|
1245
1270
|
return {
|
|
1246
|
-
queueName,
|
|
1247
|
-
consumerGroup,
|
|
1248
|
-
messageId
|
|
1271
|
+
queueName: String(data.queueName),
|
|
1272
|
+
consumerGroup: String(data.consumerGroup),
|
|
1273
|
+
messageId: String(data.messageId)
|
|
1249
1274
|
};
|
|
1250
1275
|
}
|
|
1251
|
-
function createCallbackHandler(handlers, client, visibilityTimeoutSeconds) {
|
|
1252
|
-
for (const topicPattern in handlers) {
|
|
1253
|
-
if (topicPattern.includes("*")) {
|
|
1254
|
-
if (!validateWildcardPattern(topicPattern)) {
|
|
1255
|
-
throw new Error(
|
|
1256
|
-
`Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
|
|
1257
|
-
);
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
const routeHandler = async (request) => {
|
|
1262
|
-
try {
|
|
1263
|
-
const { queueName, consumerGroup, messageId } = await parseCallback(request);
|
|
1264
|
-
const topicHandler = findTopicHandler(queueName, handlers);
|
|
1265
|
-
if (!topicHandler) {
|
|
1266
|
-
const availableTopics = Object.keys(handlers).join(", ");
|
|
1267
|
-
return Response.json(
|
|
1268
|
-
{
|
|
1269
|
-
error: `No handler found for topic: ${queueName}`,
|
|
1270
|
-
availableTopics
|
|
1271
|
-
},
|
|
1272
|
-
{ status: 404 }
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
1276
|
-
if (!consumerGroupHandler) {
|
|
1277
|
-
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
1278
|
-
return Response.json(
|
|
1279
|
-
{
|
|
1280
|
-
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
1281
|
-
availableGroups
|
|
1282
|
-
},
|
|
1283
|
-
{ status: 404 }
|
|
1284
|
-
);
|
|
1285
|
-
}
|
|
1286
|
-
const topic = new Topic(client, queueName);
|
|
1287
|
-
const cg = topic.consumerGroup(
|
|
1288
|
-
consumerGroup,
|
|
1289
|
-
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : void 0
|
|
1290
|
-
);
|
|
1291
|
-
await cg.consume(consumerGroupHandler, { messageId });
|
|
1292
|
-
return Response.json({ status: "success" });
|
|
1293
|
-
} catch (error) {
|
|
1294
|
-
console.error("Queue callback error:", error);
|
|
1295
|
-
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"))) {
|
|
1296
|
-
return Response.json({ error: error.message }, { status: 400 });
|
|
1297
|
-
}
|
|
1298
|
-
return Response.json(
|
|
1299
|
-
{ error: "Failed to process queue message" },
|
|
1300
|
-
{ status: 500 }
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
return routeHandler;
|
|
1305
|
-
}
|
|
1306
|
-
function handleCallback(handlers, options) {
|
|
1307
|
-
return createCallbackHandler(
|
|
1308
|
-
handlers,
|
|
1309
|
-
options?.client || new QueueClient(),
|
|
1310
|
-
options?.visibilityTimeoutSeconds
|
|
1311
|
-
);
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
// src/nextjs-pages.ts
|
|
1315
1276
|
function getHeader(headers, name) {
|
|
1277
|
+
if (headers instanceof Headers) {
|
|
1278
|
+
return headers.get(name);
|
|
1279
|
+
}
|
|
1316
1280
|
const value = headers[name];
|
|
1317
|
-
|
|
1281
|
+
if (Array.isArray(value)) return value[0] ?? null;
|
|
1282
|
+
return value ?? null;
|
|
1318
1283
|
}
|
|
1319
|
-
function
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1284
|
+
function parseBinaryHeaders(headers) {
|
|
1285
|
+
const ceType = getHeader(headers, "ce-type");
|
|
1286
|
+
if (ceType !== CLOUD_EVENT_TYPE_V2BETA) {
|
|
1287
|
+
throw new Error(
|
|
1288
|
+
`Invalid CloudEvent type: expected '${CLOUD_EVENT_TYPE_V2BETA}', got '${ceType}'`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
const queueName = getHeader(headers, "ce-vqsqueuename");
|
|
1292
|
+
const consumerGroup = getHeader(headers, "ce-vqsconsumergroup");
|
|
1293
|
+
const messageId = getHeader(headers, "ce-vqsmessageid");
|
|
1294
|
+
const missingFields = [];
|
|
1295
|
+
if (!queueName) missingFields.push("ce-vqsqueuename");
|
|
1296
|
+
if (!consumerGroup) missingFields.push("ce-vqsconsumergroup");
|
|
1297
|
+
if (!messageId) missingFields.push("ce-vqsmessageid");
|
|
1298
|
+
if (missingFields.length > 0) {
|
|
1299
|
+
throw new Error(
|
|
1300
|
+
`Missing required CloudEvent headers: ${missingFields.join(", ")}`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
const base = {
|
|
1304
|
+
queueName,
|
|
1305
|
+
consumerGroup,
|
|
1306
|
+
messageId
|
|
1307
|
+
};
|
|
1308
|
+
const receiptHandle = getHeader(headers, "ce-vqsreceipthandle");
|
|
1309
|
+
if (!receiptHandle) {
|
|
1310
|
+
return base;
|
|
1330
1311
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1312
|
+
const result = { ...base, receiptHandle };
|
|
1313
|
+
const deliveryCount = getHeader(headers, "ce-vqsdeliverycount");
|
|
1314
|
+
if (deliveryCount) {
|
|
1315
|
+
result.deliveryCount = parseInt(deliveryCount, 10);
|
|
1333
1316
|
}
|
|
1334
|
-
|
|
1317
|
+
const createdAt = getHeader(headers, "ce-vqscreatedat");
|
|
1318
|
+
if (createdAt) {
|
|
1319
|
+
result.createdAt = createdAt;
|
|
1320
|
+
}
|
|
1321
|
+
const contentType = getHeader(headers, "content-type");
|
|
1322
|
+
if (contentType) {
|
|
1323
|
+
result.contentType = contentType;
|
|
1324
|
+
}
|
|
1325
|
+
const visibilityDeadline = getHeader(headers, "ce-vqsvisibilitydeadline");
|
|
1326
|
+
if (visibilityDeadline) {
|
|
1327
|
+
result.visibilityDeadline = visibilityDeadline;
|
|
1328
|
+
}
|
|
1329
|
+
return result;
|
|
1335
1330
|
}
|
|
1336
|
-
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
const url = `${protocol}://${host}${req.url}`;
|
|
1343
|
-
const headers = new Headers();
|
|
1344
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
1345
|
-
if (value) {
|
|
1346
|
-
if (Array.isArray(value)) {
|
|
1347
|
-
value.forEach((v) => headers.append(key, v));
|
|
1348
|
-
} else {
|
|
1349
|
-
headers.set(key, value);
|
|
1350
|
-
}
|
|
1331
|
+
function parseRawCallback(body, headers) {
|
|
1332
|
+
const ceType = getHeader(headers, "ce-type");
|
|
1333
|
+
if (ceType === CLOUD_EVENT_TYPE_V2BETA) {
|
|
1334
|
+
const result = parseBinaryHeaders(headers);
|
|
1335
|
+
if ("receiptHandle" in result) {
|
|
1336
|
+
result.parsedPayload = body;
|
|
1351
1337
|
}
|
|
1338
|
+
return result;
|
|
1352
1339
|
}
|
|
1353
|
-
|
|
1354
|
-
return new Request(url, {
|
|
1355
|
-
method: req.method || "POST",
|
|
1356
|
-
headers,
|
|
1357
|
-
body
|
|
1358
|
-
});
|
|
1340
|
+
return parseV1StructuredBody(body, getHeader(headers, "content-type"));
|
|
1359
1341
|
}
|
|
1360
|
-
async function
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1342
|
+
async function handleCallback(handler, request, options) {
|
|
1343
|
+
const { queueName, consumerGroup, messageId } = request;
|
|
1344
|
+
const client = options?.client || new QueueClient();
|
|
1345
|
+
const topic = new Topic(client, queueName);
|
|
1346
|
+
const cg = topic.consumerGroup(
|
|
1347
|
+
consumerGroup,
|
|
1348
|
+
options?.visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds: options.visibilityTimeoutSeconds } : void 0
|
|
1349
|
+
);
|
|
1350
|
+
if ("receiptHandle" in request) {
|
|
1351
|
+
const transport = client.getTransport();
|
|
1352
|
+
let payload;
|
|
1353
|
+
if (request.rawBody) {
|
|
1354
|
+
payload = await transport.deserialize(request.rawBody);
|
|
1355
|
+
} else if (request.parsedPayload !== void 0) {
|
|
1356
|
+
payload = request.parsedPayload;
|
|
1357
|
+
} else {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
"Binary mode callback with receipt handle is missing payload"
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
const message = {
|
|
1363
|
+
messageId,
|
|
1364
|
+
payload,
|
|
1365
|
+
deliveryCount: request.deliveryCount ?? 1,
|
|
1366
|
+
createdAt: request.createdAt ? new Date(request.createdAt) : /* @__PURE__ */ new Date(),
|
|
1367
|
+
contentType: request.contentType ?? transport.contentType,
|
|
1368
|
+
receiptHandle: request.receiptHandle
|
|
1369
|
+
};
|
|
1370
|
+
const visibilityDeadline = request.visibilityDeadline ? new Date(request.visibilityDeadline) : void 0;
|
|
1371
|
+
await cg.consumeMessage(handler, message, { visibilityDeadline });
|
|
1369
1372
|
} else {
|
|
1370
|
-
|
|
1371
|
-
res.send(text);
|
|
1373
|
+
await cg.consume(handler, { messageId });
|
|
1372
1374
|
}
|
|
1373
1375
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
+
|
|
1377
|
+
// src/nextjs-pages.ts
|
|
1378
|
+
function handleCallback2(handler, options) {
|
|
1376
1379
|
return async (req, res) => {
|
|
1377
1380
|
if (req.method !== "POST") {
|
|
1378
1381
|
res.status(200).end();
|
|
1379
1382
|
return;
|
|
1380
1383
|
}
|
|
1381
1384
|
try {
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
+
const parsed = parseRawCallback(req.body, req.headers);
|
|
1386
|
+
await handleCallback(handler, parsed, options);
|
|
1387
|
+
res.status(200).json({ status: "success" });
|
|
1385
1388
|
} catch (error) {
|
|
1386
|
-
console.error("
|
|
1387
|
-
|
|
1389
|
+
console.error("Queue callback error:", error);
|
|
1390
|
+
if (error instanceof Error && (error.message.includes("Invalid content type") || error.message.includes("Invalid CloudEvent") || error.message.includes("Missing required CloudEvent") || error.message.includes("Failed to parse CloudEvent") || error.message.includes("Binary mode callback"))) {
|
|
1391
|
+
res.status(400).json({ error: error.message });
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
res.status(500).json({ error: "Failed to process queue message" });
|
|
1388
1395
|
}
|
|
1389
1396
|
};
|
|
1390
1397
|
}
|