@vercel/queue 0.0.0-alpha.5 → 0.0.0-alpha.6
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 +151 -313
- package/dist/index.d.mts +52 -104
- package/dist/index.d.ts +52 -104
- package/dist/index.js +44 -224
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +44 -221
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,116 +1,6 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
2
|
import { parseMultipartStream } from "mixpart";
|
|
3
3
|
|
|
4
|
-
// src/local.ts
|
|
5
|
-
import { spawn } from "child_process";
|
|
6
|
-
function isLocalhostWithPort(url) {
|
|
7
|
-
try {
|
|
8
|
-
const parsedUrl = new URL(url);
|
|
9
|
-
const isLocalhost = parsedUrl.hostname === "localhost";
|
|
10
|
-
const port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 0;
|
|
11
|
-
return { isLocalhost, port };
|
|
12
|
-
} catch {
|
|
13
|
-
return { isLocalhost: false };
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function isSupportedPlatform() {
|
|
17
|
-
const platform = process.platform;
|
|
18
|
-
return platform === "darwin" || platform === "linux";
|
|
19
|
-
}
|
|
20
|
-
function processDevelopmentCallbacks(callbacks) {
|
|
21
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
22
|
-
if (!isDevelopment) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
if (!isSupportedPlatform()) {
|
|
26
|
-
const hasLocalhostCallbacks = Object.values(callbacks).some((config) => {
|
|
27
|
-
const { isLocalhost } = isLocalhostWithPort(config.url);
|
|
28
|
-
return isLocalhost;
|
|
29
|
-
});
|
|
30
|
-
if (hasLocalhostCallbacks) {
|
|
31
|
-
console.warn(
|
|
32
|
-
`Queue Development Mode: Localhost callbacks are not supported on ${process.platform}. Localhost callback handling requires bash, nc, and curl which are available on macOS and Linux only. Consider using a production callback URL or developing on a supported platform.`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
const localhostCallbacks = [];
|
|
38
|
-
Object.entries(callbacks).forEach(([group, config]) => {
|
|
39
|
-
const { isLocalhost, port } = isLocalhostWithPort(config.url);
|
|
40
|
-
if (isLocalhost && port && port > 0) {
|
|
41
|
-
localhostCallbacks.push({ group, config, port });
|
|
42
|
-
} else {
|
|
43
|
-
console.warn(
|
|
44
|
-
`Queue Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
return localhostCallbacks;
|
|
49
|
-
}
|
|
50
|
-
function fireLocalhostCallbacks(localhostCallbacks, queueName, responseData) {
|
|
51
|
-
localhostCallbacks.forEach(({ group, config, port }) => {
|
|
52
|
-
const callbackHeaders = new Headers();
|
|
53
|
-
callbackHeaders.set("Vqs-Message-Id", responseData.messageId);
|
|
54
|
-
callbackHeaders.set("Vqs-Queue-Name", queueName);
|
|
55
|
-
callbackHeaders.set("Vqs-Consumer-Group", group);
|
|
56
|
-
fireAndForgetWaitForHttpReady(
|
|
57
|
-
config.url,
|
|
58
|
-
port,
|
|
59
|
-
config.delay || 0,
|
|
60
|
-
3,
|
|
61
|
-
// Default retry frequency
|
|
62
|
-
callbackHeaders
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
function fireAndForgetWaitForHttpReady(url, port, initialDelaySeconds = 0, retryFrequencySeconds = 3, headers) {
|
|
67
|
-
if (!isSupportedPlatform()) {
|
|
68
|
-
console.warn(
|
|
69
|
-
`Queue: fireAndForgetWaitForHttpReady is not supported on ${process.platform}. This function requires bash, nc, and curl which are available on macOS and Linux only.`
|
|
70
|
-
);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
let headerArgs = "";
|
|
74
|
-
if (headers) {
|
|
75
|
-
const headerArray = [];
|
|
76
|
-
headers.forEach((value, key) => {
|
|
77
|
-
headerArray.push(`-H '${key}: ${value}'`);
|
|
78
|
-
});
|
|
79
|
-
headerArgs = headerArray.join(" ");
|
|
80
|
-
}
|
|
81
|
-
const bashScript = `
|
|
82
|
-
# Wait for any initial boot time
|
|
83
|
-
sleep ${initialDelaySeconds}
|
|
84
|
-
|
|
85
|
-
missed=0
|
|
86
|
-
while true; do
|
|
87
|
-
# 1) Check if TCP port is listening
|
|
88
|
-
if nc -z localhost ${port} 2>/dev/null; then
|
|
89
|
-
missed=0
|
|
90
|
-
# 2) If port is open, try HTTP POST check
|
|
91
|
-
if curl -sSL --fail -o /dev/null -X POST ${headerArgs} "${url}"; then
|
|
92
|
-
# Success: port is up AND HTTP returned 2xx (following redirects)
|
|
93
|
-
exit 0
|
|
94
|
-
fi
|
|
95
|
-
else
|
|
96
|
-
# Port was closed\u2014increment miss counter
|
|
97
|
-
((missed+=1))
|
|
98
|
-
# If closed twice in a row, give up immediately
|
|
99
|
-
if [ "$missed" -ge 2 ]; then
|
|
100
|
-
exit 1
|
|
101
|
-
fi
|
|
102
|
-
fi
|
|
103
|
-
# Wait before next cycle
|
|
104
|
-
sleep ${retryFrequencySeconds}
|
|
105
|
-
done
|
|
106
|
-
`;
|
|
107
|
-
const childProcess = spawn("bash", ["-c", bashScript], {
|
|
108
|
-
stdio: "ignore",
|
|
109
|
-
detached: true
|
|
110
|
-
});
|
|
111
|
-
childProcess.unref();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
4
|
// src/types.ts
|
|
115
5
|
var MessageNotFoundError = class extends Error {
|
|
116
6
|
constructor(messageId) {
|
|
@@ -126,12 +16,6 @@ var MessageNotAvailableError = class extends Error {
|
|
|
126
16
|
this.name = "MessageNotAvailableError";
|
|
127
17
|
}
|
|
128
18
|
};
|
|
129
|
-
var FifoOrderingViolationError = class extends Error {
|
|
130
|
-
constructor(messageId, reason) {
|
|
131
|
-
super(`FIFO ordering violation for message ${messageId}: ${reason}`);
|
|
132
|
-
this.name = "FifoOrderingViolationError";
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
19
|
var MessageCorruptedError = class extends Error {
|
|
136
20
|
constructor(messageId, reason) {
|
|
137
21
|
super(`Message ${messageId} is corrupted: ${reason}`);
|
|
@@ -173,14 +57,6 @@ var BadRequestError = class extends Error {
|
|
|
173
57
|
this.name = "BadRequestError";
|
|
174
58
|
}
|
|
175
59
|
};
|
|
176
|
-
var FailedDependencyError = class extends Error {
|
|
177
|
-
constructor(messageId) {
|
|
178
|
-
super(
|
|
179
|
-
`Failed dependency: FIFO ordering violation for message ${messageId}`
|
|
180
|
-
);
|
|
181
|
-
this.name = "FailedDependencyError";
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
60
|
var InternalServerError = class extends Error {
|
|
185
61
|
constructor(message = "Unexpected server error") {
|
|
186
62
|
super(message);
|
|
@@ -193,12 +69,6 @@ var InvalidLimitError = class extends Error {
|
|
|
193
69
|
this.name = "InvalidLimitError";
|
|
194
70
|
}
|
|
195
71
|
};
|
|
196
|
-
var InvalidCallbackError = class extends Error {
|
|
197
|
-
constructor(message) {
|
|
198
|
-
super(message);
|
|
199
|
-
this.name = "InvalidCallbackError";
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
72
|
|
|
203
73
|
// src/client.ts
|
|
204
74
|
async function consumeStream(stream) {
|
|
@@ -295,7 +165,7 @@ var QueueClient = class _QueueClient {
|
|
|
295
165
|
* @throws {InternalServerError} When server encounters an error
|
|
296
166
|
*/
|
|
297
167
|
async sendMessage(options, transport) {
|
|
298
|
-
const { queueName, payload, idempotencyKey, retentionSeconds
|
|
168
|
+
const { queueName, payload, idempotencyKey, retentionSeconds } = options;
|
|
299
169
|
const headers = new Headers({
|
|
300
170
|
Authorization: `Bearer ${this.token}`,
|
|
301
171
|
"Vqs-Queue-Name": queueName,
|
|
@@ -310,34 +180,6 @@ var QueueClient = class _QueueClient {
|
|
|
310
180
|
if (retentionSeconds !== void 0) {
|
|
311
181
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
312
182
|
}
|
|
313
|
-
let normalizedCallbacks;
|
|
314
|
-
if (callback) {
|
|
315
|
-
if ("url" in callback && typeof callback.url === "string") {
|
|
316
|
-
normalizedCallbacks = { default: callback };
|
|
317
|
-
} else {
|
|
318
|
-
normalizedCallbacks = callback;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
let localhostCallbacks = [];
|
|
322
|
-
if (normalizedCallbacks) {
|
|
323
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
324
|
-
if (isDevelopment) {
|
|
325
|
-
localhostCallbacks = processDevelopmentCallbacks(normalizedCallbacks);
|
|
326
|
-
} else {
|
|
327
|
-
const endpoints = Object.entries(normalizedCallbacks).map(
|
|
328
|
-
([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
|
|
329
|
-
).join(",");
|
|
330
|
-
headers.set("Vqs-Callback-Url", endpoints);
|
|
331
|
-
const delays = Object.entries(normalizedCallbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
|
|
332
|
-
if (delays) {
|
|
333
|
-
headers.set("Vqs-Callback-Delay", delays);
|
|
334
|
-
}
|
|
335
|
-
const frequencies = Object.entries(normalizedCallbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
|
|
336
|
-
if (frequencies) {
|
|
337
|
-
headers.set("Vqs-Callback-Frequency", frequencies);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
183
|
const body = transport.serialize(payload);
|
|
342
184
|
const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
|
|
343
185
|
method: "POST",
|
|
@@ -368,9 +210,6 @@ var QueueClient = class _QueueClient {
|
|
|
368
210
|
);
|
|
369
211
|
}
|
|
370
212
|
const responseData = await response.json();
|
|
371
|
-
if (localhostCallbacks.length > 0) {
|
|
372
|
-
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
373
|
-
}
|
|
374
213
|
return responseData;
|
|
375
214
|
}
|
|
376
215
|
/**
|
|
@@ -380,7 +219,7 @@ var QueueClient = class _QueueClient {
|
|
|
380
219
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
381
220
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
382
221
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
383
|
-
* @throws {MessageLockedError} When
|
|
222
|
+
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
384
223
|
* @throws {BadRequestError} When request parameters are invalid
|
|
385
224
|
* @throws {UnauthorizedError} When authentication fails
|
|
386
225
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -431,7 +270,7 @@ var QueueClient = class _QueueClient {
|
|
|
431
270
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
432
271
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
433
272
|
}
|
|
434
|
-
throw new MessageLockedError("next message
|
|
273
|
+
throw new MessageLockedError("next message", retryAfter);
|
|
435
274
|
}
|
|
436
275
|
if (response.status >= 500) {
|
|
437
276
|
throw new InternalServerError(
|
|
@@ -517,9 +356,6 @@ var QueueClient = class _QueueClient {
|
|
|
517
356
|
}
|
|
518
357
|
throw new MessageLockedError(messageId, retryAfter);
|
|
519
358
|
}
|
|
520
|
-
if (response.status === 424) {
|
|
521
|
-
throw new FailedDependencyError(messageId);
|
|
522
|
-
}
|
|
523
359
|
if (response.status === 409) {
|
|
524
360
|
throw new MessageNotAvailableError(messageId);
|
|
525
361
|
}
|
|
@@ -715,24 +551,20 @@ var JsonTransport = class {
|
|
|
715
551
|
}
|
|
716
552
|
async deserialize(stream) {
|
|
717
553
|
const reader = stream.getReader();
|
|
554
|
+
let totalLength = 0;
|
|
718
555
|
const chunks = [];
|
|
719
556
|
try {
|
|
720
557
|
while (true) {
|
|
721
558
|
const { done, value } = await reader.read();
|
|
722
559
|
if (done) break;
|
|
723
560
|
chunks.push(value);
|
|
561
|
+
totalLength += value.length;
|
|
724
562
|
}
|
|
725
563
|
} finally {
|
|
726
564
|
reader.releaseLock();
|
|
727
565
|
}
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
let offset = 0;
|
|
731
|
-
for (const chunk of chunks) {
|
|
732
|
-
buffer.set(chunk, offset);
|
|
733
|
-
offset += chunk.length;
|
|
734
|
-
}
|
|
735
|
-
return JSON.parse(Buffer.from(buffer).toString("utf8"));
|
|
566
|
+
const buffer = Buffer.concat(chunks, totalLength);
|
|
567
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
736
568
|
}
|
|
737
569
|
};
|
|
738
570
|
var BufferTransport = class {
|
|
@@ -1014,8 +846,7 @@ var Topic = class {
|
|
|
1014
846
|
queueName: this.topicName,
|
|
1015
847
|
payload,
|
|
1016
848
|
idempotencyKey: options?.idempotencyKey,
|
|
1017
|
-
retentionSeconds: options?.retentionSeconds
|
|
1018
|
-
callback: options?.callback
|
|
849
|
+
retentionSeconds: options?.retentionSeconds
|
|
1019
850
|
},
|
|
1020
851
|
this.transport
|
|
1021
852
|
);
|
|
@@ -1066,8 +897,7 @@ async function send(topicName, payload, options) {
|
|
|
1066
897
|
queueName: topicName,
|
|
1067
898
|
payload,
|
|
1068
899
|
idempotencyKey: options?.idempotencyKey,
|
|
1069
|
-
retentionSeconds: options?.retentionSeconds
|
|
1070
|
-
callback: options?.callback
|
|
900
|
+
retentionSeconds: options?.retentionSeconds
|
|
1071
901
|
},
|
|
1072
902
|
transport
|
|
1073
903
|
);
|
|
@@ -1094,23 +924,22 @@ async function receive(topicName, consumerGroup, handler, options) {
|
|
|
1094
924
|
|
|
1095
925
|
// src/callback.ts
|
|
1096
926
|
function parseCallbackRequest(request) {
|
|
1097
|
-
const
|
|
1098
|
-
const
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
`Missing required queue callback headers: ${missingHeaders.join(", ")}`
|
|
927
|
+
const queueName = request.headers.get("Vqs-Queue-Name");
|
|
928
|
+
const consumerGroup = request.headers.get("Vqs-Consumer-Group");
|
|
929
|
+
const messageId = request.headers.get("Vqs-Message-Id");
|
|
930
|
+
if (!queueName || !consumerGroup || !messageId) {
|
|
931
|
+
const missingHeaders = [];
|
|
932
|
+
if (!queueName) missingHeaders.push("Vqs-Queue-Name");
|
|
933
|
+
if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
|
|
934
|
+
if (!messageId) missingHeaders.push("Vqs-Message-Id");
|
|
935
|
+
throw new Error(
|
|
936
|
+
`Missing required queue headers: ${missingHeaders.join(", ")}`
|
|
1108
937
|
);
|
|
1109
938
|
}
|
|
1110
939
|
return {
|
|
1111
|
-
messageId,
|
|
1112
940
|
queueName,
|
|
1113
|
-
consumerGroup
|
|
941
|
+
consumerGroup,
|
|
942
|
+
messageId
|
|
1114
943
|
};
|
|
1115
944
|
}
|
|
1116
945
|
function handleCallback(handlers) {
|
|
@@ -1119,41 +948,38 @@ function handleCallback(handlers) {
|
|
|
1119
948
|
const { queueName, consumerGroup, messageId } = parseCallbackRequest(request);
|
|
1120
949
|
const topicHandler = handlers[queueName];
|
|
1121
950
|
if (!topicHandler) {
|
|
1122
|
-
|
|
951
|
+
const availableTopics = Object.keys(handlers).join(", ");
|
|
952
|
+
return Response.json(
|
|
953
|
+
{
|
|
954
|
+
error: `No handler found for topic: ${queueName}`,
|
|
955
|
+
availableTopics
|
|
956
|
+
},
|
|
957
|
+
{ status: 404 }
|
|
958
|
+
);
|
|
1123
959
|
}
|
|
1124
|
-
|
|
1125
|
-
if (
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if (!consumerGroupHandler) {
|
|
1135
|
-
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
1136
|
-
throw new Error(
|
|
1137
|
-
`No handler found for consumer group "${consumerGroup}" in topic "${queueName}". Available groups: ${availableGroups}`
|
|
1138
|
-
);
|
|
1139
|
-
}
|
|
1140
|
-
actualHandler = consumerGroupHandler;
|
|
960
|
+
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
961
|
+
if (!consumerGroupHandler) {
|
|
962
|
+
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
963
|
+
return Response.json(
|
|
964
|
+
{
|
|
965
|
+
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
966
|
+
availableGroups
|
|
967
|
+
},
|
|
968
|
+
{ status: 404 }
|
|
969
|
+
);
|
|
1141
970
|
}
|
|
1142
971
|
const client = new QueueClient();
|
|
1143
972
|
const topic = new Topic(client, queueName);
|
|
1144
973
|
const cg = topic.consumerGroup(consumerGroup);
|
|
1145
|
-
await cg.consume(
|
|
974
|
+
await cg.consume(consumerGroupHandler, { messageId });
|
|
1146
975
|
return Response.json({ status: "success" });
|
|
1147
976
|
} catch (error) {
|
|
1148
|
-
console.error("
|
|
1149
|
-
if (error instanceof
|
|
1150
|
-
return Response.json(
|
|
1151
|
-
{ error: "Invalid callback request" },
|
|
1152
|
-
{ status: 400 }
|
|
1153
|
-
);
|
|
977
|
+
console.error("Queue callback error:", error);
|
|
978
|
+
if (error instanceof Error && error.message.includes("Missing required queue headers")) {
|
|
979
|
+
return Response.json({ error: error.message }, { status: 400 });
|
|
1154
980
|
}
|
|
1155
981
|
return Response.json(
|
|
1156
|
-
{ error: "Failed to process
|
|
982
|
+
{ error: "Failed to process queue message" },
|
|
1157
983
|
{ status: 500 }
|
|
1158
984
|
);
|
|
1159
985
|
}
|
|
@@ -1163,11 +989,8 @@ export {
|
|
|
1163
989
|
BadRequestError,
|
|
1164
990
|
BufferTransport,
|
|
1165
991
|
ConsumerGroup,
|
|
1166
|
-
FailedDependencyError,
|
|
1167
|
-
FifoOrderingViolationError,
|
|
1168
992
|
ForbiddenError,
|
|
1169
993
|
InternalServerError,
|
|
1170
|
-
InvalidCallbackError,
|
|
1171
994
|
InvalidLimitError,
|
|
1172
995
|
JsonTransport,
|
|
1173
996
|
MessageCorruptedError,
|