@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.js CHANGED
@@ -40,26 +40,13 @@ __export(index_exports, {
40
40
  Topic: () => Topic,
41
41
  UnauthorizedError: () => UnauthorizedError,
42
42
  createTopic: () => createTopic,
43
- getVercelOidcToken: () => getVercelOidcToken,
44
43
  handleCallback: () => handleCallback,
45
- parseCallbackRequest: () => parseCallbackRequest
44
+ parseCallbackRequest: () => parseCallbackRequest,
45
+ receive: () => receive,
46
+ send: () => send
46
47
  });
47
48
  module.exports = __toCommonJS(index_exports);
48
49
 
49
- // src/oidc.ts
50
- async function getVercelOidcToken() {
51
- const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
52
- const fromSymbol = globalThis;
53
- const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
54
- const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
55
- if (!token) {
56
- throw new Error(
57
- `The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`
58
- );
59
- }
60
- return token;
61
- }
62
-
63
50
  // src/client.ts
64
51
  var import_mixpart = require("mixpart");
65
52
 
@@ -189,13 +176,9 @@ var MessageNotAvailableError = class extends Error {
189
176
  }
190
177
  };
191
178
  var FifoOrderingViolationError = class extends Error {
192
- nextMessageId;
193
- constructor(messageId, nextMessageId, reason) {
194
- super(
195
- `FIFO ordering violation for message ${messageId}: ${reason}. Process message ${nextMessageId} first.`
196
- );
179
+ constructor(messageId, reason) {
180
+ super(`FIFO ordering violation for message ${messageId}: ${reason}`);
197
181
  this.name = "FifoOrderingViolationError";
198
- this.nextMessageId = nextMessageId;
199
182
  }
200
183
  };
201
184
  var MessageCorruptedError = class extends Error {
@@ -240,13 +223,11 @@ var BadRequestError = class extends Error {
240
223
  }
241
224
  };
242
225
  var FailedDependencyError = class extends Error {
243
- nextMessageId;
244
- constructor(messageId, nextMessageId) {
226
+ constructor(messageId) {
245
227
  super(
246
- `Failed dependency: FIFO ordering violation for message ${messageId}. Must process message ${nextMessageId} first.`
228
+ `Failed dependency: FIFO ordering violation for message ${messageId}`
247
229
  );
248
230
  this.name = "FailedDependencyError";
249
- this.nextMessageId = nextMessageId;
250
231
  }
251
232
  };
252
233
  var InternalServerError = class extends Error {
@@ -304,32 +285,53 @@ function parseQueueHeaders(headers) {
304
285
  var QueueClient = class _QueueClient {
305
286
  baseUrl;
306
287
  token;
288
+ /**
289
+ * Internal default instance for use by createTopic and other convenience functions
290
+ * @internal
291
+ */
292
+ static _defaultInstance = null;
307
293
  /**
308
294
  * Create a new Vercel Queue Service client
309
- * @param options Client configuration options
295
+ * @param options Client configuration options (optional - will auto-detect Vercel Function environment)
310
296
  */
311
- constructor(options) {
297
+ constructor(options = {}) {
312
298
  this.baseUrl = options.baseUrl || "https://vqs.vercel.sh";
313
- this.token = options.token;
299
+ if (options.token) {
300
+ this.token = options.token;
301
+ } else {
302
+ const token = this.getVercelOidcTokenSync();
303
+ if (!token) {
304
+ throw new Error(
305
+ "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'"
306
+ );
307
+ }
308
+ this.token = token;
309
+ }
314
310
  }
315
311
  /**
316
- * Create a QueueClient automatically configured for Vercel Functions
317
- * This method automatically retrieves the OIDC token from the Vercel Function environment
318
- * Always creates a fresh instance since OIDC tokens expire after 15 minutes
319
- * @param baseUrl Optional base URL override
320
- * @returns Promise resolving to a new QueueClient instance
312
+ * Get the default client instance for internal use by convenience functions
313
+ * @internal
321
314
  */
322
- static async fromVercelFunction(baseUrl) {
323
- const token = await getVercelOidcToken();
324
- if (!token) {
325
- throw new Error(
326
- "Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment."
327
- );
315
+ static _getDefaultInstance() {
316
+ if (!this._defaultInstance) {
317
+ this._defaultInstance = new _QueueClient();
318
+ }
319
+ return this._defaultInstance;
320
+ }
321
+ /**
322
+ * Synchronously get OIDC token from environment
323
+ * Used internally by constructor - mirrors the logic from getVercelOidcToken but synchronously
324
+ */
325
+ getVercelOidcTokenSync() {
326
+ try {
327
+ const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
328
+ const fromSymbol = globalThis;
329
+ const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
330
+ const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
331
+ return token || null;
332
+ } catch {
333
+ return null;
328
334
  }
329
- return new _QueueClient({
330
- token,
331
- baseUrl
332
- });
333
335
  }
334
336
  /**
335
337
  * Send a message to a queue
@@ -348,6 +350,9 @@ var QueueClient = class _QueueClient {
348
350
  "Vqs-Queue-Name": queueName,
349
351
  "Content-Type": transport.contentType
350
352
  });
353
+ if (process.env.VERCEL_DEPLOYMENT_ID) {
354
+ headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
355
+ }
351
356
  if (idempotencyKey) {
352
357
  headers.set("Vqs-Idempotency-Key", idempotencyKey);
353
358
  }
@@ -562,36 +567,9 @@ var QueueClient = class _QueueClient {
562
567
  throw new MessageLockedError(messageId, retryAfter);
563
568
  }
564
569
  if (response.status === 424) {
565
- try {
566
- const errorData = await response.json();
567
- if (errorData.meta?.nextMessageId) {
568
- throw new FailedDependencyError(
569
- messageId,
570
- errorData.meta.nextMessageId
571
- );
572
- }
573
- } catch (parseError) {
574
- if (parseError instanceof FailedDependencyError) {
575
- throw parseError;
576
- }
577
- }
578
- throw new MessageNotAvailableError(
579
- messageId,
580
- "FIFO ordering violation"
581
- );
570
+ throw new FailedDependencyError(messageId);
582
571
  }
583
572
  if (response.status === 409) {
584
- try {
585
- const errorData = await response.json();
586
- if (errorData.nextMessageId) {
587
- throw new FifoOrderingViolationError(
588
- messageId,
589
- errorData.nextMessageId,
590
- errorData.error
591
- );
592
- }
593
- } catch (parseError) {
594
- }
595
573
  throw new MessageNotAvailableError(messageId);
596
574
  }
597
575
  if (response.status >= 500) {
@@ -953,7 +931,11 @@ var ConsumerGroup = class {
953
931
  message.ticket
954
932
  );
955
933
  try {
956
- const result = await handler(message);
934
+ const result = await handler(message.payload, {
935
+ messageId: message.messageId,
936
+ deliveryCount: message.deliveryCount,
937
+ timestamp: message.timestamp
938
+ });
957
939
  await stopExtension();
958
940
  if (result && "timeoutSeconds" in result) {
959
941
  await this.client.changeVisibility({
@@ -983,219 +965,58 @@ var ConsumerGroup = class {
983
965
  throw error;
984
966
  }
985
967
  }
986
- /**
987
- * Start continuous processing of messages from the topic
988
- * @param signal AbortSignal to control when to stop processing
989
- * @param handler Function to process each message
990
- * @param options Processing options
991
- * @returns Promise that resolves when processing stops (due to signal or error)
992
- */
993
- async subscribe(signal, handler, options = {}) {
994
- const pollingInterval = options.pollingInterval || 1e3;
995
- while (!signal.aborted) {
996
- try {
997
- for await (const message of this.client.receiveMessages(
968
+ async consume(handler, options) {
969
+ if (options?.messageId) {
970
+ if (options.skipPayload) {
971
+ const response = await this.client.receiveMessageById(
998
972
  {
999
973
  queueName: this.topicName,
1000
974
  consumerGroup: this.consumerGroupName,
975
+ messageId: options.messageId,
1001
976
  visibilityTimeoutSeconds: this.visibilityTimeout,
1002
- limit: 1
1003
- // Always process one message at a time
977
+ skipPayload: true
1004
978
  },
1005
979
  this.transport
1006
- )) {
1007
- if (signal.aborted) {
1008
- break;
1009
- }
1010
- try {
1011
- await this.processMessage(message, handler);
1012
- } catch (error) {
1013
- console.error("Error processing message:", error);
1014
- }
1015
- }
1016
- if (!signal.aborted) {
1017
- await new Promise((resolve) => {
1018
- const timeoutId = setTimeout(resolve, pollingInterval);
1019
- signal.addEventListener(
1020
- "abort",
1021
- () => {
1022
- clearTimeout(timeoutId);
1023
- resolve();
1024
- },
1025
- { once: true }
1026
- );
1027
- });
1028
- }
1029
- } catch (error) {
1030
- if (error instanceof QueueEmptyError) {
1031
- if (!signal.aborted) {
1032
- await new Promise((resolve) => {
1033
- const timeoutId = setTimeout(resolve, pollingInterval);
1034
- signal.addEventListener(
1035
- "abort",
1036
- () => {
1037
- clearTimeout(timeoutId);
1038
- resolve();
1039
- },
1040
- { once: true }
1041
- );
1042
- });
1043
- }
1044
- continue;
1045
- }
1046
- if (error instanceof MessageLockedError) {
1047
- const waitTime = error.retryAfter ? error.retryAfter * 1e3 : pollingInterval;
1048
- if (!signal.aborted) {
1049
- await new Promise((resolve) => {
1050
- const timeoutId = setTimeout(resolve, waitTime);
1051
- signal.addEventListener(
1052
- "abort",
1053
- () => {
1054
- clearTimeout(timeoutId);
1055
- resolve();
1056
- },
1057
- { once: true }
1058
- );
1059
- });
1060
- }
1061
- continue;
1062
- }
1063
- console.error("Error polling topic:", error);
1064
- throw error;
980
+ );
981
+ await this.processMessage(
982
+ response.message,
983
+ handler
984
+ );
985
+ } else {
986
+ const response = await this.client.receiveMessageById(
987
+ {
988
+ queueName: this.topicName,
989
+ consumerGroup: this.consumerGroupName,
990
+ messageId: options.messageId,
991
+ visibilityTimeoutSeconds: this.visibilityTimeout
992
+ },
993
+ this.transport
994
+ );
995
+ await this.processMessage(
996
+ response.message,
997
+ handler
998
+ );
999
+ }
1000
+ } else {
1001
+ let messageFound = false;
1002
+ for await (const message of this.client.receiveMessages(
1003
+ {
1004
+ queueName: this.topicName,
1005
+ consumerGroup: this.consumerGroupName,
1006
+ visibilityTimeoutSeconds: this.visibilityTimeout,
1007
+ limit: 1
1008
+ },
1009
+ this.transport
1010
+ )) {
1011
+ messageFound = true;
1012
+ await this.processMessage(message, handler);
1013
+ break;
1014
+ }
1015
+ if (!messageFound) {
1016
+ throw new Error("No messages available");
1065
1017
  }
1066
1018
  }
1067
1019
  }
1068
- /**
1069
- * Receive and process a specific message by its ID with full payload
1070
- * @param messageId The ID of the message to receive and process
1071
- * @param handler Function to process the message with full payload
1072
- * @returns Promise that resolves when the message is processed or rejects with specific errors
1073
- * @throws {MessageNotFoundError} When the message doesn't exist (404)
1074
- * @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
1075
- * @throws {MessageLockedError} When the message is temporarily locked (423)
1076
- * @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
1077
- * @throws {FailedDependencyError} When FIFO ordering is violated (424)
1078
- * @throws {MessageCorruptedError} When the message data is corrupted
1079
- * @throws {BadRequestError} When request parameters are invalid
1080
- * @throws {UnauthorizedError} When authentication fails
1081
- * @throws {ForbiddenError} When access is denied
1082
- * @throws {InternalServerError} When server encounters an error
1083
- */
1084
- async receiveMessage(messageId, handler) {
1085
- const response = await this.client.receiveMessageById(
1086
- {
1087
- queueName: this.topicName,
1088
- consumerGroup: this.consumerGroupName,
1089
- messageId,
1090
- visibilityTimeoutSeconds: this.visibilityTimeout
1091
- },
1092
- this.transport
1093
- );
1094
- await this.processMessage(response.message, handler);
1095
- }
1096
- /**
1097
- * Receive and process the next available message from the queue
1098
- * @param handler Function to process the message
1099
- * @returns Promise that resolves when the message is processed or rejects with specific errors
1100
- * @throws {QueueEmptyError} When no messages are available in the queue (204)
1101
- * @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
1102
- * @throws {BadRequestError} When request parameters are invalid
1103
- * @throws {UnauthorizedError} When authentication fails
1104
- * @throws {ForbiddenError} When access is denied
1105
- * @throws {InternalServerError} When server encounters an error
1106
- */
1107
- async receiveNextMessage(handler) {
1108
- let messageFound = false;
1109
- for await (const message of this.client.receiveMessages(
1110
- {
1111
- queueName: this.topicName,
1112
- consumerGroup: this.consumerGroupName,
1113
- visibilityTimeoutSeconds: this.visibilityTimeout,
1114
- limit: 1
1115
- },
1116
- this.transport
1117
- )) {
1118
- messageFound = true;
1119
- await this.processMessage(message, handler);
1120
- break;
1121
- }
1122
- if (!messageFound) {
1123
- throw new Error("No messages available");
1124
- }
1125
- }
1126
- /**
1127
- * Receive and process multiple next available messages from the queue
1128
- * @param limit Number of messages to process (1-10)
1129
- * @param handler Function to process each message
1130
- * @returns Promise that resolves to an array of PromiseSettledResult (same as Promise.allSettled)
1131
- * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
1132
- * @throws {QueueEmptyError} When no messages are available in the queue (204)
1133
- * @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
1134
- * @throws {BadRequestError} When request parameters are invalid
1135
- * @throws {UnauthorizedError} When authentication fails
1136
- * @throws {ForbiddenError} When access is denied
1137
- * @throws {InternalServerError} When server encounters an error
1138
- */
1139
- async receiveNextMessages(limit, handler) {
1140
- if (limit < 1 || limit > 10) {
1141
- throw new InvalidLimitError(limit);
1142
- }
1143
- const processingPromises = [];
1144
- let messageCount = 0;
1145
- for await (const message of this.client.receiveMessages(
1146
- {
1147
- queueName: this.topicName,
1148
- consumerGroup: this.consumerGroupName,
1149
- visibilityTimeoutSeconds: this.visibilityTimeout,
1150
- limit
1151
- },
1152
- this.transport
1153
- )) {
1154
- messageCount++;
1155
- const wrappedPromise = this.processMessage(message, handler).then(
1156
- (value) => ({
1157
- status: "fulfilled",
1158
- value
1159
- }),
1160
- (reason) => ({ status: "rejected", reason })
1161
- );
1162
- processingPromises.push(wrappedPromise);
1163
- }
1164
- if (messageCount === 0) {
1165
- throw new Error("No messages available");
1166
- }
1167
- const results = await Promise.all(processingPromises);
1168
- return results;
1169
- }
1170
- /**
1171
- * Handle a specific message by its ID without downloading the payload (metadata only)
1172
- * @param messageId The ID of the message to handle
1173
- * @param handler Function to process the message metadata (payload will be void)
1174
- * @returns Promise that resolves when the message is handled or rejects with specific errors
1175
- * @throws {MessageNotFoundError} When the message doesn't exist (404)
1176
- * @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
1177
- * @throws {MessageLockedError} When the message is temporarily locked (423)
1178
- * @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
1179
- * @throws {FailedDependencyError} When FIFO ordering is violated (424)
1180
- * @throws {MessageCorruptedError} When the message data is corrupted
1181
- * @throws {BadRequestError} When request parameters are invalid
1182
- * @throws {UnauthorizedError} When authentication fails
1183
- * @throws {ForbiddenError} When access is denied
1184
- * @throws {InternalServerError} When server encounters an error
1185
- */
1186
- async handleMessage(messageId, handler) {
1187
- const response = await this.client.receiveMessageById(
1188
- {
1189
- queueName: this.topicName,
1190
- consumerGroup: this.consumerGroupName,
1191
- messageId,
1192
- visibilityTimeoutSeconds: this.visibilityTimeout,
1193
- skipPayload: true
1194
- },
1195
- this.transport
1196
- );
1197
- await this.processMessage(response.message, handler);
1198
- }
1199
1020
  /**
1200
1021
  * Get the consumer group name
1201
1022
  */
@@ -1282,9 +1103,43 @@ var Topic = class {
1282
1103
  };
1283
1104
 
1284
1105
  // src/factory.ts
1285
- function createTopic(client, topicName, transport) {
1106
+ function createTopic(topicName, transport) {
1107
+ const client = QueueClient._getDefaultInstance();
1286
1108
  return new Topic(client, topicName, transport);
1287
1109
  }
1110
+ async function send(topicName, payload, options) {
1111
+ const transport = options?.transport || new JsonTransport();
1112
+ const client = QueueClient._getDefaultInstance();
1113
+ const result = await client.sendMessage(
1114
+ {
1115
+ queueName: topicName,
1116
+ payload,
1117
+ idempotencyKey: options?.idempotencyKey,
1118
+ retentionSeconds: options?.retentionSeconds,
1119
+ callback: options?.callback
1120
+ },
1121
+ transport
1122
+ );
1123
+ return { messageId: result.messageId };
1124
+ }
1125
+ async function receive(topicName, consumerGroup, handler, options) {
1126
+ const transport = options?.transport || new JsonTransport();
1127
+ const topic = createTopic(topicName, transport);
1128
+ const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
1129
+ const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
1130
+ if (messageId) {
1131
+ if (skipPayload) {
1132
+ return consumer.consume(handler, {
1133
+ messageId,
1134
+ skipPayload: true
1135
+ });
1136
+ } else {
1137
+ return consumer.consume(handler, { messageId });
1138
+ }
1139
+ } else {
1140
+ return consumer.consume(handler);
1141
+ }
1142
+ }
1288
1143
 
1289
1144
  // src/callback.ts
1290
1145
  function parseCallbackRequest(request) {
@@ -1333,17 +1188,10 @@ function handleCallback(handlers) {
1333
1188
  }
1334
1189
  actualHandler = consumerGroupHandler;
1335
1190
  }
1336
- const client = await QueueClient.fromVercelFunction();
1191
+ const client = new QueueClient();
1337
1192
  const topic = new Topic(client, queueName);
1338
1193
  const cg = topic.consumerGroup(consumerGroup);
1339
- await cg.receiveMessage(messageId, async (message) => {
1340
- const metadata = {
1341
- messageId: message.messageId,
1342
- deliveryCount: message.deliveryCount,
1343
- timestamp: message.timestamp
1344
- };
1345
- return await actualHandler(message.payload, metadata);
1346
- });
1194
+ await cg.consume(actualHandler, { messageId });
1347
1195
  return Response.json({ status: "success" });
1348
1196
  } catch (error) {
1349
1197
  console.error("Callback error:", error);
@@ -1382,8 +1230,9 @@ function handleCallback(handlers) {
1382
1230
  Topic,
1383
1231
  UnauthorizedError,
1384
1232
  createTopic,
1385
- getVercelOidcToken,
1386
1233
  handleCallback,
1387
- parseCallbackRequest
1234
+ parseCallbackRequest,
1235
+ receive,
1236
+ send
1388
1237
  });
1389
1238
  //# sourceMappingURL=index.js.map