@vercel/queue 0.0.0-alpha.4 → 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 +47 -224
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +47 -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,46 +211,21 @@ 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,
|
|
351
218
|
"Content-Type": transport.contentType
|
|
352
219
|
});
|
|
220
|
+
if (process.env.VERCEL_DEPLOYMENT_ID) {
|
|
221
|
+
headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
|
|
222
|
+
}
|
|
353
223
|
if (idempotencyKey) {
|
|
354
224
|
headers.set("Vqs-Idempotency-Key", idempotencyKey);
|
|
355
225
|
}
|
|
356
226
|
if (retentionSeconds !== void 0) {
|
|
357
227
|
headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
|
|
358
228
|
}
|
|
359
|
-
let normalizedCallbacks;
|
|
360
|
-
if (callback) {
|
|
361
|
-
if ("url" in callback && typeof callback.url === "string") {
|
|
362
|
-
normalizedCallbacks = { default: callback };
|
|
363
|
-
} else {
|
|
364
|
-
normalizedCallbacks = callback;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
let localhostCallbacks = [];
|
|
368
|
-
if (normalizedCallbacks) {
|
|
369
|
-
const isDevelopment = process.env.NODE_ENV === "development";
|
|
370
|
-
if (isDevelopment) {
|
|
371
|
-
localhostCallbacks = processDevelopmentCallbacks(normalizedCallbacks);
|
|
372
|
-
} else {
|
|
373
|
-
const endpoints = Object.entries(normalizedCallbacks).map(
|
|
374
|
-
([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
|
|
375
|
-
).join(",");
|
|
376
|
-
headers.set("Vqs-Callback-Url", endpoints);
|
|
377
|
-
const delays = Object.entries(normalizedCallbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
|
|
378
|
-
if (delays) {
|
|
379
|
-
headers.set("Vqs-Callback-Delay", delays);
|
|
380
|
-
}
|
|
381
|
-
const frequencies = Object.entries(normalizedCallbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
|
|
382
|
-
if (frequencies) {
|
|
383
|
-
headers.set("Vqs-Callback-Frequency", frequencies);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
229
|
const body = transport.serialize(payload);
|
|
388
230
|
const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
|
|
389
231
|
method: "POST",
|
|
@@ -414,9 +256,6 @@ var QueueClient = class _QueueClient {
|
|
|
414
256
|
);
|
|
415
257
|
}
|
|
416
258
|
const responseData = await response.json();
|
|
417
|
-
if (localhostCallbacks.length > 0) {
|
|
418
|
-
fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
|
|
419
|
-
}
|
|
420
259
|
return responseData;
|
|
421
260
|
}
|
|
422
261
|
/**
|
|
@@ -426,7 +265,7 @@ var QueueClient = class _QueueClient {
|
|
|
426
265
|
* @returns AsyncGenerator that yields messages as they arrive
|
|
427
266
|
* @throws {InvalidLimitError} When limit parameter is not between 1 and 10
|
|
428
267
|
* @throws {QueueEmptyError} When no messages are available (204)
|
|
429
|
-
* @throws {MessageLockedError} When
|
|
268
|
+
* @throws {MessageLockedError} When messages are temporarily locked (423)
|
|
430
269
|
* @throws {BadRequestError} When request parameters are invalid
|
|
431
270
|
* @throws {UnauthorizedError} When authentication fails
|
|
432
271
|
* @throws {ForbiddenError} When access is denied (environment mismatch)
|
|
@@ -477,7 +316,7 @@ var QueueClient = class _QueueClient {
|
|
|
477
316
|
const parsed = parseInt(retryAfterHeader, 10);
|
|
478
317
|
retryAfter = isNaN(parsed) ? void 0 : parsed;
|
|
479
318
|
}
|
|
480
|
-
throw new MessageLockedError("next message
|
|
319
|
+
throw new MessageLockedError("next message", retryAfter);
|
|
481
320
|
}
|
|
482
321
|
if (response.status >= 500) {
|
|
483
322
|
throw new InternalServerError(
|
|
@@ -563,9 +402,6 @@ var QueueClient = class _QueueClient {
|
|
|
563
402
|
}
|
|
564
403
|
throw new MessageLockedError(messageId, retryAfter);
|
|
565
404
|
}
|
|
566
|
-
if (response.status === 424) {
|
|
567
|
-
throw new FailedDependencyError(messageId);
|
|
568
|
-
}
|
|
569
405
|
if (response.status === 409) {
|
|
570
406
|
throw new MessageNotAvailableError(messageId);
|
|
571
407
|
}
|
|
@@ -761,24 +597,20 @@ var JsonTransport = class {
|
|
|
761
597
|
}
|
|
762
598
|
async deserialize(stream) {
|
|
763
599
|
const reader = stream.getReader();
|
|
600
|
+
let totalLength = 0;
|
|
764
601
|
const chunks = [];
|
|
765
602
|
try {
|
|
766
603
|
while (true) {
|
|
767
604
|
const { done, value } = await reader.read();
|
|
768
605
|
if (done) break;
|
|
769
606
|
chunks.push(value);
|
|
607
|
+
totalLength += value.length;
|
|
770
608
|
}
|
|
771
609
|
} finally {
|
|
772
610
|
reader.releaseLock();
|
|
773
611
|
}
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
let offset = 0;
|
|
777
|
-
for (const chunk of chunks) {
|
|
778
|
-
buffer.set(chunk, offset);
|
|
779
|
-
offset += chunk.length;
|
|
780
|
-
}
|
|
781
|
-
return JSON.parse(Buffer.from(buffer).toString("utf8"));
|
|
612
|
+
const buffer = Buffer.concat(chunks, totalLength);
|
|
613
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
782
614
|
}
|
|
783
615
|
};
|
|
784
616
|
var BufferTransport = class {
|
|
@@ -1060,8 +892,7 @@ var Topic = class {
|
|
|
1060
892
|
queueName: this.topicName,
|
|
1061
893
|
payload,
|
|
1062
894
|
idempotencyKey: options?.idempotencyKey,
|
|
1063
|
-
retentionSeconds: options?.retentionSeconds
|
|
1064
|
-
callback: options?.callback
|
|
895
|
+
retentionSeconds: options?.retentionSeconds
|
|
1065
896
|
},
|
|
1066
897
|
this.transport
|
|
1067
898
|
);
|
|
@@ -1112,8 +943,7 @@ async function send(topicName, payload, options) {
|
|
|
1112
943
|
queueName: topicName,
|
|
1113
944
|
payload,
|
|
1114
945
|
idempotencyKey: options?.idempotencyKey,
|
|
1115
|
-
retentionSeconds: options?.retentionSeconds
|
|
1116
|
-
callback: options?.callback
|
|
946
|
+
retentionSeconds: options?.retentionSeconds
|
|
1117
947
|
},
|
|
1118
948
|
transport
|
|
1119
949
|
);
|
|
@@ -1140,23 +970,22 @@ async function receive(topicName, consumerGroup, handler, options) {
|
|
|
1140
970
|
|
|
1141
971
|
// src/callback.ts
|
|
1142
972
|
function parseCallbackRequest(request) {
|
|
1143
|
-
const
|
|
1144
|
-
const
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
`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(", ")}`
|
|
1154
983
|
);
|
|
1155
984
|
}
|
|
1156
985
|
return {
|
|
1157
|
-
messageId,
|
|
1158
986
|
queueName,
|
|
1159
|
-
consumerGroup
|
|
987
|
+
consumerGroup,
|
|
988
|
+
messageId
|
|
1160
989
|
};
|
|
1161
990
|
}
|
|
1162
991
|
function handleCallback(handlers) {
|
|
@@ -1165,41 +994,38 @@ function handleCallback(handlers) {
|
|
|
1165
994
|
const { queueName, consumerGroup, messageId } = parseCallbackRequest(request);
|
|
1166
995
|
const topicHandler = handlers[queueName];
|
|
1167
996
|
if (!topicHandler) {
|
|
1168
|
-
|
|
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
|
+
);
|
|
1169
1005
|
}
|
|
1170
|
-
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
if (!consumerGroupHandler) {
|
|
1181
|
-
const availableGroups = Object.keys(topicHandler).join(", ");
|
|
1182
|
-
throw new Error(
|
|
1183
|
-
`No handler found for consumer group "${consumerGroup}" in topic "${queueName}". Available groups: ${availableGroups}`
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
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
|
+
);
|
|
1187
1016
|
}
|
|
1188
1017
|
const client = new QueueClient();
|
|
1189
1018
|
const topic = new Topic(client, queueName);
|
|
1190
1019
|
const cg = topic.consumerGroup(consumerGroup);
|
|
1191
|
-
await cg.consume(
|
|
1020
|
+
await cg.consume(consumerGroupHandler, { messageId });
|
|
1192
1021
|
return Response.json({ status: "success" });
|
|
1193
1022
|
} catch (error) {
|
|
1194
|
-
console.error("
|
|
1195
|
-
if (error instanceof
|
|
1196
|
-
return Response.json(
|
|
1197
|
-
{ error: "Invalid callback request" },
|
|
1198
|
-
{ status: 400 }
|
|
1199
|
-
);
|
|
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 });
|
|
1200
1026
|
}
|
|
1201
1027
|
return Response.json(
|
|
1202
|
-
{ error: "Failed to process
|
|
1028
|
+
{ error: "Failed to process queue message" },
|
|
1203
1029
|
{ status: 500 }
|
|
1204
1030
|
);
|
|
1205
1031
|
}
|
|
@@ -1210,11 +1036,8 @@ function handleCallback(handlers) {
|
|
|
1210
1036
|
BadRequestError,
|
|
1211
1037
|
BufferTransport,
|
|
1212
1038
|
ConsumerGroup,
|
|
1213
|
-
FailedDependencyError,
|
|
1214
|
-
FifoOrderingViolationError,
|
|
1215
1039
|
ForbiddenError,
|
|
1216
1040
|
InternalServerError,
|
|
1217
|
-
InvalidCallbackError,
|
|
1218
1041
|
InvalidLimitError,
|
|
1219
1042
|
JsonTransport,
|
|
1220
1043
|
MessageCorruptedError,
|