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

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,17 +1,3 @@
1
- // src/oidc.ts
2
- async function getVercelOidcToken() {
3
- const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
4
- const fromSymbol = globalThis;
5
- const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
6
- const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
7
- if (!token) {
8
- throw new Error(
9
- `The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`
10
- );
11
- }
12
- return token;
13
- }
14
-
15
1
  // src/client.ts
16
2
  import { parseMultipartStream } from "mixpart";
17
3
 
@@ -141,13 +127,9 @@ var MessageNotAvailableError = class extends Error {
141
127
  }
142
128
  };
143
129
  var FifoOrderingViolationError = class extends Error {
144
- nextMessageId;
145
- constructor(messageId, nextMessageId, reason) {
146
- super(
147
- `FIFO ordering violation for message ${messageId}: ${reason}. Process message ${nextMessageId} first.`
148
- );
130
+ constructor(messageId, reason) {
131
+ super(`FIFO ordering violation for message ${messageId}: ${reason}`);
149
132
  this.name = "FifoOrderingViolationError";
150
- this.nextMessageId = nextMessageId;
151
133
  }
152
134
  };
153
135
  var MessageCorruptedError = class extends Error {
@@ -192,13 +174,11 @@ var BadRequestError = class extends Error {
192
174
  }
193
175
  };
194
176
  var FailedDependencyError = class extends Error {
195
- nextMessageId;
196
- constructor(messageId, nextMessageId) {
177
+ constructor(messageId) {
197
178
  super(
198
- `Failed dependency: FIFO ordering violation for message ${messageId}. Must process message ${nextMessageId} first.`
179
+ `Failed dependency: FIFO ordering violation for message ${messageId}`
199
180
  );
200
181
  this.name = "FailedDependencyError";
201
- this.nextMessageId = nextMessageId;
202
182
  }
203
183
  };
204
184
  var InternalServerError = class extends Error {
@@ -256,32 +236,53 @@ function parseQueueHeaders(headers) {
256
236
  var QueueClient = class _QueueClient {
257
237
  baseUrl;
258
238
  token;
239
+ /**
240
+ * Internal default instance for use by createTopic and other convenience functions
241
+ * @internal
242
+ */
243
+ static _defaultInstance = null;
259
244
  /**
260
245
  * Create a new Vercel Queue Service client
261
- * @param options Client configuration options
246
+ * @param options Client configuration options (optional - will auto-detect Vercel Function environment)
262
247
  */
263
- constructor(options) {
248
+ constructor(options = {}) {
264
249
  this.baseUrl = options.baseUrl || "https://vqs.vercel.sh";
265
- this.token = options.token;
250
+ if (options.token) {
251
+ this.token = options.token;
252
+ } else {
253
+ const token = this.getVercelOidcTokenSync();
254
+ if (!token) {
255
+ throw new Error(
256
+ "Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment, or provide a token explicitly.\n\nTo set up your environment:\n1. Link your project: 'vercel link'\n2. Pull environment variables: 'vercel env pull'\n3. Run with environment: 'dotenv -e .env.local -- your-command'"
257
+ );
258
+ }
259
+ this.token = token;
260
+ }
266
261
  }
267
262
  /**
268
- * Create a QueueClient automatically configured for Vercel Functions
269
- * This method automatically retrieves the OIDC token from the Vercel Function environment
270
- * Always creates a fresh instance since OIDC tokens expire after 15 minutes
271
- * @param baseUrl Optional base URL override
272
- * @returns Promise resolving to a new QueueClient instance
263
+ * Get the default client instance for internal use by convenience functions
264
+ * @internal
273
265
  */
274
- static async fromVercelFunction(baseUrl) {
275
- const token = await getVercelOidcToken();
276
- if (!token) {
277
- throw new Error(
278
- "Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment."
279
- );
266
+ static _getDefaultInstance() {
267
+ if (!this._defaultInstance) {
268
+ this._defaultInstance = new _QueueClient();
269
+ }
270
+ return this._defaultInstance;
271
+ }
272
+ /**
273
+ * Synchronously get OIDC token from environment
274
+ * Used internally by constructor - mirrors the logic from getVercelOidcToken but synchronously
275
+ */
276
+ getVercelOidcTokenSync() {
277
+ try {
278
+ const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
279
+ const fromSymbol = globalThis;
280
+ const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
281
+ const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
282
+ return token || null;
283
+ } catch {
284
+ return null;
280
285
  }
281
- return new _QueueClient({
282
- token,
283
- baseUrl
284
- });
285
286
  }
286
287
  /**
287
288
  * Send a message to a queue
@@ -300,6 +301,9 @@ var QueueClient = class _QueueClient {
300
301
  "Vqs-Queue-Name": queueName,
301
302
  "Content-Type": transport.contentType
302
303
  });
304
+ if (process.env.VERCEL_DEPLOYMENT_ID) {
305
+ headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
306
+ }
303
307
  if (idempotencyKey) {
304
308
  headers.set("Vqs-Idempotency-Key", idempotencyKey);
305
309
  }
@@ -514,36 +518,9 @@ var QueueClient = class _QueueClient {
514
518
  throw new MessageLockedError(messageId, retryAfter);
515
519
  }
516
520
  if (response.status === 424) {
517
- try {
518
- const errorData = await response.json();
519
- if (errorData.meta?.nextMessageId) {
520
- throw new FailedDependencyError(
521
- messageId,
522
- errorData.meta.nextMessageId
523
- );
524
- }
525
- } catch (parseError) {
526
- if (parseError instanceof FailedDependencyError) {
527
- throw parseError;
528
- }
529
- }
530
- throw new MessageNotAvailableError(
531
- messageId,
532
- "FIFO ordering violation"
533
- );
521
+ throw new FailedDependencyError(messageId);
534
522
  }
535
523
  if (response.status === 409) {
536
- try {
537
- const errorData = await response.json();
538
- if (errorData.nextMessageId) {
539
- throw new FifoOrderingViolationError(
540
- messageId,
541
- errorData.nextMessageId,
542
- errorData.error
543
- );
544
- }
545
- } catch (parseError) {
546
- }
547
524
  throw new MessageNotAvailableError(messageId);
548
525
  }
549
526
  if (response.status >= 500) {
@@ -905,7 +882,11 @@ var ConsumerGroup = class {
905
882
  message.ticket
906
883
  );
907
884
  try {
908
- const result = await handler(message);
885
+ const result = await handler(message.payload, {
886
+ messageId: message.messageId,
887
+ deliveryCount: message.deliveryCount,
888
+ timestamp: message.timestamp
889
+ });
909
890
  await stopExtension();
910
891
  if (result && "timeoutSeconds" in result) {
911
892
  await this.client.changeVisibility({
@@ -935,219 +916,58 @@ var ConsumerGroup = class {
935
916
  throw error;
936
917
  }
937
918
  }
938
- /**
939
- * Start continuous processing of messages from the topic
940
- * @param signal AbortSignal to control when to stop processing
941
- * @param handler Function to process each message
942
- * @param options Processing options
943
- * @returns Promise that resolves when processing stops (due to signal or error)
944
- */
945
- async subscribe(signal, handler, options = {}) {
946
- const pollingInterval = options.pollingInterval || 1e3;
947
- while (!signal.aborted) {
948
- try {
949
- for await (const message of this.client.receiveMessages(
919
+ async consume(handler, options) {
920
+ if (options?.messageId) {
921
+ if (options.skipPayload) {
922
+ const response = await this.client.receiveMessageById(
950
923
  {
951
924
  queueName: this.topicName,
952
925
  consumerGroup: this.consumerGroupName,
926
+ messageId: options.messageId,
953
927
  visibilityTimeoutSeconds: this.visibilityTimeout,
954
- limit: 1
955
- // Always process one message at a time
928
+ skipPayload: true
956
929
  },
957
930
  this.transport
958
- )) {
959
- if (signal.aborted) {
960
- break;
961
- }
962
- try {
963
- await this.processMessage(message, handler);
964
- } catch (error) {
965
- console.error("Error processing message:", error);
966
- }
967
- }
968
- if (!signal.aborted) {
969
- await new Promise((resolve) => {
970
- const timeoutId = setTimeout(resolve, pollingInterval);
971
- signal.addEventListener(
972
- "abort",
973
- () => {
974
- clearTimeout(timeoutId);
975
- resolve();
976
- },
977
- { once: true }
978
- );
979
- });
980
- }
981
- } catch (error) {
982
- if (error instanceof QueueEmptyError) {
983
- if (!signal.aborted) {
984
- await new Promise((resolve) => {
985
- const timeoutId = setTimeout(resolve, pollingInterval);
986
- signal.addEventListener(
987
- "abort",
988
- () => {
989
- clearTimeout(timeoutId);
990
- resolve();
991
- },
992
- { once: true }
993
- );
994
- });
995
- }
996
- continue;
997
- }
998
- if (error instanceof MessageLockedError) {
999
- const waitTime = error.retryAfter ? error.retryAfter * 1e3 : pollingInterval;
1000
- if (!signal.aborted) {
1001
- await new Promise((resolve) => {
1002
- const timeoutId = setTimeout(resolve, waitTime);
1003
- signal.addEventListener(
1004
- "abort",
1005
- () => {
1006
- clearTimeout(timeoutId);
1007
- resolve();
1008
- },
1009
- { once: true }
1010
- );
1011
- });
1012
- }
1013
- continue;
1014
- }
1015
- console.error("Error polling topic:", error);
1016
- throw error;
931
+ );
932
+ await this.processMessage(
933
+ response.message,
934
+ handler
935
+ );
936
+ } else {
937
+ const response = await this.client.receiveMessageById(
938
+ {
939
+ queueName: this.topicName,
940
+ consumerGroup: this.consumerGroupName,
941
+ messageId: options.messageId,
942
+ visibilityTimeoutSeconds: this.visibilityTimeout
943
+ },
944
+ this.transport
945
+ );
946
+ await this.processMessage(
947
+ response.message,
948
+ handler
949
+ );
950
+ }
951
+ } else {
952
+ let messageFound = false;
953
+ for await (const message of this.client.receiveMessages(
954
+ {
955
+ queueName: this.topicName,
956
+ consumerGroup: this.consumerGroupName,
957
+ visibilityTimeoutSeconds: this.visibilityTimeout,
958
+ limit: 1
959
+ },
960
+ this.transport
961
+ )) {
962
+ messageFound = true;
963
+ await this.processMessage(message, handler);
964
+ break;
965
+ }
966
+ if (!messageFound) {
967
+ throw new Error("No messages available");
1017
968
  }
1018
969
  }
1019
970
  }
1020
- /**
1021
- * Receive and process a specific message by its ID with full payload
1022
- * @param messageId The ID of the message to receive and process
1023
- * @param handler Function to process the message with full payload
1024
- * @returns Promise that resolves when the message is processed or rejects with specific errors
1025
- * @throws {MessageNotFoundError} When the message doesn't exist (404)
1026
- * @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
1027
- * @throws {MessageLockedError} When the message is temporarily locked (423)
1028
- * @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
1029
- * @throws {FailedDependencyError} When FIFO ordering is violated (424)
1030
- * @throws {MessageCorruptedError} When the message data is corrupted
1031
- * @throws {BadRequestError} When request parameters are invalid
1032
- * @throws {UnauthorizedError} When authentication fails
1033
- * @throws {ForbiddenError} When access is denied
1034
- * @throws {InternalServerError} When server encounters an error
1035
- */
1036
- async receiveMessage(messageId, handler) {
1037
- const response = await this.client.receiveMessageById(
1038
- {
1039
- queueName: this.topicName,
1040
- consumerGroup: this.consumerGroupName,
1041
- messageId,
1042
- visibilityTimeoutSeconds: this.visibilityTimeout
1043
- },
1044
- this.transport
1045
- );
1046
- await this.processMessage(response.message, handler);
1047
- }
1048
- /**
1049
- * Receive and process the next available message from the queue
1050
- * @param handler Function to process the message
1051
- * @returns Promise that resolves when the message is processed or rejects with specific errors
1052
- * @throws {QueueEmptyError} When no messages are available in the queue (204)
1053
- * @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
1054
- * @throws {BadRequestError} When request parameters are invalid
1055
- * @throws {UnauthorizedError} When authentication fails
1056
- * @throws {ForbiddenError} When access is denied
1057
- * @throws {InternalServerError} When server encounters an error
1058
- */
1059
- async receiveNextMessage(handler) {
1060
- let messageFound = false;
1061
- for await (const message of this.client.receiveMessages(
1062
- {
1063
- queueName: this.topicName,
1064
- consumerGroup: this.consumerGroupName,
1065
- visibilityTimeoutSeconds: this.visibilityTimeout,
1066
- limit: 1
1067
- },
1068
- this.transport
1069
- )) {
1070
- messageFound = true;
1071
- await this.processMessage(message, handler);
1072
- break;
1073
- }
1074
- if (!messageFound) {
1075
- throw new Error("No messages available");
1076
- }
1077
- }
1078
- /**
1079
- * Receive and process multiple next available messages from the queue
1080
- * @param limit Number of messages to process (1-10)
1081
- * @param handler Function to process each message
1082
- * @returns Promise that resolves to an array of PromiseSettledResult (same as Promise.allSettled)
1083
- * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
1084
- * @throws {QueueEmptyError} When no messages are available in the queue (204)
1085
- * @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
1086
- * @throws {BadRequestError} When request parameters are invalid
1087
- * @throws {UnauthorizedError} When authentication fails
1088
- * @throws {ForbiddenError} When access is denied
1089
- * @throws {InternalServerError} When server encounters an error
1090
- */
1091
- async receiveNextMessages(limit, handler) {
1092
- if (limit < 1 || limit > 10) {
1093
- throw new InvalidLimitError(limit);
1094
- }
1095
- const processingPromises = [];
1096
- let messageCount = 0;
1097
- for await (const message of this.client.receiveMessages(
1098
- {
1099
- queueName: this.topicName,
1100
- consumerGroup: this.consumerGroupName,
1101
- visibilityTimeoutSeconds: this.visibilityTimeout,
1102
- limit
1103
- },
1104
- this.transport
1105
- )) {
1106
- messageCount++;
1107
- const wrappedPromise = this.processMessage(message, handler).then(
1108
- (value) => ({
1109
- status: "fulfilled",
1110
- value
1111
- }),
1112
- (reason) => ({ status: "rejected", reason })
1113
- );
1114
- processingPromises.push(wrappedPromise);
1115
- }
1116
- if (messageCount === 0) {
1117
- throw new Error("No messages available");
1118
- }
1119
- const results = await Promise.all(processingPromises);
1120
- return results;
1121
- }
1122
- /**
1123
- * Handle a specific message by its ID without downloading the payload (metadata only)
1124
- * @param messageId The ID of the message to handle
1125
- * @param handler Function to process the message metadata (payload will be void)
1126
- * @returns Promise that resolves when the message is handled or rejects with specific errors
1127
- * @throws {MessageNotFoundError} When the message doesn't exist (404)
1128
- * @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
1129
- * @throws {MessageLockedError} When the message is temporarily locked (423)
1130
- * @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
1131
- * @throws {FailedDependencyError} When FIFO ordering is violated (424)
1132
- * @throws {MessageCorruptedError} When the message data is corrupted
1133
- * @throws {BadRequestError} When request parameters are invalid
1134
- * @throws {UnauthorizedError} When authentication fails
1135
- * @throws {ForbiddenError} When access is denied
1136
- * @throws {InternalServerError} When server encounters an error
1137
- */
1138
- async handleMessage(messageId, handler) {
1139
- const response = await this.client.receiveMessageById(
1140
- {
1141
- queueName: this.topicName,
1142
- consumerGroup: this.consumerGroupName,
1143
- messageId,
1144
- visibilityTimeoutSeconds: this.visibilityTimeout,
1145
- skipPayload: true
1146
- },
1147
- this.transport
1148
- );
1149
- await this.processMessage(response.message, handler);
1150
- }
1151
971
  /**
1152
972
  * Get the consumer group name
1153
973
  */
@@ -1234,9 +1054,43 @@ var Topic = class {
1234
1054
  };
1235
1055
 
1236
1056
  // src/factory.ts
1237
- function createTopic(client, topicName, transport) {
1057
+ function createTopic(topicName, transport) {
1058
+ const client = QueueClient._getDefaultInstance();
1238
1059
  return new Topic(client, topicName, transport);
1239
1060
  }
1061
+ async function send(topicName, payload, options) {
1062
+ const transport = options?.transport || new JsonTransport();
1063
+ const client = QueueClient._getDefaultInstance();
1064
+ const result = await client.sendMessage(
1065
+ {
1066
+ queueName: topicName,
1067
+ payload,
1068
+ idempotencyKey: options?.idempotencyKey,
1069
+ retentionSeconds: options?.retentionSeconds,
1070
+ callback: options?.callback
1071
+ },
1072
+ transport
1073
+ );
1074
+ return { messageId: result.messageId };
1075
+ }
1076
+ async function receive(topicName, consumerGroup, handler, options) {
1077
+ const transport = options?.transport || new JsonTransport();
1078
+ const topic = createTopic(topicName, transport);
1079
+ const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
1080
+ const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
1081
+ if (messageId) {
1082
+ if (skipPayload) {
1083
+ return consumer.consume(handler, {
1084
+ messageId,
1085
+ skipPayload: true
1086
+ });
1087
+ } else {
1088
+ return consumer.consume(handler, { messageId });
1089
+ }
1090
+ } else {
1091
+ return consumer.consume(handler);
1092
+ }
1093
+ }
1240
1094
 
1241
1095
  // src/callback.ts
1242
1096
  function parseCallbackRequest(request) {
@@ -1285,17 +1139,10 @@ function handleCallback(handlers) {
1285
1139
  }
1286
1140
  actualHandler = consumerGroupHandler;
1287
1141
  }
1288
- const client = await QueueClient.fromVercelFunction();
1142
+ const client = new QueueClient();
1289
1143
  const topic = new Topic(client, queueName);
1290
1144
  const cg = topic.consumerGroup(consumerGroup);
1291
- await cg.receiveMessage(messageId, async (message) => {
1292
- const metadata = {
1293
- messageId: message.messageId,
1294
- deliveryCount: message.deliveryCount,
1295
- timestamp: message.timestamp
1296
- };
1297
- return await actualHandler(message.payload, metadata);
1298
- });
1145
+ await cg.consume(actualHandler, { messageId });
1299
1146
  return Response.json({ status: "success" });
1300
1147
  } catch (error) {
1301
1148
  console.error("Callback error:", error);
@@ -1333,8 +1180,9 @@ export {
1333
1180
  Topic,
1334
1181
  UnauthorizedError,
1335
1182
  createTopic,
1336
- getVercelOidcToken,
1337
1183
  handleCallback,
1338
- parseCallbackRequest
1184
+ parseCallbackRequest,
1185
+ receive,
1186
+ send
1339
1187
  };
1340
1188
  //# sourceMappingURL=index.mjs.map