@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/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,46 +165,21 @@ 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,
302
172
  "Content-Type": transport.contentType
303
173
  });
174
+ if (process.env.VERCEL_DEPLOYMENT_ID) {
175
+ headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
176
+ }
304
177
  if (idempotencyKey) {
305
178
  headers.set("Vqs-Idempotency-Key", idempotencyKey);
306
179
  }
307
180
  if (retentionSeconds !== void 0) {
308
181
  headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
309
182
  }
310
- let normalizedCallbacks;
311
- if (callback) {
312
- if ("url" in callback && typeof callback.url === "string") {
313
- normalizedCallbacks = { default: callback };
314
- } else {
315
- normalizedCallbacks = callback;
316
- }
317
- }
318
- let localhostCallbacks = [];
319
- if (normalizedCallbacks) {
320
- const isDevelopment = process.env.NODE_ENV === "development";
321
- if (isDevelopment) {
322
- localhostCallbacks = processDevelopmentCallbacks(normalizedCallbacks);
323
- } else {
324
- const endpoints = Object.entries(normalizedCallbacks).map(
325
- ([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
326
- ).join(",");
327
- headers.set("Vqs-Callback-Url", endpoints);
328
- const delays = Object.entries(normalizedCallbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
329
- if (delays) {
330
- headers.set("Vqs-Callback-Delay", delays);
331
- }
332
- const frequencies = Object.entries(normalizedCallbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
333
- if (frequencies) {
334
- headers.set("Vqs-Callback-Frequency", frequencies);
335
- }
336
- }
337
- }
338
183
  const body = transport.serialize(payload);
339
184
  const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
340
185
  method: "POST",
@@ -365,9 +210,6 @@ var QueueClient = class _QueueClient {
365
210
  );
366
211
  }
367
212
  const responseData = await response.json();
368
- if (localhostCallbacks.length > 0) {
369
- fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
370
- }
371
213
  return responseData;
372
214
  }
373
215
  /**
@@ -377,7 +219,7 @@ var QueueClient = class _QueueClient {
377
219
  * @returns AsyncGenerator that yields messages as they arrive
378
220
  * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
379
221
  * @throws {QueueEmptyError} When no messages are available (204)
380
- * @throws {MessageLockedError} When FIFO queue has locked messages (423)
222
+ * @throws {MessageLockedError} When messages are temporarily locked (423)
381
223
  * @throws {BadRequestError} When request parameters are invalid
382
224
  * @throws {UnauthorizedError} When authentication fails
383
225
  * @throws {ForbiddenError} When access is denied (environment mismatch)
@@ -428,7 +270,7 @@ var QueueClient = class _QueueClient {
428
270
  const parsed = parseInt(retryAfterHeader, 10);
429
271
  retryAfter = isNaN(parsed) ? void 0 : parsed;
430
272
  }
431
- throw new MessageLockedError("next message in FIFO queue", retryAfter);
273
+ throw new MessageLockedError("next message", retryAfter);
432
274
  }
433
275
  if (response.status >= 500) {
434
276
  throw new InternalServerError(
@@ -514,9 +356,6 @@ var QueueClient = class _QueueClient {
514
356
  }
515
357
  throw new MessageLockedError(messageId, retryAfter);
516
358
  }
517
- if (response.status === 424) {
518
- throw new FailedDependencyError(messageId);
519
- }
520
359
  if (response.status === 409) {
521
360
  throw new MessageNotAvailableError(messageId);
522
361
  }
@@ -712,24 +551,20 @@ var JsonTransport = class {
712
551
  }
713
552
  async deserialize(stream) {
714
553
  const reader = stream.getReader();
554
+ let totalLength = 0;
715
555
  const chunks = [];
716
556
  try {
717
557
  while (true) {
718
558
  const { done, value } = await reader.read();
719
559
  if (done) break;
720
560
  chunks.push(value);
561
+ totalLength += value.length;
721
562
  }
722
563
  } finally {
723
564
  reader.releaseLock();
724
565
  }
725
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
726
- const buffer = new Uint8Array(totalLength);
727
- let offset = 0;
728
- for (const chunk of chunks) {
729
- buffer.set(chunk, offset);
730
- offset += chunk.length;
731
- }
732
- return JSON.parse(Buffer.from(buffer).toString("utf8"));
566
+ const buffer = Buffer.concat(chunks, totalLength);
567
+ return JSON.parse(buffer.toString("utf8"));
733
568
  }
734
569
  };
735
570
  var BufferTransport = class {
@@ -1011,8 +846,7 @@ var Topic = class {
1011
846
  queueName: this.topicName,
1012
847
  payload,
1013
848
  idempotencyKey: options?.idempotencyKey,
1014
- retentionSeconds: options?.retentionSeconds,
1015
- callback: options?.callback
849
+ retentionSeconds: options?.retentionSeconds
1016
850
  },
1017
851
  this.transport
1018
852
  );
@@ -1063,8 +897,7 @@ async function send(topicName, payload, options) {
1063
897
  queueName: topicName,
1064
898
  payload,
1065
899
  idempotencyKey: options?.idempotencyKey,
1066
- retentionSeconds: options?.retentionSeconds,
1067
- callback: options?.callback
900
+ retentionSeconds: options?.retentionSeconds
1068
901
  },
1069
902
  transport
1070
903
  );
@@ -1091,23 +924,22 @@ async function receive(topicName, consumerGroup, handler, options) {
1091
924
 
1092
925
  // src/callback.ts
1093
926
  function parseCallbackRequest(request) {
1094
- const headers = request.headers;
1095
- const messageId = headers.get("Vqs-Message-Id");
1096
- const queueName = headers.get("Vqs-Queue-Name");
1097
- const consumerGroup = headers.get("Vqs-Consumer-Group");
1098
- const missingHeaders = [];
1099
- if (!messageId) missingHeaders.push("Vqs-Message-Id");
1100
- if (!queueName) missingHeaders.push("Vqs-Queue-Name");
1101
- if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
1102
- if (missingHeaders.length > 0) {
1103
- throw new InvalidCallbackError(
1104
- `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(", ")}`
1105
937
  );
1106
938
  }
1107
939
  return {
1108
- messageId,
1109
940
  queueName,
1110
- consumerGroup
941
+ consumerGroup,
942
+ messageId
1111
943
  };
1112
944
  }
1113
945
  function handleCallback(handlers) {
@@ -1116,41 +948,38 @@ function handleCallback(handlers) {
1116
948
  const { queueName, consumerGroup, messageId } = parseCallbackRequest(request);
1117
949
  const topicHandler = handlers[queueName];
1118
950
  if (!topicHandler) {
1119
- 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
+ );
1120
959
  }
1121
- let actualHandler;
1122
- if (typeof topicHandler === "function") {
1123
- if (consumerGroup !== "default") {
1124
- throw new Error(
1125
- `Topic "${queueName}" has a single handler but received consumer group "${consumerGroup}". Expected "default".`
1126
- );
1127
- }
1128
- actualHandler = topicHandler;
1129
- } else {
1130
- const consumerGroupHandler = topicHandler[consumerGroup];
1131
- if (!consumerGroupHandler) {
1132
- const availableGroups = Object.keys(topicHandler).join(", ");
1133
- throw new Error(
1134
- `No handler found for consumer group "${consumerGroup}" in topic "${queueName}". Available groups: ${availableGroups}`
1135
- );
1136
- }
1137
- 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
+ );
1138
970
  }
1139
971
  const client = new QueueClient();
1140
972
  const topic = new Topic(client, queueName);
1141
973
  const cg = topic.consumerGroup(consumerGroup);
1142
- await cg.consume(actualHandler, { messageId });
974
+ await cg.consume(consumerGroupHandler, { messageId });
1143
975
  return Response.json({ status: "success" });
1144
976
  } catch (error) {
1145
- console.error("Callback error:", error);
1146
- if (error instanceof InvalidCallbackError) {
1147
- return Response.json(
1148
- { error: "Invalid callback request" },
1149
- { status: 400 }
1150
- );
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 });
1151
980
  }
1152
981
  return Response.json(
1153
- { error: "Failed to process callback" },
982
+ { error: "Failed to process queue message" },
1154
983
  { status: 500 }
1155
984
  );
1156
985
  }
@@ -1160,11 +989,8 @@ export {
1160
989
  BadRequestError,
1161
990
  BufferTransport,
1162
991
  ConsumerGroup,
1163
- FailedDependencyError,
1164
- FifoOrderingViolationError,
1165
992
  ForbiddenError,
1166
993
  InternalServerError,
1167
- InvalidCallbackError,
1168
994
  InvalidLimitError,
1169
995
  JsonTransport,
1170
996
  MessageCorruptedError,