@vercel/queue 0.0.0-alpha.5 → 0.0.0-alpha.7

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,115 +1,77 @@
1
- // src/client.ts
2
- import { parseMultipartStream } from "mixpart";
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 [];
1
+ // src/transports.ts
2
+ var JsonTransport = class {
3
+ contentType = "application/json";
4
+ serialize(value) {
5
+ return Buffer.from(JSON.stringify(value), "utf8");
24
6
  }
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
- );
7
+ async deserialize(stream) {
8
+ const reader = stream.getReader();
9
+ let totalLength = 0;
10
+ const chunks = [];
11
+ try {
12
+ while (true) {
13
+ const { done, value } = await reader.read();
14
+ if (done) break;
15
+ chunks.push(value);
16
+ totalLength += value.length;
17
+ }
18
+ } finally {
19
+ reader.releaseLock();
34
20
  }
35
- return [];
21
+ const buffer = Buffer.concat(chunks, totalLength);
22
+ return JSON.parse(buffer.toString("utf8"));
36
23
  }
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
- );
24
+ };
25
+ var BufferTransport = class {
26
+ contentType = "application/octet-stream";
27
+ serialize(value) {
28
+ return value;
29
+ }
30
+ async deserialize(stream) {
31
+ const reader = stream.getReader();
32
+ const chunks = [];
33
+ try {
34
+ while (true) {
35
+ const { done, value } = await reader.read();
36
+ if (done) break;
37
+ chunks.push(value);
38
+ }
39
+ } finally {
40
+ reader.releaseLock();
46
41
  }
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;
42
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
43
+ const buffer = new Uint8Array(totalLength);
44
+ let offset = 0;
45
+ for (const chunk of chunks) {
46
+ buffer.set(chunk, offset);
47
+ offset += chunk.length;
48
+ }
49
+ return Buffer.from(buffer);
72
50
  }
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(" ");
51
+ };
52
+ var StreamTransport = class {
53
+ contentType = "application/octet-stream";
54
+ serialize(value) {
55
+ return value;
80
56
  }
81
- const bashScript = `
82
- # Wait for any initial boot time
83
- sleep ${initialDelaySeconds}
57
+ async deserialize(stream) {
58
+ return stream;
59
+ }
60
+ async finalize(payload) {
61
+ const reader = payload.getReader();
62
+ try {
63
+ while (true) {
64
+ const { done } = await reader.read();
65
+ if (done) break;
66
+ }
67
+ } finally {
68
+ reader.releaseLock();
69
+ }
70
+ }
71
+ };
84
72
 
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
- }
73
+ // src/client.ts
74
+ import { parseMultipartStream } from "mixpart";
113
75
 
114
76
  // src/types.ts
115
77
  var MessageNotFoundError = class extends Error {
@@ -126,12 +88,6 @@ var MessageNotAvailableError = class extends Error {
126
88
  this.name = "MessageNotAvailableError";
127
89
  }
128
90
  };
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
91
  var MessageCorruptedError = class extends Error {
136
92
  constructor(messageId, reason) {
137
93
  super(`Message ${messageId} is corrupted: ${reason}`);
@@ -173,14 +129,6 @@ var BadRequestError = class extends Error {
173
129
  this.name = "BadRequestError";
174
130
  }
175
131
  };
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
132
  var InternalServerError = class extends Error {
185
133
  constructor(message = "Unexpected server error") {
186
134
  super(message);
@@ -193,12 +141,6 @@ var InvalidLimitError = class extends Error {
193
141
  this.name = "InvalidLimitError";
194
142
  }
195
143
  };
196
- var InvalidCallbackError = class extends Error {
197
- constructor(message) {
198
- super(message);
199
- this.name = "InvalidCallbackError";
200
- }
201
- };
202
144
 
203
145
  // src/client.ts
204
146
  async function consumeStream(stream) {
@@ -237,7 +179,7 @@ var QueueClient = class _QueueClient {
237
179
  baseUrl;
238
180
  token;
239
181
  /**
240
- * Internal default instance for use by createTopic and other convenience functions
182
+ * Internal default instance for use by convenience functions
241
183
  * @internal
242
184
  */
243
185
  static _defaultInstance = null;
@@ -295,7 +237,7 @@ var QueueClient = class _QueueClient {
295
237
  * @throws {InternalServerError} When server encounters an error
296
238
  */
297
239
  async sendMessage(options, transport) {
298
- const { queueName, payload, idempotencyKey, retentionSeconds, callback } = options;
240
+ const { queueName, payload, idempotencyKey, retentionSeconds } = options;
299
241
  const headers = new Headers({
300
242
  Authorization: `Bearer ${this.token}`,
301
243
  "Vqs-Queue-Name": queueName,
@@ -310,34 +252,6 @@ var QueueClient = class _QueueClient {
310
252
  if (retentionSeconds !== void 0) {
311
253
  headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
312
254
  }
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
255
  const body = transport.serialize(payload);
342
256
  const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
343
257
  method: "POST",
@@ -368,9 +282,6 @@ var QueueClient = class _QueueClient {
368
282
  );
369
283
  }
370
284
  const responseData = await response.json();
371
- if (localhostCallbacks.length > 0) {
372
- fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
373
- }
374
285
  return responseData;
375
286
  }
376
287
  /**
@@ -380,7 +291,7 @@ var QueueClient = class _QueueClient {
380
291
  * @returns AsyncGenerator that yields messages as they arrive
381
292
  * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
382
293
  * @throws {QueueEmptyError} When no messages are available (204)
383
- * @throws {MessageLockedError} When FIFO queue has locked messages (423)
294
+ * @throws {MessageLockedError} When messages are temporarily locked (423)
384
295
  * @throws {BadRequestError} When request parameters are invalid
385
296
  * @throws {UnauthorizedError} When authentication fails
386
297
  * @throws {ForbiddenError} When access is denied (environment mismatch)
@@ -431,7 +342,7 @@ var QueueClient = class _QueueClient {
431
342
  const parsed = parseInt(retryAfterHeader, 10);
432
343
  retryAfter = isNaN(parsed) ? void 0 : parsed;
433
344
  }
434
- throw new MessageLockedError("next message in FIFO queue", retryAfter);
345
+ throw new MessageLockedError("next message", retryAfter);
435
346
  }
436
347
  if (response.status >= 500) {
437
348
  throw new InternalServerError(
@@ -517,9 +428,6 @@ var QueueClient = class _QueueClient {
517
428
  }
518
429
  throw new MessageLockedError(messageId, retryAfter);
519
430
  }
520
- if (response.status === 424) {
521
- throw new FailedDependencyError(messageId);
522
- }
523
431
  if (response.status === 409) {
524
432
  throw new MessageNotAvailableError(messageId);
525
433
  }
@@ -707,82 +615,6 @@ var QueueClient = class _QueueClient {
707
615
  }
708
616
  };
709
617
 
710
- // src/transports.ts
711
- var JsonTransport = class {
712
- contentType = "application/json";
713
- serialize(value) {
714
- return Buffer.from(JSON.stringify(value), "utf8");
715
- }
716
- async deserialize(stream) {
717
- const reader = stream.getReader();
718
- const chunks = [];
719
- try {
720
- while (true) {
721
- const { done, value } = await reader.read();
722
- if (done) break;
723
- chunks.push(value);
724
- }
725
- } finally {
726
- reader.releaseLock();
727
- }
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"));
736
- }
737
- };
738
- var BufferTransport = class {
739
- contentType = "application/octet-stream";
740
- serialize(value) {
741
- return value;
742
- }
743
- async deserialize(stream) {
744
- const reader = stream.getReader();
745
- const chunks = [];
746
- try {
747
- while (true) {
748
- const { done, value } = await reader.read();
749
- if (done) break;
750
- chunks.push(value);
751
- }
752
- } finally {
753
- reader.releaseLock();
754
- }
755
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
756
- const buffer = new Uint8Array(totalLength);
757
- let offset = 0;
758
- for (const chunk of chunks) {
759
- buffer.set(chunk, offset);
760
- offset += chunk.length;
761
- }
762
- return Buffer.from(buffer);
763
- }
764
- };
765
- var StreamTransport = class {
766
- contentType = "application/octet-stream";
767
- serialize(value) {
768
- return value;
769
- }
770
- async deserialize(stream) {
771
- return stream;
772
- }
773
- async finalize(payload) {
774
- const reader = payload.getReader();
775
- try {
776
- while (true) {
777
- const { done } = await reader.read();
778
- if (done) break;
779
- }
780
- } finally {
781
- reader.releaseLock();
782
- }
783
- }
784
- };
785
-
786
618
  // src/consumer-group.ts
787
619
  var ConsumerGroup = class {
788
620
  client;
@@ -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
  );
@@ -1054,10 +885,6 @@ var Topic = class {
1054
885
  };
1055
886
 
1056
887
  // src/factory.ts
1057
- function createTopic(topicName, transport) {
1058
- const client = QueueClient._getDefaultInstance();
1059
- return new Topic(client, topicName, transport);
1060
- }
1061
888
  async function send(topicName, payload, options) {
1062
889
  const transport = options?.transport || new JsonTransport();
1063
890
  const client = QueueClient._getDefaultInstance();
@@ -1066,8 +893,7 @@ async function send(topicName, payload, options) {
1066
893
  queueName: topicName,
1067
894
  payload,
1068
895
  idempotencyKey: options?.idempotencyKey,
1069
- retentionSeconds: options?.retentionSeconds,
1070
- callback: options?.callback
896
+ retentionSeconds: options?.retentionSeconds
1071
897
  },
1072
898
  transport
1073
899
  );
@@ -1075,7 +901,8 @@ async function send(topicName, payload, options) {
1075
901
  }
1076
902
  async function receive(topicName, consumerGroup, handler, options) {
1077
903
  const transport = options?.transport || new JsonTransport();
1078
- const topic = createTopic(topicName, transport);
904
+ const client = QueueClient._getDefaultInstance();
905
+ const topic = new Topic(client, topicName, transport);
1079
906
  const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
1080
907
  const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
1081
908
  if (messageId) {
@@ -1094,23 +921,22 @@ async function receive(topicName, consumerGroup, handler, options) {
1094
921
 
1095
922
  // src/callback.ts
1096
923
  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(", ")}`
924
+ const queueName = request.headers.get("Vqs-Queue-Name");
925
+ const consumerGroup = request.headers.get("Vqs-Consumer-Group");
926
+ const messageId = request.headers.get("Vqs-Message-Id");
927
+ if (!queueName || !consumerGroup || !messageId) {
928
+ const missingHeaders = [];
929
+ if (!queueName) missingHeaders.push("Vqs-Queue-Name");
930
+ if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
931
+ if (!messageId) missingHeaders.push("Vqs-Message-Id");
932
+ throw new Error(
933
+ `Missing required queue headers: ${missingHeaders.join(", ")}`
1108
934
  );
1109
935
  }
1110
936
  return {
1111
- messageId,
1112
937
  queueName,
1113
- consumerGroup
938
+ consumerGroup,
939
+ messageId
1114
940
  };
1115
941
  }
1116
942
  function handleCallback(handlers) {
@@ -1119,41 +945,38 @@ function handleCallback(handlers) {
1119
945
  const { queueName, consumerGroup, messageId } = parseCallbackRequest(request);
1120
946
  const topicHandler = handlers[queueName];
1121
947
  if (!topicHandler) {
1122
- throw new Error(`No handler found for topic: ${queueName}`);
948
+ const availableTopics = Object.keys(handlers).join(", ");
949
+ return Response.json(
950
+ {
951
+ error: `No handler found for topic: ${queueName}`,
952
+ availableTopics
953
+ },
954
+ { status: 404 }
955
+ );
1123
956
  }
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;
957
+ const consumerGroupHandler = topicHandler[consumerGroup];
958
+ if (!consumerGroupHandler) {
959
+ const availableGroups = Object.keys(topicHandler).join(", ");
960
+ return Response.json(
961
+ {
962
+ error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
963
+ availableGroups
964
+ },
965
+ { status: 404 }
966
+ );
1141
967
  }
1142
968
  const client = new QueueClient();
1143
969
  const topic = new Topic(client, queueName);
1144
970
  const cg = topic.consumerGroup(consumerGroup);
1145
- await cg.consume(actualHandler, { messageId });
971
+ await cg.consume(consumerGroupHandler, { messageId });
1146
972
  return Response.json({ status: "success" });
1147
973
  } 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
- );
974
+ console.error("Queue callback error:", error);
975
+ if (error instanceof Error && error.message.includes("Missing required queue headers")) {
976
+ return Response.json({ error: error.message }, { status: 400 });
1154
977
  }
1155
978
  return Response.json(
1156
- { error: "Failed to process callback" },
979
+ { error: "Failed to process queue message" },
1157
980
  { status: 500 }
1158
981
  );
1159
982
  }
@@ -1162,26 +985,18 @@ function handleCallback(handlers) {
1162
985
  export {
1163
986
  BadRequestError,
1164
987
  BufferTransport,
1165
- ConsumerGroup,
1166
- FailedDependencyError,
1167
- FifoOrderingViolationError,
1168
988
  ForbiddenError,
1169
989
  InternalServerError,
1170
- InvalidCallbackError,
1171
990
  InvalidLimitError,
1172
991
  JsonTransport,
1173
992
  MessageCorruptedError,
1174
993
  MessageLockedError,
1175
994
  MessageNotAvailableError,
1176
995
  MessageNotFoundError,
1177
- QueueClient,
1178
996
  QueueEmptyError,
1179
997
  StreamTransport,
1180
- Topic,
1181
998
  UnauthorizedError,
1182
- createTopic,
1183
999
  handleCallback,
1184
- parseCallbackRequest,
1185
1000
  receive,
1186
1001
  send
1187
1002
  };