@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.js
CHANGED
|
@@ -23,11 +23,8 @@ __export(index_exports, {
|
|
|
23
23
|
BadRequestError: () => BadRequestError,
|
|
24
24
|
BufferTransport: () => BufferTransport,
|
|
25
25
|
ConsumerGroup: () => ConsumerGroup,
|
|
26
|
-
FailedDependencyError: () => FailedDependencyError,
|
|
27
|
-
FifoOrderingViolationError: () => FifoOrderingViolationError,
|
|
28
26
|
ForbiddenError: () => ForbiddenError,
|
|
29
27
|
InternalServerError: () => InternalServerError,
|
|
30
|
-
InvalidCallbackError: () => InvalidCallbackError,
|
|
31
28
|
InvalidLimitError: () => InvalidLimitError,
|
|
32
29
|
JsonTransport: () => JsonTransport,
|
|
33
30
|
MessageCorruptedError: () => MessageCorruptedError,
|
|
@@ -50,116 +47,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
50
47
|
// src/client.ts
|
|
51
48
|
var import_mixpart = require("mixpart");
|
|
52
49
|
|
|
53
|
-
// src/local.ts
|
|
54
|
-
var import_node_child_process = require("child_process");
|
|
55
|
-
function isLocalhostWithPort(url) {
|
|
56
|
-
try {
|
|
57
|
-
const parsedUrl = new URL(url);
|
|
58
|
-
const isLocalhost = parsedUrl.hostname === "localhost";
|
|
59
|
-
const port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 0;
|
|
60
|
-
return { isLocalhost, port };
|
|
61
|
-
} catch {
|
|
62
|
-
return { isLocalhost: false };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function isSupportedPlatform() {
|
|
66
|
-
const platform = process.platform;
|
|
67
|
-
return platform === "darwin" || platform === "linux";
|
|
68
|
-
}
|
|
69
|
-
function processDevelopmentCallbacks(callbacks) {
|
|
70
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
71
|
-
if (!isDevelopment) {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
if (!isSupportedPlatform()) {
|
|
75
|
-
const hasLocalhostCallbacks = Object.values(callbacks).some((config) => {
|
|
76
|
-
const { isLocalhost } = isLocalhostWithPort(config.url);
|
|
77
|
-
return isLocalhost;
|
|
78
|
-
});
|
|
79
|
-
if (hasLocalhostCallbacks) {
|
|
80
|
-
console.warn(
|
|
81
|
-
`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.`
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
const localhostCallbacks = [];
|
|
87
|
-
Object.entries(callbacks).forEach(([group, config]) => {
|
|
88
|
-
const { isLocalhost, port } = isLocalhostWithPort(config.url);
|
|
89
|
-
if (isLocalhost && port && port > 0) {
|
|
90
|
-
localhostCallbacks.push({ group, config, port });
|
|
91
|
-
} else {
|
|
92
|
-
console.warn(
|
|
93
|
-
`Queue Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
return localhostCallbacks;
|
|
98
|
-
}
|
|
99
|
-
function fireLocalhostCallbacks(localhostCallbacks, queueName, responseData) {
|
|
100
|
-
localhostCallbacks.forEach(({ group, config, port }) => {
|
|
101
|
-
const callbackHeaders = new Headers();
|
|
102
|
-
callbackHeaders.set("Vqs-Message-Id", responseData.messageId);
|
|
103
|
-
callbackHeaders.set("Vqs-Queue-Name", queueName);
|
|
104
|
-
callbackHeaders.set("Vqs-Consumer-Group", group);
|
|
105
|
-
fireAndForgetWaitForHttpReady(
|
|
106
|
-
config.url,
|
|
107
|
-
port,
|
|
108
|
-
config.delay || 0,
|
|
109
|
-
3,
|
|
110
|
-
// Default retry frequency
|
|
111
|
-
callbackHeaders
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
function fireAndForgetWaitForHttpReady(url, port, initialDelaySeconds = 0, retryFrequencySeconds = 3, headers) {
|
|
116
|
-
if (!isSupportedPlatform()) {
|
|
117
|
-
console.warn(
|
|
118
|
-
`Queue: fireAndForgetWaitForHttpReady is not supported on ${process.platform}. This function requires bash, nc, and curl which are available on macOS and Linux only.`
|
|
119
|
-
);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
let headerArgs = "";
|
|
123
|
-
if (headers) {
|
|
124
|
-
const headerArray = [];
|
|
125
|
-
headers.forEach((value, key) => {
|
|
126
|
-
headerArray.push(`-H '${key}: ${value}'`);
|
|
127
|
-
});
|
|
128
|
-
headerArgs = headerArray.join(" ");
|
|
129
|
-
}
|
|
130
|
-
const bashScript = `
|
|
131
|
-
# Wait for any initial boot time
|
|
132
|
-
sleep ${initialDelaySeconds}
|
|
133
|
-
|
|
134
|
-
missed=0
|
|
135
|
-
while true; do
|
|
136
|
-
# 1) Check if TCP port is listening
|
|
137
|
-
if nc -z localhost ${port} 2>/dev/null; then
|
|
138
|
-
missed=0
|
|
139
|
-
# 2) If port is open, try HTTP POST check
|
|
140
|
-
if curl -sSL --fail -o /dev/null -X POST ${headerArgs} "${url}"; then
|
|
141
|
-
# Success: port is up AND HTTP returned 2xx (following redirects)
|
|
142
|
-
exit 0
|
|
143
|
-
fi
|
|
144
|
-
else
|
|
145
|
-
# Port was closed\u2014increment miss counter
|
|
146
|
-
((missed+=1))
|
|
147
|
-
# If closed twice in a row, give up immediately
|
|
148
|
-
if [ "$missed" -ge 2 ]; then
|
|
149
|
-
exit 1
|
|
150
|
-
fi
|
|
151
|
-
fi
|
|
152
|
-
# Wait before next cycle
|
|
153
|
-
sleep ${retryFrequencySeconds}
|
|
154
|
-
done
|
|
155
|
-
`;
|
|
156
|
-
const childProcess = (0, import_node_child_process.spawn)("bash", ["-c", bashScript], {
|
|
157
|
-
stdio: "ignore",
|
|
158
|
-
detached: true
|
|
159
|
-
});
|
|
160
|
-
childProcess.unref();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
50
|
// src/types.ts
|
|
164
51
|
var MessageNotFoundError = class extends Error {
|
|
165
52
|
constructor(messageId) {
|
|
@@ -175,12 +62,6 @@ var MessageNotAvailableError = class extends Error {
|
|
|
175
62
|
this.name = "MessageNotAvailableError";
|
|
176
63
|
}
|
|
177
64
|
};
|
|
178
|
-
var FifoOrderingViolationError = class extends Error {
|
|
179
|
-
constructor(messageId, reason) {
|
|
180
|
-
super(`FIFO ordering violation for message ${messageId}: ${reason}`);
|
|
181
|
-
this.name = "FifoOrderingViolationError";
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
65
|
var MessageCorruptedError = class extends Error {
|
|
185
66
|
constructor(messageId, reason) {
|
|
186
67
|
super(`Message ${messageId} is corrupted: ${reason}`);
|
|
@@ -222,14 +103,6 @@ var BadRequestError = class extends Error {
|
|
|
222
103
|
this.name = "BadRequestError";
|
|
223
104
|
}
|
|
224
105
|
};
|
|
225
|
-
var FailedDependencyError = class extends Error {
|
|
226
|
-
constructor(messageId) {
|
|
227
|
-
super(
|
|
228
|
-
`Failed dependency: FIFO ordering violation for message ${messageId}`
|
|
229
|
-
);
|
|
230
|
-
this.name = "FailedDependencyError";
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
106
|
var InternalServerError = class extends Error {
|
|
234
107
|
constructor(message = "Unexpected server error") {
|
|
235
108
|
super(message);
|
|
@@ -242,12 +115,6 @@ var InvalidLimitError = class extends Error {
|
|
|
242
115
|
this.name = "InvalidLimitError";
|
|
243
116
|
}
|
|
244
117
|
};
|
|
245
|
-
var InvalidCallbackError = class extends Error {
|
|
246
|
-
constructor(message) {
|
|
247
|
-
super(message);
|
|
248
|
-
this.name = "InvalidCallbackError";
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
118
|
|
|
252
119
|
// src/client.ts
|
|
253
120
|
async function consumeStream(stream) {
|
|
@@ -344,7 +211,7 @@ var QueueClient = class _QueueClient {
|
|
|
344
211
|
* @throws {InternalServerError} When server encounters an error
|
|
345
212
|
*/
|
|
346
213
|
async sendMessage(options, transport) {
|
|
347
|
-
const { queueName, payload, idempotencyKey, retentionSeconds
|
|
214
|
+
const { queueName, payload, idempotencyKey, retentionSeconds } = options;
|
|
348
215
|
const headers = new Headers({
|
|
349
216
|
Authorization: `Bearer ${this.token}`,
|
|
350
217
|
"Vqs-Queue-Name": queueName,
|
|
@@ -359,34 +226,6 @@ var QueueClient = class _QueueClient {
|
|
|
359
226
|
if (retentionSeconds !== void 0) {
|
|
360
227
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
361
228
|
}
|
|
362
|
-
let normalizedCallbacks;
|
|
363
|
-
if (callback) {
|
|
364
|
-
if ("url" in callback && typeof callback.url === "string") {
|
|
365
|
-
normalizedCallbacks = { default: callback };
|
|
366
|
-
} else {
|
|
367
|
-
normalizedCallbacks = callback;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
let localhostCallbacks = [];
|
|
371
|
-
if (normalizedCallbacks) {
|
|
372
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
373
|
-
if (isDevelopment) {
|
|
374
|
-
localhostCallbacks = processDevelopmentCallbacks(normalizedCallbacks);
|
|
375
|
-
} else {
|
|
376
|
-
const endpoints = Object.entries(normalizedCallbacks).map(
|
|
377
|
-
([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
|
|
378
|
-
).join(",");
|
|
379
|
-
headers.set("Vqs-Callback-Url", endpoints);
|
|
380
|
-
const delays = Object.entries(normalizedCallbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
|
|
381
|
-
if (delays) {
|
|
382
|
-
headers.set("Vqs-Callback-Delay", delays);
|
|
383
|
-
}
|
|
384
|
-
const frequencies = Object.entries(normalizedCallbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
|
|
385
|
-
if (frequencies) {
|
|
386
|
-
headers.set("Vqs-Callback-Frequency", frequencies);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
229
|
const body = transport.serialize(payload);
|
|
391
230
|
const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
|
|
392
231
|
method: "POST",
|
|
@@ -417,9 +256,6 @@ var QueueClient = class _QueueClient {
|
|
|
417
256
|
);
|
|
418
257
|
}
|
|
419
258
|
const responseData = await response.json();
|
|
420
|
-
if (localhostCallbacks.length > 0) {
|
|
421
|
-
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
422
|
-
}
|
|
423
259
|
return responseData;
|
|
424
260
|
}
|
|
425
261
|
/**
|
|
@@ -429,7 +265,7 @@ var QueueClient = class _QueueClient {
|
|
|
429
265
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
430
266
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
431
267
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
432
|
-
* @throws {MessageLockedError} When
|
|
268
|
+
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
433
269
|
* @throws {BadRequestError} When request parameters are invalid
|
|
434
270
|
* @throws {UnauthorizedError} When authentication fails
|
|
435
271
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -480,7 +316,7 @@ var QueueClient = class _QueueClient {
|
|
|
480
316
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
481
317
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
482
318
|
}
|
|
483
|
-
throw new MessageLockedError("next message
|
|
319
|
+
throw new MessageLockedError("next message", retryAfter);
|
|
484
320
|
}
|
|
485
321
|
if (response.status >= 500) {
|
|
486
322
|
throw new InternalServerError(
|
|
@@ -566,9 +402,6 @@ var QueueClient = class _QueueClient {
|
|
|
566
402
|
}
|
|
567
403
|
throw new MessageLockedError(messageId, retryAfter);
|
|
568
404
|
}
|
|
569
|
-
if (response.status === 424) {
|
|
570
|
-
throw new FailedDependencyError(messageId);
|
|
571
|
-
}
|
|
572
405
|
if (response.status === 409) {
|
|
573
406
|
throw new MessageNotAvailableError(messageId);
|
|
574
407
|
}
|
|
@@ -764,24 +597,20 @@ var JsonTransport = class {
|
|
|
764
597
|
}
|
|
765
598
|
async deserialize(stream) {
|
|
766
599
|
const reader = stream.getReader();
|
|
600
|
+
let totalLength = 0;
|
|
767
601
|
const chunks = [];
|
|
768
602
|
try {
|
|
769
603
|
while (true) {
|
|
770
604
|
const { done, value } = await reader.read();
|
|
771
605
|
if (done) break;
|
|
772
606
|
chunks.push(value);
|
|
607
|
+
totalLength += value.length;
|
|
773
608
|
}
|
|
774
609
|
} finally {
|
|
775
610
|
reader.releaseLock();
|
|
776
611
|
}
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
let offset = 0;
|
|
780
|
-
for (const chunk of chunks) {
|
|
781
|
-
buffer.set(chunk, offset);
|
|
782
|
-
offset += chunk.length;
|
|
783
|
-
}
|
|
784
|
-
return JSON.parse(Buffer.from(buffer).toString("utf8"));
|
|
612
|
+
const buffer = Buffer.concat(chunks, totalLength);
|
|
613
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
785
614
|
}
|
|
786
615
|
};
|
|
787
616
|
var BufferTransport = class {
|
|
@@ -1063,8 +892,7 @@ var Topic = class {
|
|
|
1063
892
|
queueName: this.topicName,
|
|
1064
893
|
payload,
|
|
1065
894
|
idempotencyKey: options?.idempotencyKey,
|
|
1066
|
-
retentionSeconds: options?.retentionSeconds
|
|
1067
|
-
callback: options?.callback
|
|
895
|
+
retentionSeconds: options?.retentionSeconds
|
|
1068
896
|
},
|
|
1069
897
|
this.transport
|
|
1070
898
|
);
|
|
@@ -1115,8 +943,7 @@ async function send(topicName, payload, options) {
|
|
|
1115
943
|
queueName: topicName,
|
|
1116
944
|
payload,
|
|
1117
945
|
idempotencyKey: options?.idempotencyKey,
|
|
1118
|
-
retentionSeconds: options?.retentionSeconds
|
|
1119
|
-
callback: options?.callback
|
|
946
|
+
retentionSeconds: options?.retentionSeconds
|
|
1120
947
|
},
|
|
1121
948
|
transport
|
|
1122
949
|
);
|
|
@@ -1143,23 +970,22 @@ async function receive(topicName, consumerGroup, handler, options) {
|
|
|
1143
970
|
|
|
1144
971
|
// src/callback.ts
|
|
1145
972
|
function parseCallbackRequest(request) {
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
`Missing required queue callback headers: ${missingHeaders.join(", ")}`
|
|
973
|
+
const queueName = request.headers.get("Vqs-Queue-Name");
|
|
974
|
+
const consumerGroup = request.headers.get("Vqs-Consumer-Group");
|
|
975
|
+
const messageId = request.headers.get("Vqs-Message-Id");
|
|
976
|
+
if (!queueName || !consumerGroup || !messageId) {
|
|
977
|
+
const missingHeaders = [];
|
|
978
|
+
if (!queueName) missingHeaders.push("Vqs-Queue-Name");
|
|
979
|
+
if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
|
|
980
|
+
if (!messageId) missingHeaders.push("Vqs-Message-Id");
|
|
981
|
+
throw new Error(
|
|
982
|
+
`Missing required queue headers: ${missingHeaders.join(", ")}`
|
|
1157
983
|
);
|
|
1158
984
|
}
|
|
1159
985
|
return {
|
|
1160
|
-
messageId,
|
|
1161
986
|
queueName,
|
|
1162
|
-
consumerGroup
|
|
987
|
+
consumerGroup,
|
|
988
|
+
messageId
|
|
1163
989
|
};
|
|
1164
990
|
}
|
|
1165
991
|
function handleCallback(handlers) {
|
|
@@ -1168,41 +994,38 @@ function handleCallback(handlers) {
|
|
|
1168
994
|
const { queueName, consumerGroup, messageId } = parseCallbackRequest(request);
|
|
1169
995
|
const topicHandler = handlers[queueName];
|
|
1170
996
|
if (!topicHandler) {
|
|
1171
|
-
|
|
997
|
+
const availableTopics = Object.keys(handlers).join(", ");
|
|
998
|
+
return Response.json(
|
|
999
|
+
{
|
|
1000
|
+
error: `No handler found for topic: ${queueName}`,
|
|
1001
|
+
availableTopics
|
|
1002
|
+
},
|
|
1003
|
+
{ status: 404 }
|
|
1004
|
+
);
|
|
1172
1005
|
}
|
|
1173
|
-
|
|
1174
|
-
if (
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
if (!consumerGroupHandler) {
|
|
1184
|
-
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
1185
|
-
throw new Error(
|
|
1186
|
-
`No handler found for consumer group "${consumerGroup}" in topic "${queueName}". Available groups: ${availableGroups}`
|
|
1187
|
-
);
|
|
1188
|
-
}
|
|
1189
|
-
actualHandler = consumerGroupHandler;
|
|
1006
|
+
const consumerGroupHandler = topicHandler[consumerGroup];
|
|
1007
|
+
if (!consumerGroupHandler) {
|
|
1008
|
+
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
1009
|
+
return Response.json(
|
|
1010
|
+
{
|
|
1011
|
+
error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
|
|
1012
|
+
availableGroups
|
|
1013
|
+
},
|
|
1014
|
+
{ status: 404 }
|
|
1015
|
+
);
|
|
1190
1016
|
}
|
|
1191
1017
|
const client = new QueueClient();
|
|
1192
1018
|
const topic = new Topic(client, queueName);
|
|
1193
1019
|
const cg = topic.consumerGroup(consumerGroup);
|
|
1194
|
-
await cg.consume(
|
|
1020
|
+
await cg.consume(consumerGroupHandler, { messageId });
|
|
1195
1021
|
return Response.json({ status: "success" });
|
|
1196
1022
|
} catch (error) {
|
|
1197
|
-
console.error("
|
|
1198
|
-
if (error instanceof
|
|
1199
|
-
return Response.json(
|
|
1200
|
-
{ error: "Invalid callback request" },
|
|
1201
|
-
{ status: 400 }
|
|
1202
|
-
);
|
|
1023
|
+
console.error("Queue callback error:", error);
|
|
1024
|
+
if (error instanceof Error && error.message.includes("Missing required queue headers")) {
|
|
1025
|
+
return Response.json({ error: error.message }, { status: 400 });
|
|
1203
1026
|
}
|
|
1204
1027
|
return Response.json(
|
|
1205
|
-
{ error: "Failed to process
|
|
1028
|
+
{ error: "Failed to process queue message" },
|
|
1206
1029
|
{ status: 500 }
|
|
1207
1030
|
);
|
|
1208
1031
|
}
|
|
@@ -1213,11 +1036,8 @@ function handleCallback(handlers) {
|
|
|
1213
1036
|
BadRequestError,
|
|
1214
1037
|
BufferTransport,
|
|
1215
1038
|
ConsumerGroup,
|
|
1216
|
-
FailedDependencyError,
|
|
1217
|
-
FifoOrderingViolationError,
|
|
1218
1039
|
ForbiddenError,
|
|
1219
1040
|
InternalServerError,
|
|
1220
|
-
InvalidCallbackError,
|
|
1221
1041
|
InvalidLimitError,
|
|
1222
1042
|
JsonTransport,
|
|
1223
1043
|
MessageCorruptedError,
|