@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.js
CHANGED
|
@@ -41,46 +41,6 @@ var import_mixpart = require("mixpart");
|
|
|
41
41
|
var fs = __toESM(require("fs"));
|
|
42
42
|
var path = __toESM(require("path"));
|
|
43
43
|
|
|
44
|
-
// src/transports.ts
|
|
45
|
-
async function streamToBuffer(stream) {
|
|
46
|
-
let totalLength = 0;
|
|
47
|
-
const reader = stream.getReader();
|
|
48
|
-
const chunks = [];
|
|
49
|
-
try {
|
|
50
|
-
while (true) {
|
|
51
|
-
const { done, value } = await reader.read();
|
|
52
|
-
if (done) break;
|
|
53
|
-
chunks.push(value);
|
|
54
|
-
totalLength += value.length;
|
|
55
|
-
}
|
|
56
|
-
} finally {
|
|
57
|
-
reader.releaseLock();
|
|
58
|
-
}
|
|
59
|
-
return Buffer.concat(chunks, totalLength);
|
|
60
|
-
}
|
|
61
|
-
var JsonTransport = class {
|
|
62
|
-
contentType = "application/json";
|
|
63
|
-
replacer;
|
|
64
|
-
reviver;
|
|
65
|
-
/**
|
|
66
|
-
* Create a new JsonTransport.
|
|
67
|
-
* @param options - Optional JSON serialization options
|
|
68
|
-
* @param options.replacer - Custom replacer for JSON.stringify
|
|
69
|
-
* @param options.reviver - Custom reviver for JSON.parse
|
|
70
|
-
*/
|
|
71
|
-
constructor(options = {}) {
|
|
72
|
-
this.replacer = options.replacer;
|
|
73
|
-
this.reviver = options.reviver;
|
|
74
|
-
}
|
|
75
|
-
serialize(value) {
|
|
76
|
-
return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
|
|
77
|
-
}
|
|
78
|
-
async deserialize(stream) {
|
|
79
|
-
const buffer = await streamToBuffer(stream);
|
|
80
|
-
return JSON.parse(buffer.toString("utf8"), this.reviver);
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
44
|
// src/types.ts
|
|
85
45
|
var MessageNotFoundError = class extends Error {
|
|
86
46
|
constructor(messageId) {
|
|
@@ -102,14 +62,6 @@ var MessageCorruptedError = class extends Error {
|
|
|
102
62
|
this.name = "MessageCorruptedError";
|
|
103
63
|
}
|
|
104
64
|
};
|
|
105
|
-
var QueueEmptyError = class extends Error {
|
|
106
|
-
constructor(queueName, consumerGroup) {
|
|
107
|
-
super(
|
|
108
|
-
`No messages available in queue "${queueName}" for consumer group "${consumerGroup}"`
|
|
109
|
-
);
|
|
110
|
-
this.name = "QueueEmptyError";
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
65
|
var UnauthorizedError = class extends Error {
|
|
114
66
|
constructor(message = "Missing or invalid authentication token") {
|
|
115
67
|
super(message);
|
|
@@ -172,12 +124,15 @@ var ConsumerRegistryNotConfiguredError = class extends Error {
|
|
|
172
124
|
// src/dev.ts
|
|
173
125
|
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
174
126
|
function filePathToUrlPath(filePath) {
|
|
175
|
-
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|js|tsx|jsx)$/, "").replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
127
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|mts|js|mjs|tsx|jsx)$/, "").replace(/\.(ts|mts|js|mjs|tsx|jsx)$/, "");
|
|
176
128
|
if (!urlPath.startsWith("/")) {
|
|
177
129
|
urlPath = "/" + urlPath;
|
|
178
130
|
}
|
|
179
131
|
return urlPath;
|
|
180
132
|
}
|
|
133
|
+
function filePathToConsumerGroup(filePath) {
|
|
134
|
+
return filePath.replace(/_/g, "__").replace(/\//g, "_S").replace(/\./g, "_D");
|
|
135
|
+
}
|
|
181
136
|
function getDevRouteMappings() {
|
|
182
137
|
const g = globalThis;
|
|
183
138
|
if (ROUTE_MAPPINGS_KEY in g) {
|
|
@@ -198,11 +153,11 @@ function getDevRouteMappings() {
|
|
|
198
153
|
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
199
154
|
if (!config.experimentalTriggers) continue;
|
|
200
155
|
for (const trigger of config.experimentalTriggers) {
|
|
201
|
-
if (trigger.type?.startsWith("queue/") && trigger.topic
|
|
156
|
+
if (trigger.type?.startsWith("queue/") && trigger.topic) {
|
|
202
157
|
mappings.push({
|
|
203
158
|
urlPath: filePathToUrlPath(filePath),
|
|
204
159
|
topic: trigger.topic,
|
|
205
|
-
consumer:
|
|
160
|
+
consumer: filePathToConsumerGroup(filePath)
|
|
206
161
|
});
|
|
207
162
|
}
|
|
208
163
|
}
|
|
@@ -235,20 +190,16 @@ var DEV_VISIBILITY_MAX_WAIT = 5e3;
|
|
|
235
190
|
var DEV_VISIBILITY_BACKOFF_MULTIPLIER = 2;
|
|
236
191
|
async function waitForMessageVisibility(topicName, consumerGroup, messageId) {
|
|
237
192
|
const client = new QueueClient();
|
|
238
|
-
const transport = new JsonTransport();
|
|
239
193
|
let elapsed = 0;
|
|
240
194
|
let interval = DEV_VISIBILITY_POLL_INTERVAL;
|
|
241
195
|
while (elapsed < DEV_VISIBILITY_MAX_WAIT) {
|
|
242
196
|
try {
|
|
243
|
-
await client.receiveMessageById(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
},
|
|
250
|
-
transport
|
|
251
|
-
);
|
|
197
|
+
await client.receiveMessageById({
|
|
198
|
+
queueName: topicName,
|
|
199
|
+
consumerGroup,
|
|
200
|
+
messageId,
|
|
201
|
+
visibilityTimeoutSeconds: 0
|
|
202
|
+
});
|
|
252
203
|
return true;
|
|
253
204
|
} catch (error) {
|
|
254
205
|
if (error instanceof MessageNotFoundError) {
|
|
@@ -322,26 +273,15 @@ function triggerDevCallbacks(topicName, messageId, delaySeconds) {
|
|
|
322
273
|
console.log(
|
|
323
274
|
`[Dev Mode] Invoking handler: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" url="${url}"`
|
|
324
275
|
);
|
|
325
|
-
const cloudEvent = {
|
|
326
|
-
type: "com.vercel.queue.v1beta",
|
|
327
|
-
source: `/topic/${topicName}/consumer/${route.consumer}`,
|
|
328
|
-
id: messageId,
|
|
329
|
-
datacontenttype: "application/json",
|
|
330
|
-
data: {
|
|
331
|
-
messageId,
|
|
332
|
-
queueName: topicName,
|
|
333
|
-
consumerGroup: route.consumer
|
|
334
|
-
},
|
|
335
|
-
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
336
|
-
specversion: "1.0"
|
|
337
|
-
};
|
|
338
276
|
try {
|
|
339
277
|
const response = await fetch(url, {
|
|
340
278
|
method: "POST",
|
|
341
279
|
headers: {
|
|
342
|
-
"
|
|
343
|
-
|
|
344
|
-
|
|
280
|
+
"ce-type": CLOUD_EVENT_TYPE_V2BETA,
|
|
281
|
+
"ce-vqsqueuename": topicName,
|
|
282
|
+
"ce-vqsconsumergroup": route.consumer,
|
|
283
|
+
"ce-vqsmessageid": messageId
|
|
284
|
+
}
|
|
345
285
|
});
|
|
346
286
|
if (response.ok) {
|
|
347
287
|
try {
|
|
@@ -388,6 +328,46 @@ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
|
388
328
|
// src/oidc.ts
|
|
389
329
|
var import_oidc = require("@vercel/oidc");
|
|
390
330
|
|
|
331
|
+
// src/transports.ts
|
|
332
|
+
async function streamToBuffer(stream) {
|
|
333
|
+
let totalLength = 0;
|
|
334
|
+
const reader = stream.getReader();
|
|
335
|
+
const chunks = [];
|
|
336
|
+
try {
|
|
337
|
+
while (true) {
|
|
338
|
+
const { done, value } = await reader.read();
|
|
339
|
+
if (done) break;
|
|
340
|
+
chunks.push(value);
|
|
341
|
+
totalLength += value.length;
|
|
342
|
+
}
|
|
343
|
+
} finally {
|
|
344
|
+
reader.releaseLock();
|
|
345
|
+
}
|
|
346
|
+
return Buffer.concat(chunks, totalLength);
|
|
347
|
+
}
|
|
348
|
+
var JsonTransport = class {
|
|
349
|
+
contentType = "application/json";
|
|
350
|
+
replacer;
|
|
351
|
+
reviver;
|
|
352
|
+
/**
|
|
353
|
+
* Create a new JsonTransport.
|
|
354
|
+
* @param options - Optional JSON serialization options
|
|
355
|
+
* @param options.replacer - Custom replacer for JSON.stringify
|
|
356
|
+
* @param options.reviver - Custom reviver for JSON.parse
|
|
357
|
+
*/
|
|
358
|
+
constructor(options = {}) {
|
|
359
|
+
this.replacer = options.replacer;
|
|
360
|
+
this.reviver = options.reviver;
|
|
361
|
+
}
|
|
362
|
+
serialize(value) {
|
|
363
|
+
return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
|
|
364
|
+
}
|
|
365
|
+
async deserialize(stream) {
|
|
366
|
+
const buffer = await streamToBuffer(stream);
|
|
367
|
+
return JSON.parse(buffer.toString("utf8"), this.reviver);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
391
371
|
// src/client.ts
|
|
392
372
|
function isDebugEnabled() {
|
|
393
373
|
return process.env.VERCEL_QUEUE_DEBUG === "1" || process.env.VERCEL_QUEUE_DEBUG === "true";
|
|
@@ -448,6 +428,7 @@ var QueueClient = class {
|
|
|
448
428
|
providedToken;
|
|
449
429
|
defaultDeploymentId;
|
|
450
430
|
pinToDeployment;
|
|
431
|
+
transport;
|
|
451
432
|
constructor(options = {}) {
|
|
452
433
|
this.baseUrl = options.baseUrl || process.env.VERCEL_QUEUE_BASE_URL || "https://vercel-queue.com";
|
|
453
434
|
this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v3/topic";
|
|
@@ -455,6 +436,10 @@ var QueueClient = class {
|
|
|
455
436
|
this.providedToken = options.token;
|
|
456
437
|
this.defaultDeploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
|
|
457
438
|
this.pinToDeployment = options.pinToDeployment ?? true;
|
|
439
|
+
this.transport = options.transport || new JsonTransport();
|
|
440
|
+
}
|
|
441
|
+
getTransport() {
|
|
442
|
+
return this.transport;
|
|
458
443
|
}
|
|
459
444
|
getSendDeploymentId() {
|
|
460
445
|
if (isDevMode()) {
|
|
@@ -511,6 +496,8 @@ var QueueClient = class {
|
|
|
511
496
|
}
|
|
512
497
|
console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
|
|
513
498
|
}
|
|
499
|
+
init.headers.set("User-Agent", `@vercel/queue/${"0.0.0-alpha.38"}`);
|
|
500
|
+
init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
|
|
514
501
|
const response = await fetch(url, init);
|
|
515
502
|
if (isDebugEnabled()) {
|
|
516
503
|
const logData = {
|
|
@@ -533,7 +520,6 @@ var QueueClient = class {
|
|
|
533
520
|
* @param options.idempotencyKey - Optional deduplication key (dedup window: min(retention, 24h))
|
|
534
521
|
* @param options.retentionSeconds - Message TTL (default: 86400, min: 60, max: 86400)
|
|
535
522
|
* @param options.delaySeconds - Delivery delay (default: 0, max: retentionSeconds)
|
|
536
|
-
* @param transport - Serializer for the payload
|
|
537
523
|
* @returns Promise with the generated messageId
|
|
538
524
|
* @throws {DuplicateMessageError} When idempotency key was already used
|
|
539
525
|
* @throws {ConsumerDiscoveryError} When consumer discovery fails
|
|
@@ -543,7 +529,8 @@ var QueueClient = class {
|
|
|
543
529
|
* @throws {ForbiddenError} When access is denied
|
|
544
530
|
* @throws {InternalServerError} When server encounters an error
|
|
545
531
|
*/
|
|
546
|
-
async sendMessage(options
|
|
532
|
+
async sendMessage(options) {
|
|
533
|
+
const transport = this.transport;
|
|
547
534
|
const {
|
|
548
535
|
queueName,
|
|
549
536
|
payload,
|
|
@@ -625,21 +612,24 @@ var QueueClient = class {
|
|
|
625
612
|
/**
|
|
626
613
|
* Receive messages from a topic as an async generator.
|
|
627
614
|
*
|
|
615
|
+
* When the queue is empty, the generator completes without yielding any
|
|
616
|
+
* messages. Callers should handle the case where no messages are yielded.
|
|
617
|
+
*
|
|
628
618
|
* @param options - Receive options
|
|
629
619
|
* @param options.queueName - Topic name (pattern: `[A-Za-z0-9_-]+`)
|
|
630
620
|
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
631
621
|
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
632
622
|
* @param options.limit - Max messages to retrieve (default: 1, min: 1, max: 10)
|
|
633
|
-
* @param transport - Deserializer for message payloads
|
|
634
623
|
* @yields Message objects with payload, messageId, receiptHandle, etc.
|
|
635
|
-
*
|
|
624
|
+
* Yields nothing if queue is empty.
|
|
636
625
|
* @throws {InvalidLimitError} When limit is outside 1-10 range
|
|
637
626
|
* @throws {BadRequestError} When parameters are invalid
|
|
638
627
|
* @throws {UnauthorizedError} When authentication fails
|
|
639
628
|
* @throws {ForbiddenError} When access is denied
|
|
640
629
|
* @throws {InternalServerError} When server encounters an error
|
|
641
630
|
*/
|
|
642
|
-
async *receiveMessages(options
|
|
631
|
+
async *receiveMessages(options) {
|
|
632
|
+
const transport = this.transport;
|
|
643
633
|
const { queueName, consumerGroup, visibilityTimeoutSeconds, limit } = options;
|
|
644
634
|
if (limit !== void 0 && (limit < 1 || limit > 10)) {
|
|
645
635
|
throw new InvalidLimitError(limit);
|
|
@@ -670,7 +660,7 @@ var QueueClient = class {
|
|
|
670
660
|
}
|
|
671
661
|
);
|
|
672
662
|
if (response.status === 204) {
|
|
673
|
-
|
|
663
|
+
return;
|
|
674
664
|
}
|
|
675
665
|
if (!response.ok) {
|
|
676
666
|
const errorText = await response.text();
|
|
@@ -711,7 +701,6 @@ var QueueClient = class {
|
|
|
711
701
|
* @param options.consumerGroup - Consumer group name (pattern: `[A-Za-z0-9_-]+`)
|
|
712
702
|
* @param options.messageId - Message ID to retrieve
|
|
713
703
|
* @param options.visibilityTimeoutSeconds - Lock duration (default: 30, min: 0, max: 3600)
|
|
714
|
-
* @param transport - Deserializer for the message payload
|
|
715
704
|
* @returns Promise with the message
|
|
716
705
|
* @throws {MessageNotFoundError} When message doesn't exist
|
|
717
706
|
* @throws {MessageNotAvailableError} When message is in wrong state or was a duplicate
|
|
@@ -721,7 +710,8 @@ var QueueClient = class {
|
|
|
721
710
|
* @throws {ForbiddenError} When access is denied
|
|
722
711
|
* @throws {InternalServerError} When server encounters an error
|
|
723
712
|
*/
|
|
724
|
-
async receiveMessageById(options
|
|
713
|
+
async receiveMessageById(options) {
|
|
714
|
+
const transport = this.transport;
|
|
725
715
|
const { queueName, consumerGroup, messageId, visibilityTimeoutSeconds } = options;
|
|
726
716
|
const headers = new Headers({
|
|
727
717
|
Authorization: `Bearer ${await this.getToken()}`,
|
|
@@ -984,40 +974,90 @@ var QueueClient = class {
|
|
|
984
974
|
};
|
|
985
975
|
|
|
986
976
|
// src/consumer-group.ts
|
|
977
|
+
var DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 300;
|
|
978
|
+
var MIN_VISIBILITY_TIMEOUT_SECONDS = 30;
|
|
979
|
+
var MAX_RENEWAL_INTERVAL_SECONDS = 60;
|
|
980
|
+
var MIN_RENEWAL_INTERVAL_SECONDS = 10;
|
|
981
|
+
var RETRY_INTERVAL_MS = 3e3;
|
|
982
|
+
function calculateRenewalInterval(visibilityTimeoutSeconds) {
|
|
983
|
+
return Math.min(
|
|
984
|
+
MAX_RENEWAL_INTERVAL_SECONDS,
|
|
985
|
+
Math.max(MIN_RENEWAL_INTERVAL_SECONDS, visibilityTimeoutSeconds / 5)
|
|
986
|
+
);
|
|
987
|
+
}
|
|
987
988
|
var ConsumerGroup = class {
|
|
988
989
|
client;
|
|
989
990
|
topicName;
|
|
990
991
|
consumerGroupName;
|
|
991
992
|
visibilityTimeout;
|
|
992
|
-
refreshInterval;
|
|
993
|
-
transport;
|
|
994
993
|
/**
|
|
995
994
|
* Create a new ConsumerGroup instance.
|
|
996
995
|
*
|
|
997
|
-
* @param client - QueueClient instance to use for API calls
|
|
996
|
+
* @param client - QueueClient instance to use for API calls (transport is configured on the client)
|
|
998
997
|
* @param topicName - Name of the topic to consume from (pattern: `[A-Za-z0-9_-]+`)
|
|
999
998
|
* @param consumerGroupName - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
1000
999
|
* @param options - Optional configuration
|
|
1001
|
-
* @param options.
|
|
1002
|
-
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 30, max: 3600)
|
|
1003
|
-
* @param options.visibilityRefreshInterval - Lock refresh interval in seconds (default: visibilityTimeout / 3)
|
|
1000
|
+
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
|
|
1004
1001
|
*/
|
|
1005
1002
|
constructor(client, topicName, consumerGroupName, options = {}) {
|
|
1006
1003
|
this.client = client;
|
|
1007
1004
|
this.topicName = topicName;
|
|
1008
1005
|
this.consumerGroupName = consumerGroupName;
|
|
1009
|
-
this.visibilityTimeout =
|
|
1010
|
-
|
|
1011
|
-
|
|
1006
|
+
this.visibilityTimeout = Math.max(
|
|
1007
|
+
MIN_VISIBILITY_TIMEOUT_SECONDS,
|
|
1008
|
+
options.visibilityTimeoutSeconds ?? DEFAULT_VISIBILITY_TIMEOUT_SECONDS
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Check if an error is a 4xx client error that should stop retries.
|
|
1013
|
+
* 4xx errors indicate the request is fundamentally invalid and retrying won't help.
|
|
1014
|
+
* - 409: Ticket mismatch (lost ownership to another consumer)
|
|
1015
|
+
* - 404: Message/receipt handle not found
|
|
1016
|
+
* - 400, 401, 403: Other client errors
|
|
1017
|
+
*/
|
|
1018
|
+
isClientError(error) {
|
|
1019
|
+
return error instanceof MessageNotAvailableError || // 409 - ticket mismatch, lost ownership
|
|
1020
|
+
error instanceof MessageNotFoundError || // 404 - receipt handle not found
|
|
1021
|
+
error instanceof BadRequestError || // 400 - invalid parameters
|
|
1022
|
+
error instanceof UnauthorizedError || // 401 - auth failed
|
|
1023
|
+
error instanceof ForbiddenError;
|
|
1012
1024
|
}
|
|
1013
1025
|
/**
|
|
1014
1026
|
* Starts a background loop that periodically extends the visibility timeout for a message.
|
|
1027
|
+
*
|
|
1028
|
+
* Timing strategy:
|
|
1029
|
+
* - Renewal interval: min(60s, max(10s, visibilityTimeout/5))
|
|
1030
|
+
* - Extensions request the same duration as the initial visibility timeout
|
|
1031
|
+
* - When `visibilityDeadline` is provided (binary mode small body), the first
|
|
1032
|
+
* extension delay is calculated from the time remaining until the deadline
|
|
1033
|
+
* using the same renewal formula, ensuring the first extension fires before
|
|
1034
|
+
* the server-assigned lease expires. Subsequent renewals use the standard interval.
|
|
1035
|
+
*
|
|
1036
|
+
* Retry strategy:
|
|
1037
|
+
* - On transient failures (5xx, network errors): retry every 3 seconds
|
|
1038
|
+
* - On 4xx client errors: stop retrying (the lease is lost or invalid)
|
|
1039
|
+
*
|
|
1040
|
+
* @param receiptHandle - The receipt handle to extend visibility for
|
|
1041
|
+
* @param options - Optional configuration
|
|
1042
|
+
* @param options.visibilityDeadline - Absolute deadline (from server's `ce-vqsvisibilitydeadline`)
|
|
1043
|
+
* when the current visibility timeout expires. Used to calculate the first extension delay.
|
|
1015
1044
|
*/
|
|
1016
|
-
startVisibilityExtension(receiptHandle) {
|
|
1045
|
+
startVisibilityExtension(receiptHandle, options) {
|
|
1017
1046
|
let isRunning = true;
|
|
1018
1047
|
let isResolved = false;
|
|
1019
1048
|
let resolveLifecycle;
|
|
1020
1049
|
let timeoutId = null;
|
|
1050
|
+
const renewalIntervalMs = calculateRenewalInterval(this.visibilityTimeout) * 1e3;
|
|
1051
|
+
let firstDelayMs = renewalIntervalMs;
|
|
1052
|
+
if (options?.visibilityDeadline) {
|
|
1053
|
+
const timeRemainingMs = options.visibilityDeadline.getTime() - Date.now();
|
|
1054
|
+
if (timeRemainingMs > 0) {
|
|
1055
|
+
const timeRemainingSeconds = timeRemainingMs / 1e3;
|
|
1056
|
+
firstDelayMs = calculateRenewalInterval(timeRemainingSeconds) * 1e3;
|
|
1057
|
+
} else {
|
|
1058
|
+
firstDelayMs = 0;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1021
1061
|
const lifecyclePromise = new Promise((resolve) => {
|
|
1022
1062
|
resolveLifecycle = resolve;
|
|
1023
1063
|
});
|
|
@@ -1040,19 +1080,31 @@ var ConsumerGroup = class {
|
|
|
1040
1080
|
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1041
1081
|
});
|
|
1042
1082
|
if (isRunning) {
|
|
1043
|
-
timeoutId = setTimeout(() => extend(),
|
|
1083
|
+
timeoutId = setTimeout(() => extend(), renewalIntervalMs);
|
|
1044
1084
|
} else {
|
|
1045
1085
|
safeResolve();
|
|
1046
1086
|
}
|
|
1047
1087
|
} catch (error) {
|
|
1088
|
+
if (this.isClientError(error)) {
|
|
1089
|
+
console.error(
|
|
1090
|
+
`Visibility extension failed with client error for receipt handle ${receiptHandle} (stopping retries):`,
|
|
1091
|
+
error
|
|
1092
|
+
);
|
|
1093
|
+
safeResolve();
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1048
1096
|
console.error(
|
|
1049
|
-
`Failed to extend visibility for receipt handle ${receiptHandle}:`,
|
|
1097
|
+
`Failed to extend visibility for receipt handle ${receiptHandle} (will retry in ${RETRY_INTERVAL_MS / 1e3}s):`,
|
|
1050
1098
|
error
|
|
1051
1099
|
);
|
|
1052
|
-
|
|
1100
|
+
if (isRunning) {
|
|
1101
|
+
timeoutId = setTimeout(() => extend(), RETRY_INTERVAL_MS);
|
|
1102
|
+
} else {
|
|
1103
|
+
safeResolve();
|
|
1104
|
+
}
|
|
1053
1105
|
}
|
|
1054
1106
|
};
|
|
1055
|
-
timeoutId = setTimeout(() => extend(),
|
|
1107
|
+
timeoutId = setTimeout(() => extend(), firstDelayMs);
|
|
1056
1108
|
return async (waitForCompletion = false) => {
|
|
1057
1109
|
isRunning = false;
|
|
1058
1110
|
if (timeoutId) {
|
|
@@ -1066,8 +1118,11 @@ var ConsumerGroup = class {
|
|
|
1066
1118
|
}
|
|
1067
1119
|
};
|
|
1068
1120
|
}
|
|
1069
|
-
async processMessage(message, handler) {
|
|
1070
|
-
const stopExtension = this.startVisibilityExtension(
|
|
1121
|
+
async processMessage(message, handler, options) {
|
|
1122
|
+
const stopExtension = this.startVisibilityExtension(
|
|
1123
|
+
message.receiptHandle,
|
|
1124
|
+
options
|
|
1125
|
+
);
|
|
1071
1126
|
try {
|
|
1072
1127
|
await handler(message.payload, {
|
|
1073
1128
|
messageId: message.messageId,
|
|
@@ -1084,9 +1139,10 @@ var ConsumerGroup = class {
|
|
|
1084
1139
|
});
|
|
1085
1140
|
} catch (error) {
|
|
1086
1141
|
await stopExtension();
|
|
1087
|
-
|
|
1142
|
+
const transport = this.client.getTransport();
|
|
1143
|
+
if (transport.finalize && message.payload !== void 0 && message.payload !== null) {
|
|
1088
1144
|
try {
|
|
1089
|
-
await
|
|
1145
|
+
await transport.finalize(message.payload);
|
|
1090
1146
|
} catch (finalizeError) {
|
|
1091
1147
|
console.warn("Failed to finalize message payload:", finalizeError);
|
|
1092
1148
|
}
|
|
@@ -1094,35 +1150,49 @@ var ConsumerGroup = class {
|
|
|
1094
1150
|
throw error;
|
|
1095
1151
|
}
|
|
1096
1152
|
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Process a pre-fetched message directly, without calling `receiveMessageById`.
|
|
1155
|
+
*
|
|
1156
|
+
* Used by the binary mode (v2beta) small body fast path, where the server
|
|
1157
|
+
* pushes the full message payload in the callback request. The message is
|
|
1158
|
+
* processed with the same lifecycle guarantees as `consume()`:
|
|
1159
|
+
* - Visibility timeout is extended periodically during processing
|
|
1160
|
+
* - Message is deleted on successful handler completion
|
|
1161
|
+
* - Payload is finalized on error if the transport supports it
|
|
1162
|
+
*
|
|
1163
|
+
* @param handler - Function to process the message payload and metadata
|
|
1164
|
+
* @param message - The complete message including payload and receipt handle
|
|
1165
|
+
* @param options - Optional configuration
|
|
1166
|
+
* @param options.visibilityDeadline - Absolute deadline when the server-assigned
|
|
1167
|
+
* visibility timeout expires (from `ce-vqsvisibilitydeadline`). Used to
|
|
1168
|
+
* schedule the first visibility extension before the lease expires.
|
|
1169
|
+
*/
|
|
1170
|
+
async consumeMessage(handler, message, options) {
|
|
1171
|
+
await this.processMessage(message, handler, options);
|
|
1172
|
+
}
|
|
1097
1173
|
async consume(handler, options) {
|
|
1098
|
-
if (options
|
|
1099
|
-
const response = await this.client.receiveMessageById(
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
},
|
|
1106
|
-
this.transport
|
|
1107
|
-
);
|
|
1174
|
+
if (options && "messageId" in options) {
|
|
1175
|
+
const response = await this.client.receiveMessageById({
|
|
1176
|
+
queueName: this.topicName,
|
|
1177
|
+
consumerGroup: this.consumerGroupName,
|
|
1178
|
+
messageId: options.messageId,
|
|
1179
|
+
visibilityTimeoutSeconds: this.visibilityTimeout
|
|
1180
|
+
});
|
|
1108
1181
|
await this.processMessage(response.message, handler);
|
|
1109
1182
|
} else {
|
|
1183
|
+
const limit = options && "limit" in options ? options.limit : 1;
|
|
1110
1184
|
let messageFound = false;
|
|
1111
|
-
for await (const message of this.client.receiveMessages(
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
},
|
|
1118
|
-
this.transport
|
|
1119
|
-
)) {
|
|
1185
|
+
for await (const message of this.client.receiveMessages({
|
|
1186
|
+
queueName: this.topicName,
|
|
1187
|
+
consumerGroup: this.consumerGroupName,
|
|
1188
|
+
visibilityTimeoutSeconds: this.visibilityTimeout,
|
|
1189
|
+
limit
|
|
1190
|
+
})) {
|
|
1120
1191
|
messageFound = true;
|
|
1121
1192
|
await this.processMessage(message, handler);
|
|
1122
|
-
break;
|
|
1123
1193
|
}
|
|
1124
1194
|
if (!messageFound) {
|
|
1125
|
-
|
|
1195
|
+
await handler(null, null);
|
|
1126
1196
|
}
|
|
1127
1197
|
}
|
|
1128
1198
|
}
|
|
@@ -1144,17 +1214,14 @@ var ConsumerGroup = class {
|
|
|
1144
1214
|
var Topic = class {
|
|
1145
1215
|
client;
|
|
1146
1216
|
topicName;
|
|
1147
|
-
transport;
|
|
1148
1217
|
/**
|
|
1149
1218
|
* Create a new Topic instance
|
|
1150
|
-
* @param client QueueClient instance to use for API calls
|
|
1219
|
+
* @param client QueueClient instance to use for API calls (transport is configured on the client)
|
|
1151
1220
|
* @param topicName Name of the topic to work with
|
|
1152
|
-
* @param transport Optional serializer/deserializer for the payload (defaults to JSON)
|
|
1153
1221
|
*/
|
|
1154
|
-
constructor(client, topicName
|
|
1222
|
+
constructor(client, topicName) {
|
|
1155
1223
|
this.client = client;
|
|
1156
1224
|
this.topicName = topicName;
|
|
1157
|
-
this.transport = transport || new JsonTransport();
|
|
1158
1225
|
}
|
|
1159
1226
|
/**
|
|
1160
1227
|
* Publish a message to the topic
|
|
@@ -1167,17 +1234,14 @@ var Topic = class {
|
|
|
1167
1234
|
* @throws {InternalServerError} When server encounters an error
|
|
1168
1235
|
*/
|
|
1169
1236
|
async publish(payload, options) {
|
|
1170
|
-
const result = await this.client.sendMessage(
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
},
|
|
1179
|
-
this.transport
|
|
1180
|
-
);
|
|
1237
|
+
const result = await this.client.sendMessage({
|
|
1238
|
+
queueName: this.topicName,
|
|
1239
|
+
payload,
|
|
1240
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1241
|
+
retentionSeconds: options?.retentionSeconds,
|
|
1242
|
+
delaySeconds: options?.delaySeconds,
|
|
1243
|
+
headers: options?.headers
|
|
1244
|
+
});
|
|
1181
1245
|
if (isDevMode()) {
|
|
1182
1246
|
triggerDevCallbacks(this.topicName, result.messageId);
|
|
1183
1247
|
}
|
|
@@ -1190,15 +1254,11 @@ var Topic = class {
|
|
|
1190
1254
|
* @returns A ConsumerGroup instance
|
|
1191
1255
|
*/
|
|
1192
1256
|
consumerGroup(consumerGroupName, options) {
|
|
1193
|
-
const consumerOptions = {
|
|
1194
|
-
...options,
|
|
1195
|
-
transport: options?.transport || this.transport
|
|
1196
|
-
};
|
|
1197
1257
|
return new ConsumerGroup(
|
|
1198
1258
|
this.client,
|
|
1199
1259
|
this.topicName,
|
|
1200
1260
|
consumerGroupName,
|
|
1201
|
-
|
|
1261
|
+
options
|
|
1202
1262
|
);
|
|
1203
1263
|
}
|
|
1204
1264
|
/**
|
|
@@ -1207,220 +1267,167 @@ var Topic = class {
|
|
|
1207
1267
|
get name() {
|
|
1208
1268
|
return this.topicName;
|
|
1209
1269
|
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Get the transport used by this topic
|
|
1212
|
-
*/
|
|
1213
|
-
get serializer() {
|
|
1214
|
-
return this.transport;
|
|
1215
|
-
}
|
|
1216
1270
|
};
|
|
1217
1271
|
|
|
1218
1272
|
// src/callback.ts
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
const lastIndex = pattern.lastIndexOf("*");
|
|
1222
|
-
if (firstIndex !== lastIndex) {
|
|
1223
|
-
return false;
|
|
1224
|
-
}
|
|
1225
|
-
if (firstIndex === -1) {
|
|
1226
|
-
return false;
|
|
1227
|
-
}
|
|
1228
|
-
if (firstIndex !== pattern.length - 1) {
|
|
1229
|
-
return false;
|
|
1230
|
-
}
|
|
1231
|
-
return true;
|
|
1232
|
-
}
|
|
1273
|
+
var CLOUD_EVENT_TYPE_V1BETA = "com.vercel.queue.v1beta";
|
|
1274
|
+
var CLOUD_EVENT_TYPE_V2BETA = "com.vercel.queue.v2beta";
|
|
1233
1275
|
function matchesWildcardPattern(topicName, pattern) {
|
|
1234
1276
|
const prefix = pattern.slice(0, -1);
|
|
1235
1277
|
return topicName.startsWith(prefix);
|
|
1236
1278
|
}
|
|
1237
|
-
function
|
|
1238
|
-
|
|
1239
|
-
if (exactHandler) {
|
|
1240
|
-
return exactHandler;
|
|
1241
|
-
}
|
|
1242
|
-
for (const pattern in handlers) {
|
|
1243
|
-
if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
|
|
1244
|
-
return handlers[pattern];
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
return null;
|
|
1279
|
+
function isRecord(value) {
|
|
1280
|
+
return typeof value === "object" && value !== null;
|
|
1248
1281
|
}
|
|
1249
|
-
|
|
1250
|
-
const contentType = request.headers.get("content-type");
|
|
1282
|
+
function parseV1StructuredBody(body, contentType) {
|
|
1251
1283
|
if (!contentType || !contentType.includes("application/cloudevents+json")) {
|
|
1252
1284
|
throw new Error(
|
|
1253
1285
|
"Invalid content type: expected 'application/cloudevents+json'"
|
|
1254
1286
|
);
|
|
1255
1287
|
}
|
|
1256
|
-
|
|
1257
|
-
try {
|
|
1258
|
-
cloudEvent = await request.json();
|
|
1259
|
-
} catch (error) {
|
|
1260
|
-
throw new Error("Failed to parse CloudEvent from request body");
|
|
1261
|
-
}
|
|
1262
|
-
if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
|
|
1288
|
+
if (!isRecord(body) || !body.type || !body.source || !body.id || !isRecord(body.data)) {
|
|
1263
1289
|
throw new Error("Invalid CloudEvent: missing required fields");
|
|
1264
1290
|
}
|
|
1265
|
-
if (
|
|
1291
|
+
if (body.type !== CLOUD_EVENT_TYPE_V1BETA) {
|
|
1266
1292
|
throw new Error(
|
|
1267
|
-
`Invalid CloudEvent type: expected '
|
|
1293
|
+
`Invalid CloudEvent type: expected '${CLOUD_EVENT_TYPE_V1BETA}', got '${String(body.type)}'`
|
|
1268
1294
|
);
|
|
1269
1295
|
}
|
|
1296
|
+
const { data } = body;
|
|
1270
1297
|
const missingFields = [];
|
|
1271
|
-
if (!("queueName" in
|
|
1272
|
-
if (!("consumerGroup" in
|
|
1273
|
-
|
|
1274
|
-
if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
|
|
1298
|
+
if (!("queueName" in data)) missingFields.push("queueName");
|
|
1299
|
+
if (!("consumerGroup" in data)) missingFields.push("consumerGroup");
|
|
1300
|
+
if (!("messageId" in data)) missingFields.push("messageId");
|
|
1275
1301
|
if (missingFields.length > 0) {
|
|
1276
1302
|
throw new Error(
|
|
1277
1303
|
`Missing required CloudEvent data fields: ${missingFields.join(", ")}`
|
|
1278
1304
|
);
|
|
1279
1305
|
}
|
|
1280
|
-
const { messageId, queueName, consumerGroup } = cloudEvent.data;
|
|
1281
1306
|
return {
|
|
1282
|
-
queueName,
|
|
1283
|
-
consumerGroup,
|
|
1284
|
-
messageId
|
|
1307
|
+
queueName: String(data.queueName),
|
|
1308
|
+
consumerGroup: String(data.consumerGroup),
|
|
1309
|
+
messageId: String(data.messageId)
|
|
1285
1310
|
};
|
|
1286
1311
|
}
|
|
1287
|
-
function createCallbackHandler(handlers, client, visibilityTimeoutSeconds) {
|
|
1288
|
-
for (const topicPattern in handlers) {
|
|
1289
|
-
if (topicPattern.includes("*")) {
|
|
1290
|
-
if (!validateWildcardPattern(topicPattern)) {
|
|
1291
|
-
throw new Error(
|
|
1292
|
-
`Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
|
|
1293
|
-
);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
const routeHandler = async (request) => {
|
|
1298
|
-
try {
|
|
1299
|
-
const { queueName, consumerGroup, messageId } = await parseCallback(request);
|
|
1300
|
-
const topicHandler = findTopicHandler(queueName, handlers);
|
|
1301
|
-
if (!topicHandler) {
|
|
1302
|
-
const availableTopics = Object.keys(handlers).join(", ");
|
|
1303
|
-
return Response.json(
|
|
1304
|
-
{
|
|
1305
|
-
error: `No handler found for topic: ${queueName}`,
|
|
1306
|
-
availableTopics
|
|
1307
|
-
},
|
|
1308
|
-
{ status: 404 }
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
1312
|
-
if (!consumerGroupHandler) {
|
|
1313
|
-
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
1314
|
-
return Response.json(
|
|
1315
|
-
{
|
|
1316
|
-
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
1317
|
-
availableGroups
|
|
1318
|
-
},
|
|
1319
|
-
{ status: 404 }
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
const topic = new Topic(client, queueName);
|
|
1323
|
-
const cg = topic.consumerGroup(
|
|
1324
|
-
consumerGroup,
|
|
1325
|
-
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : void 0
|
|
1326
|
-
);
|
|
1327
|
-
await cg.consume(consumerGroupHandler, { messageId });
|
|
1328
|
-
return Response.json({ status: "success" });
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
console.error("Queue callback error:", error);
|
|
1331
|
-
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"))) {
|
|
1332
|
-
return Response.json({ error: error.message }, { status: 400 });
|
|
1333
|
-
}
|
|
1334
|
-
return Response.json(
|
|
1335
|
-
{ error: "Failed to process queue message" },
|
|
1336
|
-
{ status: 500 }
|
|
1337
|
-
);
|
|
1338
|
-
}
|
|
1339
|
-
};
|
|
1340
|
-
return routeHandler;
|
|
1341
|
-
}
|
|
1342
|
-
function handleCallback(handlers, options) {
|
|
1343
|
-
return createCallbackHandler(
|
|
1344
|
-
handlers,
|
|
1345
|
-
options?.client || new QueueClient(),
|
|
1346
|
-
options?.visibilityTimeoutSeconds
|
|
1347
|
-
);
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
// src/nextjs-pages.ts
|
|
1351
1312
|
function getHeader(headers, name) {
|
|
1313
|
+
if (headers instanceof Headers) {
|
|
1314
|
+
return headers.get(name);
|
|
1315
|
+
}
|
|
1352
1316
|
const value = headers[name];
|
|
1353
|
-
|
|
1317
|
+
if (Array.isArray(value)) return value[0] ?? null;
|
|
1318
|
+
return value ?? null;
|
|
1354
1319
|
}
|
|
1355
|
-
function
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1320
|
+
function parseBinaryHeaders(headers) {
|
|
1321
|
+
const ceType = getHeader(headers, "ce-type");
|
|
1322
|
+
if (ceType !== CLOUD_EVENT_TYPE_V2BETA) {
|
|
1323
|
+
throw new Error(
|
|
1324
|
+
`Invalid CloudEvent type: expected '${CLOUD_EVENT_TYPE_V2BETA}', got '${ceType}'`
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
const queueName = getHeader(headers, "ce-vqsqueuename");
|
|
1328
|
+
const consumerGroup = getHeader(headers, "ce-vqsconsumergroup");
|
|
1329
|
+
const messageId = getHeader(headers, "ce-vqsmessageid");
|
|
1330
|
+
const missingFields = [];
|
|
1331
|
+
if (!queueName) missingFields.push("ce-vqsqueuename");
|
|
1332
|
+
if (!consumerGroup) missingFields.push("ce-vqsconsumergroup");
|
|
1333
|
+
if (!messageId) missingFields.push("ce-vqsmessageid");
|
|
1334
|
+
if (missingFields.length > 0) {
|
|
1335
|
+
throw new Error(
|
|
1336
|
+
`Missing required CloudEvent headers: ${missingFields.join(", ")}`
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
const base = {
|
|
1340
|
+
queueName,
|
|
1341
|
+
consumerGroup,
|
|
1342
|
+
messageId
|
|
1343
|
+
};
|
|
1344
|
+
const receiptHandle = getHeader(headers, "ce-vqsreceipthandle");
|
|
1345
|
+
if (!receiptHandle) {
|
|
1346
|
+
return base;
|
|
1366
1347
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1348
|
+
const result = { ...base, receiptHandle };
|
|
1349
|
+
const deliveryCount = getHeader(headers, "ce-vqsdeliverycount");
|
|
1350
|
+
if (deliveryCount) {
|
|
1351
|
+
result.deliveryCount = parseInt(deliveryCount, 10);
|
|
1369
1352
|
}
|
|
1370
|
-
|
|
1353
|
+
const createdAt = getHeader(headers, "ce-vqscreatedat");
|
|
1354
|
+
if (createdAt) {
|
|
1355
|
+
result.createdAt = createdAt;
|
|
1356
|
+
}
|
|
1357
|
+
const contentType = getHeader(headers, "content-type");
|
|
1358
|
+
if (contentType) {
|
|
1359
|
+
result.contentType = contentType;
|
|
1360
|
+
}
|
|
1361
|
+
const visibilityDeadline = getHeader(headers, "ce-vqsvisibilitydeadline");
|
|
1362
|
+
if (visibilityDeadline) {
|
|
1363
|
+
result.visibilityDeadline = visibilityDeadline;
|
|
1364
|
+
}
|
|
1365
|
+
return result;
|
|
1371
1366
|
}
|
|
1372
|
-
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
const url = `${protocol}://${host}${req.url}`;
|
|
1379
|
-
const headers = new Headers();
|
|
1380
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
1381
|
-
if (value) {
|
|
1382
|
-
if (Array.isArray(value)) {
|
|
1383
|
-
value.forEach((v) => headers.append(key, v));
|
|
1384
|
-
} else {
|
|
1385
|
-
headers.set(key, value);
|
|
1386
|
-
}
|
|
1367
|
+
function parseRawCallback(body, headers) {
|
|
1368
|
+
const ceType = getHeader(headers, "ce-type");
|
|
1369
|
+
if (ceType === CLOUD_EVENT_TYPE_V2BETA) {
|
|
1370
|
+
const result = parseBinaryHeaders(headers);
|
|
1371
|
+
if ("receiptHandle" in result) {
|
|
1372
|
+
result.parsedPayload = body;
|
|
1387
1373
|
}
|
|
1374
|
+
return result;
|
|
1388
1375
|
}
|
|
1389
|
-
|
|
1390
|
-
return new Request(url, {
|
|
1391
|
-
method: req.method || "POST",
|
|
1392
|
-
headers,
|
|
1393
|
-
body
|
|
1394
|
-
});
|
|
1376
|
+
return parseV1StructuredBody(body, getHeader(headers, "content-type"));
|
|
1395
1377
|
}
|
|
1396
|
-
async function
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1378
|
+
async function handleCallback(handler, request, options) {
|
|
1379
|
+
const { queueName, consumerGroup, messageId } = request;
|
|
1380
|
+
const client = options?.client || new QueueClient();
|
|
1381
|
+
const topic = new Topic(client, queueName);
|
|
1382
|
+
const cg = topic.consumerGroup(
|
|
1383
|
+
consumerGroup,
|
|
1384
|
+
options?.visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds: options.visibilityTimeoutSeconds } : void 0
|
|
1385
|
+
);
|
|
1386
|
+
if ("receiptHandle" in request) {
|
|
1387
|
+
const transport = client.getTransport();
|
|
1388
|
+
let payload;
|
|
1389
|
+
if (request.rawBody) {
|
|
1390
|
+
payload = await transport.deserialize(request.rawBody);
|
|
1391
|
+
} else if (request.parsedPayload !== void 0) {
|
|
1392
|
+
payload = request.parsedPayload;
|
|
1393
|
+
} else {
|
|
1394
|
+
throw new Error(
|
|
1395
|
+
"Binary mode callback with receipt handle is missing payload"
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
const message = {
|
|
1399
|
+
messageId,
|
|
1400
|
+
payload,
|
|
1401
|
+
deliveryCount: request.deliveryCount ?? 1,
|
|
1402
|
+
createdAt: request.createdAt ? new Date(request.createdAt) : /* @__PURE__ */ new Date(),
|
|
1403
|
+
contentType: request.contentType ?? transport.contentType,
|
|
1404
|
+
receiptHandle: request.receiptHandle
|
|
1405
|
+
};
|
|
1406
|
+
const visibilityDeadline = request.visibilityDeadline ? new Date(request.visibilityDeadline) : void 0;
|
|
1407
|
+
await cg.consumeMessage(handler, message, { visibilityDeadline });
|
|
1405
1408
|
} else {
|
|
1406
|
-
|
|
1407
|
-
res.send(text);
|
|
1409
|
+
await cg.consume(handler, { messageId });
|
|
1408
1410
|
}
|
|
1409
1411
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
+
|
|
1413
|
+
// src/nextjs-pages.ts
|
|
1414
|
+
function handleCallback2(handler, options) {
|
|
1412
1415
|
return async (req, res) => {
|
|
1413
1416
|
if (req.method !== "POST") {
|
|
1414
1417
|
res.status(200).end();
|
|
1415
1418
|
return;
|
|
1416
1419
|
}
|
|
1417
1420
|
try {
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
+
const parsed = parseRawCallback(req.body, req.headers);
|
|
1422
|
+
await handleCallback(handler, parsed, options);
|
|
1423
|
+
res.status(200).json({ status: "success" });
|
|
1421
1424
|
} catch (error) {
|
|
1422
|
-
console.error("
|
|
1423
|
-
|
|
1425
|
+
console.error("Queue callback error:", error);
|
|
1426
|
+
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"))) {
|
|
1427
|
+
res.status(400).json({ error: error.message });
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
res.status(500).json({ error: "Failed to process queue message" });
|
|
1424
1431
|
}
|
|
1425
1432
|
};
|
|
1426
1433
|
}
|