@vercel/queue 0.0.0-alpha.1 → 0.0.0-alpha.10

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
@@ -22,12 +22,8 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  BadRequestError: () => BadRequestError,
24
24
  BufferTransport: () => BufferTransport,
25
- ConsumerGroup: () => ConsumerGroup,
26
- FailedDependencyError: () => FailedDependencyError,
27
- FifoOrderingViolationError: () => FifoOrderingViolationError,
28
25
  ForbiddenError: () => ForbiddenError,
29
26
  InternalServerError: () => InternalServerError,
30
- InvalidCallbackError: () => InvalidCallbackError,
31
27
  InvalidLimitError: () => InvalidLimitError,
32
28
  JsonTransport: () => JsonTransport,
33
29
  MessageCorruptedError: () => MessageCorruptedError,
@@ -36,140 +32,98 @@ __export(index_exports, {
36
32
  MessageNotFoundError: () => MessageNotFoundError,
37
33
  QueueEmptyError: () => QueueEmptyError,
38
34
  StreamTransport: () => StreamTransport,
39
- Topic: () => Topic,
40
35
  UnauthorizedError: () => UnauthorizedError,
41
- VQSClient: () => VQSClient,
42
- createTopic: () => createTopic,
43
- getVercelOidcToken: () => getVercelOidcToken,
44
- parseCallbackRequest: () => parseCallbackRequest
36
+ handleCallback: () => handleCallback,
37
+ receive: () => receive,
38
+ send: () => send
45
39
  });
46
40
  module.exports = __toCommonJS(index_exports);
47
41
 
48
- // src/oidc.ts
49
- async function getVercelOidcToken() {
50
- const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
51
- const fromSymbol = globalThis;
52
- const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
53
- const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
54
- if (!token) {
55
- throw new Error(
56
- `The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?`
57
- );
42
+ // src/transports.ts
43
+ var JsonTransport = class {
44
+ contentType = "application/json";
45
+ serialize(value) {
46
+ return Buffer.from(JSON.stringify(value), "utf8");
58
47
  }
59
- return token;
60
- }
61
-
62
- // src/client.ts
63
- var import_mixpart = require("mixpart");
64
-
65
- // src/local.ts
66
- var import_node_child_process = require("child_process");
67
- function isLocalhostWithPort(url) {
68
- try {
69
- const parsedUrl = new URL(url);
70
- const isLocalhost = parsedUrl.hostname === "localhost";
71
- const port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 0;
72
- return { isLocalhost, port };
73
- } catch {
74
- return { isLocalhost: false };
48
+ async deserialize(stream) {
49
+ const reader = stream.getReader();
50
+ let totalLength = 0;
51
+ const chunks = [];
52
+ try {
53
+ while (true) {
54
+ const { done, value } = await reader.read();
55
+ if (done) break;
56
+ chunks.push(value);
57
+ totalLength += value.length;
58
+ }
59
+ } finally {
60
+ reader.releaseLock();
61
+ }
62
+ const buffer = Buffer.concat(chunks, totalLength);
63
+ return JSON.parse(buffer.toString("utf8"));
75
64
  }
76
- }
77
- function isSupportedPlatform() {
78
- const platform = process.platform;
79
- return platform === "darwin" || platform === "linux";
80
- }
81
- function processDevelopmentCallbacks(callbacks) {
82
- const isDevelopment = process.env.NODE_ENV === "development";
83
- if (!isDevelopment) {
84
- return [];
65
+ };
66
+ var BufferTransport = class {
67
+ contentType = "application/octet-stream";
68
+ serialize(value) {
69
+ return value;
85
70
  }
86
- if (!isSupportedPlatform()) {
87
- const hasLocalhostCallbacks = Object.values(callbacks).some((config) => {
88
- const { isLocalhost } = isLocalhostWithPort(config.url);
89
- return isLocalhost;
90
- });
91
- if (hasLocalhostCallbacks) {
92
- console.warn(
93
- `VQS 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.`
94
- );
71
+ async deserialize(stream) {
72
+ const reader = stream.getReader();
73
+ const chunks = [];
74
+ try {
75
+ while (true) {
76
+ const { done, value } = await reader.read();
77
+ if (done) break;
78
+ chunks.push(value);
79
+ }
80
+ } finally {
81
+ reader.releaseLock();
95
82
  }
96
- return [];
97
- }
98
- const localhostCallbacks = [];
99
- Object.entries(callbacks).forEach(([group, config]) => {
100
- const { isLocalhost, port } = isLocalhostWithPort(config.url);
101
- if (isLocalhost && port && port > 0) {
102
- localhostCallbacks.push({ group, config, port });
103
- } else {
104
- console.warn(
105
- `VQS Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
106
- );
83
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
84
+ const buffer = new Uint8Array(totalLength);
85
+ let offset = 0;
86
+ for (const chunk of chunks) {
87
+ buffer.set(chunk, offset);
88
+ offset += chunk.length;
107
89
  }
108
- });
109
- return localhostCallbacks;
110
- }
111
- function fireLocalhostCallbacks(localhostCallbacks, queueName, responseData) {
112
- localhostCallbacks.forEach(({ group, config, port }) => {
113
- const callbackHeaders = new Headers();
114
- callbackHeaders.set("Vqs-Message-Id", responseData.messageId);
115
- callbackHeaders.set("Vqs-Queue-Name", queueName);
116
- callbackHeaders.set("Vqs-Consumer-Group", group);
117
- fireAndForgetWaitForHttpReady(
118
- config.url,
119
- port,
120
- config.delay || 0,
121
- 3,
122
- // Default retry frequency
123
- callbackHeaders
124
- );
125
- });
126
- }
127
- function fireAndForgetWaitForHttpReady(url, port, initialDelaySeconds = 0, retryFrequencySeconds = 3, headers) {
128
- if (!isSupportedPlatform()) {
129
- console.warn(
130
- `VQS: fireAndForgetWaitForHttpReady is not supported on ${process.platform}. This function requires bash, nc, and curl which are available on macOS and Linux only.`
131
- );
132
- return;
90
+ return Buffer.from(buffer);
133
91
  }
134
- let headerArgs = "";
135
- if (headers) {
136
- const headerArray = [];
137
- headers.forEach((value, key) => {
138
- headerArray.push(`-H '${key}: ${value}'`);
139
- });
140
- headerArgs = headerArray.join(" ");
92
+ };
93
+ var StreamTransport = class {
94
+ contentType = "application/octet-stream";
95
+ serialize(value) {
96
+ return value;
141
97
  }
142
- const bashScript = `
143
- # Wait for any initial boot time
144
- sleep ${initialDelaySeconds}
98
+ async deserialize(stream) {
99
+ return stream;
100
+ }
101
+ async finalize(payload) {
102
+ const reader = payload.getReader();
103
+ try {
104
+ while (true) {
105
+ const { done } = await reader.read();
106
+ if (done) break;
107
+ }
108
+ } finally {
109
+ reader.releaseLock();
110
+ }
111
+ }
112
+ };
113
+
114
+ // src/client.ts
115
+ var import_mixpart = require("mixpart");
145
116
 
146
- missed=0
147
- while true; do
148
- # 1) Check if TCP port is listening
149
- if nc -z localhost ${port} 2>/dev/null; then
150
- missed=0
151
- # 2) If port is open, try HTTP POST check
152
- if curl -sSL --fail -o /dev/null -X POST ${headerArgs} "${url}"; then
153
- # Success: port is up AND HTTP returned 2xx (following redirects)
154
- exit 0
155
- fi
156
- else
157
- # Port was closed\u2014increment miss counter
158
- ((missed+=1))
159
- # If closed twice in a row, give up immediately
160
- if [ "$missed" -ge 2 ]; then
161
- exit 1
162
- fi
163
- fi
164
- # Wait before next cycle
165
- sleep ${retryFrequencySeconds}
166
- done
167
- `;
168
- const childProcess = (0, import_node_child_process.spawn)("bash", ["-c", bashScript], {
169
- stdio: "ignore",
170
- detached: true
171
- });
172
- childProcess.unref();
117
+ // src/oidc.ts
118
+ function getVercelOidcToken() {
119
+ const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
120
+ const fromSymbol = globalThis;
121
+ const context = fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
122
+ const token = context.headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
123
+ if (!token) {
124
+ return null;
125
+ }
126
+ return token;
173
127
  }
174
128
 
175
129
  // src/types.ts
@@ -187,16 +141,6 @@ var MessageNotAvailableError = class extends Error {
187
141
  this.name = "MessageNotAvailableError";
188
142
  }
189
143
  };
190
- var FifoOrderingViolationError = class extends Error {
191
- nextMessageId;
192
- constructor(messageId, nextMessageId, reason) {
193
- super(
194
- `FIFO ordering violation for message ${messageId}: ${reason}. Process message ${nextMessageId} first.`
195
- );
196
- this.name = "FifoOrderingViolationError";
197
- this.nextMessageId = nextMessageId;
198
- }
199
- };
200
144
  var MessageCorruptedError = class extends Error {
201
145
  constructor(messageId, reason) {
202
146
  super(`Message ${messageId} is corrupted: ${reason}`);
@@ -238,16 +182,6 @@ var BadRequestError = class extends Error {
238
182
  this.name = "BadRequestError";
239
183
  }
240
184
  };
241
- var FailedDependencyError = class extends Error {
242
- nextMessageId;
243
- constructor(messageId, nextMessageId) {
244
- super(
245
- `Failed dependency: FIFO ordering violation for message ${messageId}. Must process message ${nextMessageId} first.`
246
- );
247
- this.name = "FailedDependencyError";
248
- this.nextMessageId = nextMessageId;
249
- }
250
- };
251
185
  var InternalServerError = class extends Error {
252
186
  constructor(message = "Unexpected server error") {
253
187
  super(message);
@@ -260,12 +194,6 @@ var InvalidLimitError = class extends Error {
260
194
  this.name = "InvalidLimitError";
261
195
  }
262
196
  };
263
- var InvalidCallbackError = class extends Error {
264
- constructor(message) {
265
- super(message);
266
- this.name = "InvalidCallbackError";
267
- }
268
- };
269
197
 
270
198
  // src/client.ts
271
199
  async function consumeStream(stream) {
@@ -279,7 +207,7 @@ async function consumeStream(stream) {
279
207
  reader.releaseLock();
280
208
  }
281
209
  }
282
- function parseVQSHeaders(headers) {
210
+ function parseQueueHeaders(headers) {
283
211
  const messageId = headers.get("Vqs-Message-Id");
284
212
  const deliveryCountStr = headers.get("Vqs-Delivery-Count") || "0";
285
213
  const timestamp = headers.get("Vqs-Timestamp");
@@ -295,40 +223,33 @@ function parseVQSHeaders(headers) {
295
223
  return {
296
224
  messageId,
297
225
  deliveryCount,
298
- timestamp,
226
+ createdAt: new Date(timestamp),
299
227
  contentType,
300
228
  ticket
301
229
  };
302
230
  }
303
- var VQSClient = class _VQSClient {
231
+ var QueueClient = class {
304
232
  baseUrl;
233
+ basePath;
305
234
  token;
306
235
  /**
307
236
  * Create a new Vercel Queue Service client
308
- * @param options Client configuration options
237
+ * @param options Client configuration options (optional - will auto-detect Vercel Function environment)
309
238
  */
310
- constructor(options) {
311
- this.baseUrl = options.baseUrl || "https://vqs.vercel.sh";
312
- this.token = options.token;
313
- }
314
- /**
315
- * Create a VQSClient automatically configured for Vercel Functions
316
- * This method automatically retrieves the OIDC token from the Vercel Function environment
317
- * Always creates a fresh instance since OIDC tokens expire after 15 minutes
318
- * @param baseUrl Optional base URL override
319
- * @returns Promise resolving to a new VQSClient instance
320
- */
321
- static async fromVercelFunction(baseUrl) {
322
- const token = await getVercelOidcToken();
323
- if (!token) {
324
- throw new Error(
325
- "Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment."
326
- );
239
+ constructor(options = {}) {
240
+ this.baseUrl = options.baseUrl || "https://api.vercel.com";
241
+ this.basePath = options.basePath || "/v1/queues/messages";
242
+ if (options.token) {
243
+ this.token = options.token;
244
+ } else {
245
+ const token = getVercelOidcToken();
246
+ if (!token) {
247
+ throw new Error(
248
+ "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'"
249
+ );
250
+ }
251
+ this.token = token;
327
252
  }
328
- return new _VQSClient({
329
- token,
330
- baseUrl
331
- });
332
253
  }
333
254
  /**
334
255
  * Send a message to a queue
@@ -341,40 +262,23 @@ var VQSClient = class _VQSClient {
341
262
  * @throws {InternalServerError} When server encounters an error
342
263
  */
343
264
  async sendMessage(options, transport) {
344
- const { queueName, payload, idempotencyKey, retentionSeconds, callbacks } = options;
265
+ const { queueName, payload, idempotencyKey, retentionSeconds } = options;
345
266
  const headers = new Headers({
346
267
  Authorization: `Bearer ${this.token}`,
347
268
  "Vqs-Queue-Name": queueName,
348
269
  "Content-Type": transport.contentType
349
270
  });
271
+ if (process.env.VERCEL_DEPLOYMENT_ID) {
272
+ headers.set("Vqs-Deployment-Id", process.env.VERCEL_DEPLOYMENT_ID);
273
+ }
350
274
  if (idempotencyKey) {
351
275
  headers.set("Vqs-Idempotency-Key", idempotencyKey);
352
276
  }
353
277
  if (retentionSeconds !== void 0) {
354
278
  headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
355
279
  }
356
- let localhostCallbacks = [];
357
- if (callbacks) {
358
- const isDevelopment = process.env.NODE_ENV === "development";
359
- if (isDevelopment) {
360
- localhostCallbacks = processDevelopmentCallbacks(callbacks);
361
- } else {
362
- const endpoints = Object.entries(callbacks).map(
363
- ([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
364
- ).join(",");
365
- headers.set("Vqs-Callback-Url", endpoints);
366
- const delays = Object.entries(callbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
367
- if (delays) {
368
- headers.set("Vqs-Callback-Delay", delays);
369
- }
370
- const frequencies = Object.entries(callbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
371
- if (frequencies) {
372
- headers.set("Vqs-Callback-Frequency", frequencies);
373
- }
374
- }
375
- }
376
280
  const body = transport.serialize(payload);
377
- const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
281
+ const response = await fetch(`${this.baseUrl}${this.basePath}`, {
378
282
  method: "POST",
379
283
  headers,
380
284
  body
@@ -403,9 +307,6 @@ var VQSClient = class _VQSClient {
403
307
  );
404
308
  }
405
309
  const responseData = await response.json();
406
- if (localhostCallbacks.length > 0) {
407
- fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
408
- }
409
310
  return responseData;
410
311
  }
411
312
  /**
@@ -415,7 +316,7 @@ var VQSClient = class _VQSClient {
415
316
  * @returns AsyncGenerator that yields messages as they arrive
416
317
  * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
417
318
  * @throws {QueueEmptyError} When no messages are available (204)
418
- * @throws {MessageLockedError} When FIFO queue has locked messages (423)
319
+ * @throws {MessageLockedError} When messages are temporarily locked (423)
419
320
  * @throws {BadRequestError} When request parameters are invalid
420
321
  * @throws {UnauthorizedError} When authentication fails
421
322
  * @throws {ForbiddenError} When access is denied (environment mismatch)
@@ -441,7 +342,7 @@ var VQSClient = class _VQSClient {
441
342
  if (limit !== void 0) {
442
343
  headers.set("Vqs-Limit", limit.toString());
443
344
  }
444
- const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
345
+ const response = await fetch(`${this.baseUrl}${this.basePath}`, {
445
346
  method: "GET",
446
347
  headers
447
348
  });
@@ -466,7 +367,7 @@ var VQSClient = class _VQSClient {
466
367
  const parsed = parseInt(retryAfterHeader, 10);
467
368
  retryAfter = isNaN(parsed) ? void 0 : parsed;
468
369
  }
469
- throw new MessageLockedError("next message in FIFO queue", retryAfter);
370
+ throw new MessageLockedError("next message", retryAfter);
470
371
  }
471
372
  if (response.status >= 500) {
472
373
  throw new InternalServerError(
@@ -479,9 +380,9 @@ var VQSClient = class _VQSClient {
479
380
  }
480
381
  for await (const multipartMessage of (0, import_mixpart.parseMultipartStream)(response)) {
481
382
  try {
482
- const parsedHeaders = parseVQSHeaders(multipartMessage.headers);
383
+ const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
483
384
  if (!parsedHeaders) {
484
- console.warn("Missing required VQS headers in multipart part");
385
+ console.warn("Missing required queue headers in multipart part");
485
386
  await consumeStream(multipartMessage.payload);
486
387
  continue;
487
388
  }
@@ -523,7 +424,7 @@ var VQSClient = class _VQSClient {
523
424
  headers.set("Vqs-Skip-Payload", "1");
524
425
  }
525
426
  const response = await fetch(
526
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
427
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
527
428
  {
528
429
  method: "GET",
529
430
  headers
@@ -552,37 +453,7 @@ var VQSClient = class _VQSClient {
552
453
  }
553
454
  throw new MessageLockedError(messageId, retryAfter);
554
455
  }
555
- if (response.status === 424) {
556
- try {
557
- const errorData = await response.json();
558
- if (errorData.meta?.nextMessageId) {
559
- throw new FailedDependencyError(
560
- messageId,
561
- errorData.meta.nextMessageId
562
- );
563
- }
564
- } catch (parseError) {
565
- if (parseError instanceof FailedDependencyError) {
566
- throw parseError;
567
- }
568
- }
569
- throw new MessageNotAvailableError(
570
- messageId,
571
- "FIFO ordering violation"
572
- );
573
- }
574
456
  if (response.status === 409) {
575
- try {
576
- const errorData = await response.json();
577
- if (errorData.nextMessageId) {
578
- throw new FifoOrderingViolationError(
579
- messageId,
580
- errorData.nextMessageId,
581
- errorData.error
582
- );
583
- }
584
- } catch (parseError) {
585
- }
586
457
  throw new MessageNotAvailableError(messageId);
587
458
  }
588
459
  if (response.status >= 500) {
@@ -595,11 +466,11 @@ var VQSClient = class _VQSClient {
595
466
  );
596
467
  }
597
468
  if (skipPayload && response.status === 204) {
598
- const parsedHeaders = parseVQSHeaders(response.headers);
469
+ const parsedHeaders = parseQueueHeaders(response.headers);
599
470
  if (!parsedHeaders) {
600
471
  throw new MessageCorruptedError(
601
472
  messageId,
602
- "Missing required VQS headers in 204 response"
473
+ "Missing required queue headers in 204 response"
603
474
  );
604
475
  }
605
476
  const message = {
@@ -614,9 +485,9 @@ var VQSClient = class _VQSClient {
614
485
  try {
615
486
  for await (const multipartMessage of (0, import_mixpart.parseMultipartStream)(response)) {
616
487
  try {
617
- const parsedHeaders = parseVQSHeaders(multipartMessage.headers);
488
+ const parsedHeaders = parseQueueHeaders(multipartMessage.headers);
618
489
  if (!parsedHeaders) {
619
- console.warn("Missing required VQS headers in multipart part");
490
+ console.warn("Missing required queue headers in multipart part");
620
491
  await consumeStream(multipartMessage.payload);
621
492
  continue;
622
493
  }
@@ -662,7 +533,7 @@ var VQSClient = class _VQSClient {
662
533
  async deleteMessage(options) {
663
534
  const { queueName, consumerGroup, messageId, ticket } = options;
664
535
  const response = await fetch(
665
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
536
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
666
537
  {
667
538
  method: "DELETE",
668
539
  headers: new Headers({
@@ -723,7 +594,7 @@ var VQSClient = class _VQSClient {
723
594
  visibilityTimeoutSeconds
724
595
  } = options;
725
596
  const response = await fetch(
726
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
597
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
727
598
  {
728
599
  method: "PATCH",
729
600
  headers: new Headers({
@@ -769,82 +640,6 @@ var VQSClient = class _VQSClient {
769
640
  }
770
641
  };
771
642
 
772
- // src/transports.ts
773
- var JsonTransport = class {
774
- contentType = "application/json";
775
- serialize(value) {
776
- return Buffer.from(JSON.stringify(value), "utf8");
777
- }
778
- async deserialize(stream) {
779
- const reader = stream.getReader();
780
- const chunks = [];
781
- try {
782
- while (true) {
783
- const { done, value } = await reader.read();
784
- if (done) break;
785
- chunks.push(value);
786
- }
787
- } finally {
788
- reader.releaseLock();
789
- }
790
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
791
- const buffer = new Uint8Array(totalLength);
792
- let offset = 0;
793
- for (const chunk of chunks) {
794
- buffer.set(chunk, offset);
795
- offset += chunk.length;
796
- }
797
- return JSON.parse(Buffer.from(buffer).toString("utf8"));
798
- }
799
- };
800
- var BufferTransport = class {
801
- contentType = "application/octet-stream";
802
- serialize(value) {
803
- return value;
804
- }
805
- async deserialize(stream) {
806
- const reader = stream.getReader();
807
- const chunks = [];
808
- try {
809
- while (true) {
810
- const { done, value } = await reader.read();
811
- if (done) break;
812
- chunks.push(value);
813
- }
814
- } finally {
815
- reader.releaseLock();
816
- }
817
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
818
- const buffer = new Uint8Array(totalLength);
819
- let offset = 0;
820
- for (const chunk of chunks) {
821
- buffer.set(chunk, offset);
822
- offset += chunk.length;
823
- }
824
- return Buffer.from(buffer);
825
- }
826
- };
827
- var StreamTransport = class {
828
- contentType = "application/octet-stream";
829
- serialize(value) {
830
- return value;
831
- }
832
- async deserialize(stream) {
833
- return stream;
834
- }
835
- async finalize(payload) {
836
- const reader = payload.getReader();
837
- try {
838
- while (true) {
839
- const { done } = await reader.read();
840
- if (done) break;
841
- }
842
- } finally {
843
- reader.releaseLock();
844
- }
845
- }
846
- };
847
-
848
643
  // src/consumer-group.ts
849
644
  var ConsumerGroup = class {
850
645
  client;
@@ -855,7 +650,7 @@ var ConsumerGroup = class {
855
650
  transport;
856
651
  /**
857
652
  * Create a new ConsumerGroup instance
858
- * @param client VQSClient instance to use for API calls
653
+ * @param client QueueClient instance to use for API calls
859
654
  * @param topicName Name of the topic to consume from
860
655
  * @param consumerGroupName Name of the consumer group
861
656
  * @param options Optional configuration
@@ -944,7 +739,11 @@ var ConsumerGroup = class {
944
739
  message.ticket
945
740
  );
946
741
  try {
947
- const result = await handler(message);
742
+ const result = await handler(message.payload, {
743
+ messageId: message.messageId,
744
+ deliveryCount: message.deliveryCount,
745
+ createdAt: message.createdAt
746
+ });
948
747
  await stopExtension();
949
748
  if (result && "timeoutSeconds" in result) {
950
749
  await this.client.changeVisibility({
@@ -974,219 +773,58 @@ var ConsumerGroup = class {
974
773
  throw error;
975
774
  }
976
775
  }
977
- /**
978
- * Start continuous processing of messages from the topic
979
- * @param signal AbortSignal to control when to stop processing
980
- * @param handler Function to process each message
981
- * @param options Processing options
982
- * @returns Promise that resolves when processing stops (due to signal or error)
983
- */
984
- async subscribe(signal, handler, options = {}) {
985
- const pollingInterval = options.pollingInterval || 1e3;
986
- while (!signal.aborted) {
987
- try {
988
- for await (const message of this.client.receiveMessages(
776
+ async consume(handler, options) {
777
+ if (options?.messageId) {
778
+ if (options.skipPayload) {
779
+ const response = await this.client.receiveMessageById(
989
780
  {
990
781
  queueName: this.topicName,
991
782
  consumerGroup: this.consumerGroupName,
783
+ messageId: options.messageId,
992
784
  visibilityTimeoutSeconds: this.visibilityTimeout,
993
- limit: 1
994
- // Always process one message at a time
785
+ skipPayload: true
995
786
  },
996
787
  this.transport
997
- )) {
998
- if (signal.aborted) {
999
- break;
1000
- }
1001
- try {
1002
- await this.processMessage(message, handler);
1003
- } catch (error) {
1004
- console.error("Error processing message:", error);
1005
- }
1006
- }
1007
- if (!signal.aborted) {
1008
- await new Promise((resolve) => {
1009
- const timeoutId = setTimeout(resolve, pollingInterval);
1010
- signal.addEventListener(
1011
- "abort",
1012
- () => {
1013
- clearTimeout(timeoutId);
1014
- resolve();
1015
- },
1016
- { once: true }
1017
- );
1018
- });
1019
- }
1020
- } catch (error) {
1021
- if (error instanceof QueueEmptyError) {
1022
- if (!signal.aborted) {
1023
- await new Promise((resolve) => {
1024
- const timeoutId = setTimeout(resolve, pollingInterval);
1025
- signal.addEventListener(
1026
- "abort",
1027
- () => {
1028
- clearTimeout(timeoutId);
1029
- resolve();
1030
- },
1031
- { once: true }
1032
- );
1033
- });
1034
- }
1035
- continue;
1036
- }
1037
- if (error instanceof MessageLockedError) {
1038
- const waitTime = error.retryAfter ? error.retryAfter * 1e3 : pollingInterval;
1039
- if (!signal.aborted) {
1040
- await new Promise((resolve) => {
1041
- const timeoutId = setTimeout(resolve, waitTime);
1042
- signal.addEventListener(
1043
- "abort",
1044
- () => {
1045
- clearTimeout(timeoutId);
1046
- resolve();
1047
- },
1048
- { once: true }
1049
- );
1050
- });
1051
- }
1052
- continue;
1053
- }
1054
- console.error("Error polling topic:", error);
1055
- throw error;
788
+ );
789
+ await this.processMessage(
790
+ response.message,
791
+ handler
792
+ );
793
+ } else {
794
+ const response = await this.client.receiveMessageById(
795
+ {
796
+ queueName: this.topicName,
797
+ consumerGroup: this.consumerGroupName,
798
+ messageId: options.messageId,
799
+ visibilityTimeoutSeconds: this.visibilityTimeout
800
+ },
801
+ this.transport
802
+ );
803
+ await this.processMessage(
804
+ response.message,
805
+ handler
806
+ );
807
+ }
808
+ } else {
809
+ let messageFound = false;
810
+ for await (const message of this.client.receiveMessages(
811
+ {
812
+ queueName: this.topicName,
813
+ consumerGroup: this.consumerGroupName,
814
+ visibilityTimeoutSeconds: this.visibilityTimeout,
815
+ limit: 1
816
+ },
817
+ this.transport
818
+ )) {
819
+ messageFound = true;
820
+ await this.processMessage(message, handler);
821
+ break;
822
+ }
823
+ if (!messageFound) {
824
+ throw new Error("No messages available");
1056
825
  }
1057
826
  }
1058
827
  }
1059
- /**
1060
- * Receive and process a specific message by its ID with full payload
1061
- * @param messageId The ID of the message to receive and process
1062
- * @param handler Function to process the message with full payload
1063
- * @returns Promise that resolves when the message is processed or rejects with specific errors
1064
- * @throws {MessageNotFoundError} When the message doesn't exist (404)
1065
- * @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
1066
- * @throws {MessageLockedError} When the message is temporarily locked (423)
1067
- * @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
1068
- * @throws {FailedDependencyError} When FIFO ordering is violated (424)
1069
- * @throws {MessageCorruptedError} When the message data is corrupted
1070
- * @throws {BadRequestError} When request parameters are invalid
1071
- * @throws {UnauthorizedError} When authentication fails
1072
- * @throws {ForbiddenError} When access is denied
1073
- * @throws {InternalServerError} When server encounters an error
1074
- */
1075
- async receiveMessage(messageId, handler) {
1076
- const response = await this.client.receiveMessageById(
1077
- {
1078
- queueName: this.topicName,
1079
- consumerGroup: this.consumerGroupName,
1080
- messageId,
1081
- visibilityTimeoutSeconds: this.visibilityTimeout
1082
- },
1083
- this.transport
1084
- );
1085
- await this.processMessage(response.message, handler);
1086
- }
1087
- /**
1088
- * Receive and process the next available message from the queue
1089
- * @param handler Function to process the message
1090
- * @returns Promise that resolves when the message is processed or rejects with specific errors
1091
- * @throws {QueueEmptyError} When no messages are available in the queue (204)
1092
- * @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
1093
- * @throws {BadRequestError} When request parameters are invalid
1094
- * @throws {UnauthorizedError} When authentication fails
1095
- * @throws {ForbiddenError} When access is denied
1096
- * @throws {InternalServerError} When server encounters an error
1097
- */
1098
- async receiveNextMessage(handler) {
1099
- let messageFound = false;
1100
- for await (const message of this.client.receiveMessages(
1101
- {
1102
- queueName: this.topicName,
1103
- consumerGroup: this.consumerGroupName,
1104
- visibilityTimeoutSeconds: this.visibilityTimeout,
1105
- limit: 1
1106
- },
1107
- this.transport
1108
- )) {
1109
- messageFound = true;
1110
- await this.processMessage(message, handler);
1111
- break;
1112
- }
1113
- if (!messageFound) {
1114
- throw new Error("No messages available");
1115
- }
1116
- }
1117
- /**
1118
- * Receive and process multiple next available messages from the queue
1119
- * @param limit Number of messages to process (1-10)
1120
- * @param handler Function to process each message
1121
- * @returns Promise that resolves to an array of PromiseSettledResult (same as Promise.allSettled)
1122
- * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
1123
- * @throws {QueueEmptyError} When no messages are available in the queue (204)
1124
- * @throws {MessageLockedError} When the next message in a FIFO queue is locked (423)
1125
- * @throws {BadRequestError} When request parameters are invalid
1126
- * @throws {UnauthorizedError} When authentication fails
1127
- * @throws {ForbiddenError} When access is denied
1128
- * @throws {InternalServerError} When server encounters an error
1129
- */
1130
- async receiveNextMessages(limit, handler) {
1131
- if (limit < 1 || limit > 10) {
1132
- throw new InvalidLimitError(limit);
1133
- }
1134
- const processingPromises = [];
1135
- let messageCount = 0;
1136
- for await (const message of this.client.receiveMessages(
1137
- {
1138
- queueName: this.topicName,
1139
- consumerGroup: this.consumerGroupName,
1140
- visibilityTimeoutSeconds: this.visibilityTimeout,
1141
- limit
1142
- },
1143
- this.transport
1144
- )) {
1145
- messageCount++;
1146
- const wrappedPromise = this.processMessage(message, handler).then(
1147
- (value) => ({
1148
- status: "fulfilled",
1149
- value
1150
- }),
1151
- (reason) => ({ status: "rejected", reason })
1152
- );
1153
- processingPromises.push(wrappedPromise);
1154
- }
1155
- if (messageCount === 0) {
1156
- throw new Error("No messages available");
1157
- }
1158
- const results = await Promise.all(processingPromises);
1159
- return results;
1160
- }
1161
- /**
1162
- * Handle a specific message by its ID without downloading the payload (metadata only)
1163
- * @param messageId The ID of the message to handle
1164
- * @param handler Function to process the message metadata (payload will be void)
1165
- * @returns Promise that resolves when the message is handled or rejects with specific errors
1166
- * @throws {MessageNotFoundError} When the message doesn't exist (404)
1167
- * @throws {MessageNotAvailableError} When the message exists but isn't available for processing (409)
1168
- * @throws {MessageLockedError} When the message is temporarily locked (423)
1169
- * @throws {FifoOrderingViolationError} When there's a FIFO ordering violation (409 with nextMessageId)
1170
- * @throws {FailedDependencyError} When FIFO ordering is violated (424)
1171
- * @throws {MessageCorruptedError} When the message data is corrupted
1172
- * @throws {BadRequestError} When request parameters are invalid
1173
- * @throws {UnauthorizedError} When authentication fails
1174
- * @throws {ForbiddenError} When access is denied
1175
- * @throws {InternalServerError} When server encounters an error
1176
- */
1177
- async handleMessage(messageId, handler) {
1178
- const response = await this.client.receiveMessageById(
1179
- {
1180
- queueName: this.topicName,
1181
- consumerGroup: this.consumerGroupName,
1182
- messageId,
1183
- visibilityTimeoutSeconds: this.visibilityTimeout,
1184
- skipPayload: true
1185
- },
1186
- this.transport
1187
- );
1188
- await this.processMessage(response.message, handler);
1189
- }
1190
828
  /**
1191
829
  * Get the consumer group name
1192
830
  */
@@ -1208,7 +846,7 @@ var Topic = class {
1208
846
  transport;
1209
847
  /**
1210
848
  * Create a new Topic instance
1211
- * @param client VQSClient instance to use for API calls
849
+ * @param client QueueClient instance to use for API calls
1212
850
  * @param topicName Name of the topic to work with
1213
851
  * @param transport Optional serializer/deserializer for the payload (defaults to JSON)
1214
852
  */
@@ -1233,8 +871,7 @@ var Topic = class {
1233
871
  queueName: this.topicName,
1234
872
  payload,
1235
873
  idempotencyKey: options?.idempotencyKey,
1236
- retentionSeconds: options?.retentionSeconds,
1237
- callbacks: options?.callbacks
874
+ retentionSeconds: options?.retentionSeconds
1238
875
  },
1239
876
  this.transport
1240
877
  );
@@ -1273,41 +910,128 @@ var Topic = class {
1273
910
  };
1274
911
 
1275
912
  // src/factory.ts
1276
- function createTopic(client, topicName, transport) {
1277
- return new Topic(client, topicName, transport);
913
+ async function send(topicName, payload, options) {
914
+ const transport = options?.transport || new JsonTransport();
915
+ const client = new QueueClient();
916
+ const result = await client.sendMessage(
917
+ {
918
+ queueName: topicName,
919
+ payload,
920
+ idempotencyKey: options?.idempotencyKey,
921
+ retentionSeconds: options?.retentionSeconds
922
+ },
923
+ transport
924
+ );
925
+ return { messageId: result.messageId };
926
+ }
927
+ async function receive(topicName, consumerGroup, handler, options) {
928
+ const transport = options?.transport || new JsonTransport();
929
+ const client = new QueueClient();
930
+ const topic = new Topic(client, topicName, transport);
931
+ const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
932
+ const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
933
+ if (messageId) {
934
+ if (skipPayload) {
935
+ return consumer.consume(handler, {
936
+ messageId,
937
+ skipPayload: true
938
+ });
939
+ } else {
940
+ return consumer.consume(handler, { messageId });
941
+ }
942
+ } else {
943
+ return consumer.consume(handler);
944
+ }
1278
945
  }
1279
946
 
1280
947
  // src/callback.ts
1281
- function parseCallbackRequest(request) {
1282
- const headers = request.headers;
1283
- const messageId = headers.get("Vqs-Message-Id");
1284
- const queueName = headers.get("Vqs-Queue-Name");
1285
- const consumerGroup = headers.get("Vqs-Consumer-Group");
1286
- const missingHeaders = [];
1287
- if (!messageId) missingHeaders.push("Vqs-Message-Id");
1288
- if (!queueName) missingHeaders.push("Vqs-Queue-Name");
1289
- if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
1290
- if (missingHeaders.length > 0) {
1291
- throw new InvalidCallbackError(
1292
- `Missing required VQS callback headers: ${missingHeaders.join(", ")}`
948
+ async function parseCallbackRequest(request) {
949
+ const contentType = request.headers.get("content-type");
950
+ if (!contentType || !contentType.includes("application/cloudevents+json")) {
951
+ throw new Error(
952
+ "Invalid content type: expected 'application/cloudevents+json'"
1293
953
  );
1294
954
  }
955
+ let cloudEvent;
956
+ try {
957
+ cloudEvent = await request.json();
958
+ } catch (error) {
959
+ throw new Error("Failed to parse CloudEvent from request body");
960
+ }
961
+ if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
962
+ throw new Error("Invalid CloudEvent: missing required fields");
963
+ }
964
+ if (cloudEvent.type !== "com.vercel.queue.v1beta") {
965
+ throw new Error(
966
+ `Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
967
+ );
968
+ }
969
+ const missingFields = [];
970
+ if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
971
+ if (!("consumerGroup" in cloudEvent.data))
972
+ missingFields.push("consumerGroup");
973
+ if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
974
+ if (missingFields.length > 0) {
975
+ throw new Error(
976
+ `Missing required CloudEvent data fields: ${missingFields.join(", ")}`
977
+ );
978
+ }
979
+ const { messageId, queueName, consumerGroup } = cloudEvent.data;
1295
980
  return {
1296
- messageId,
1297
981
  queueName,
1298
- consumerGroup
982
+ consumerGroup,
983
+ messageId
984
+ };
985
+ }
986
+ function handleCallback(handlers) {
987
+ return async (request) => {
988
+ try {
989
+ const { queueName, consumerGroup, messageId } = await parseCallbackRequest(request);
990
+ const topicHandler = handlers[queueName];
991
+ if (!topicHandler) {
992
+ const availableTopics = Object.keys(handlers).join(", ");
993
+ return Response.json(
994
+ {
995
+ error: `No handler found for topic: ${queueName}`,
996
+ availableTopics
997
+ },
998
+ { status: 404 }
999
+ );
1000
+ }
1001
+ const consumerGroupHandler = topicHandler[consumerGroup];
1002
+ if (!consumerGroupHandler) {
1003
+ const availableGroups = Object.keys(topicHandler).join(", ");
1004
+ return Response.json(
1005
+ {
1006
+ error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
1007
+ availableGroups
1008
+ },
1009
+ { status: 404 }
1010
+ );
1011
+ }
1012
+ const client = new QueueClient();
1013
+ const topic = new Topic(client, queueName);
1014
+ const cg = topic.consumerGroup(consumerGroup);
1015
+ await cg.consume(consumerGroupHandler, { messageId });
1016
+ return Response.json({ status: "success" });
1017
+ } catch (error) {
1018
+ console.error("Queue callback error:", error);
1019
+ if (error instanceof Error && (error.message.includes("Missing required CloudEvent data fields") || error.message.includes("Invalid CloudEvent") || error.message.includes("Invalid CloudEvent type") || error.message.includes("Invalid content type") || error.message.includes("Failed to parse CloudEvent"))) {
1020
+ return Response.json({ error: error.message }, { status: 400 });
1021
+ }
1022
+ return Response.json(
1023
+ { error: "Failed to process queue message" },
1024
+ { status: 500 }
1025
+ );
1026
+ }
1299
1027
  };
1300
1028
  }
1301
1029
  // Annotate the CommonJS export names for ESM import in node:
1302
1030
  0 && (module.exports = {
1303
1031
  BadRequestError,
1304
1032
  BufferTransport,
1305
- ConsumerGroup,
1306
- FailedDependencyError,
1307
- FifoOrderingViolationError,
1308
1033
  ForbiddenError,
1309
1034
  InternalServerError,
1310
- InvalidCallbackError,
1311
1035
  InvalidLimitError,
1312
1036
  JsonTransport,
1313
1037
  MessageCorruptedError,
@@ -1316,11 +1040,9 @@ function parseCallbackRequest(request) {
1316
1040
  MessageNotFoundError,
1317
1041
  QueueEmptyError,
1318
1042
  StreamTransport,
1319
- Topic,
1320
1043
  UnauthorizedError,
1321
- VQSClient,
1322
- createTopic,
1323
- getVercelOidcToken,
1324
- parseCallbackRequest
1044
+ handleCallback,
1045
+ receive,
1046
+ send
1325
1047
  });
1326
1048
  //# sourceMappingURL=index.js.map