@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/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, callback } = options;
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 FIFO queue has locked messages (423)
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 in FIFO queue", retryAfter);
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 totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
729
- const buffer = new Uint8Array(totalLength);
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 headers = request.headers;
1098
- const messageId = headers.get("Vqs-Message-Id");
1099
- const queueName = headers.get("Vqs-Queue-Name");
1100
- const consumerGroup = headers.get("Vqs-Consumer-Group");
1101
- const missingHeaders = [];
1102
- if (!messageId) missingHeaders.push("Vqs-Message-Id");
1103
- if (!queueName) missingHeaders.push("Vqs-Queue-Name");
1104
- if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
1105
- if (missingHeaders.length > 0) {
1106
- throw new InvalidCallbackError(
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
- throw new Error(`No handler found for topic: ${queueName}`);
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
- let actualHandler;
1125
- if (typeof topicHandler === "function") {
1126
- if (consumerGroup !== "default") {
1127
- throw new Error(
1128
- `Topic "${queueName}" has a single handler but received consumer group "${consumerGroup}". Expected "default".`
1129
- );
1130
- }
1131
- actualHandler = topicHandler;
1132
- } else {
1133
- const consumerGroupHandler = topicHandler[consumerGroup];
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(actualHandler, { messageId });
974
+ await cg.consume(consumerGroupHandler, { messageId });
1146
975
  return Response.json({ status: "success" });
1147
976
  } catch (error) {
1148
- console.error("Callback error:", error);
1149
- if (error instanceof InvalidCallbackError) {
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 callback" },
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,