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

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,129 +1,71 @@
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
- // src/client.ts
16
- import { parseMultipartStream } from "mixpart";
17
-
18
- // src/local.ts
19
- import { spawn } from "child_process";
20
- function isLocalhostWithPort(url) {
1
+ // src/transports.ts
2
+ async function streamToBuffer(stream) {
3
+ let totalLength = 0;
4
+ const reader = stream.getReader();
5
+ const chunks = [];
21
6
  try {
22
- const parsedUrl = new URL(url);
23
- const isLocalhost = parsedUrl.hostname === "localhost";
24
- const port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 0;
25
- return { isLocalhost, port };
26
- } catch {
27
- return { isLocalhost: false };
7
+ while (true) {
8
+ const { done, value } = await reader.read();
9
+ if (done) break;
10
+ chunks.push(value);
11
+ totalLength += value.length;
12
+ }
13
+ } finally {
14
+ reader.releaseLock();
28
15
  }
16
+ return Buffer.concat(chunks, totalLength);
29
17
  }
30
- function isSupportedPlatform() {
31
- const platform = process.platform;
32
- return platform === "darwin" || platform === "linux";
33
- }
34
- function processDevelopmentCallbacks(callbacks) {
35
- const isDevelopment = process.env.NODE_ENV === "development";
36
- if (!isDevelopment) {
37
- return [];
18
+ var JsonTransport = class {
19
+ contentType = "application/json";
20
+ replacer;
21
+ reviver;
22
+ constructor(options = {}) {
23
+ this.replacer = options.replacer;
24
+ this.reviver = options.reviver;
38
25
  }
39
- if (!isSupportedPlatform()) {
40
- const hasLocalhostCallbacks = Object.values(callbacks).some((config) => {
41
- const { isLocalhost } = isLocalhostWithPort(config.url);
42
- return isLocalhost;
43
- });
44
- if (hasLocalhostCallbacks) {
45
- console.warn(
46
- `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.`
47
- );
48
- }
49
- return [];
26
+ serialize(value) {
27
+ return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
50
28
  }
51
- const localhostCallbacks = [];
52
- Object.entries(callbacks).forEach(([group, config]) => {
53
- const { isLocalhost, port } = isLocalhostWithPort(config.url);
54
- if (isLocalhost && port && port > 0) {
55
- localhostCallbacks.push({ group, config, port });
56
- } else {
57
- console.warn(
58
- `Queue Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
59
- );
60
- }
61
- });
62
- return localhostCallbacks;
63
- }
64
- function fireLocalhostCallbacks(localhostCallbacks, queueName, responseData) {
65
- localhostCallbacks.forEach(({ group, config, port }) => {
66
- const callbackHeaders = new Headers();
67
- callbackHeaders.set("Vqs-Message-Id", responseData.messageId);
68
- callbackHeaders.set("Vqs-Queue-Name", queueName);
69
- callbackHeaders.set("Vqs-Consumer-Group", group);
70
- fireAndForgetWaitForHttpReady(
71
- config.url,
72
- port,
73
- config.delay || 0,
74
- 3,
75
- // Default retry frequency
76
- callbackHeaders
77
- );
78
- });
79
- }
80
- function fireAndForgetWaitForHttpReady(url, port, initialDelaySeconds = 0, retryFrequencySeconds = 3, headers) {
81
- if (!isSupportedPlatform()) {
82
- console.warn(
83
- `Queue: fireAndForgetWaitForHttpReady is not supported on ${process.platform}. This function requires bash, nc, and curl which are available on macOS and Linux only.`
84
- );
85
- return;
29
+ async deserialize(stream) {
30
+ const buffer = await streamToBuffer(stream);
31
+ return JSON.parse(buffer.toString("utf8"), this.reviver);
86
32
  }
87
- let headerArgs = "";
88
- if (headers) {
89
- const headerArray = [];
90
- headers.forEach((value, key) => {
91
- headerArray.push(`-H '${key}: ${value}'`);
92
- });
93
- headerArgs = headerArray.join(" ");
33
+ };
34
+ var BufferTransport = class {
35
+ contentType = "application/octet-stream";
36
+ serialize(value) {
37
+ return value;
38
+ }
39
+ async deserialize(stream) {
40
+ return await streamToBuffer(stream);
41
+ }
42
+ };
43
+ var StreamTransport = class {
44
+ contentType = "application/octet-stream";
45
+ serialize(value) {
46
+ return value;
47
+ }
48
+ async deserialize(stream) {
49
+ return stream;
94
50
  }
95
- const bashScript = `
96
- # Wait for any initial boot time
97
- sleep ${initialDelaySeconds}
51
+ async finalize(payload) {
52
+ const reader = payload.getReader();
53
+ try {
54
+ while (true) {
55
+ const { done } = await reader.read();
56
+ if (done) break;
57
+ }
58
+ } finally {
59
+ reader.releaseLock();
60
+ }
61
+ }
62
+ };
98
63
 
99
- missed=0
100
- while true; do
101
- # 1) Check if TCP port is listening
102
- if nc -z localhost ${port} 2>/dev/null; then
103
- missed=0
104
- # 2) If port is open, try HTTP POST check
105
- if curl -sSL --fail -o /dev/null -X POST ${headerArgs} "${url}"; then
106
- # Success: port is up AND HTTP returned 2xx (following redirects)
107
- exit 0
108
- fi
109
- else
110
- # Port was closed\u2014increment miss counter
111
- ((missed+=1))
112
- # If closed twice in a row, give up immediately
113
- if [ "$missed" -ge 2 ]; then
114
- exit 1
115
- fi
116
- fi
117
- # Wait before next cycle
118
- sleep ${retryFrequencySeconds}
119
- done
120
- `;
121
- const childProcess = spawn("bash", ["-c", bashScript], {
122
- stdio: "ignore",
123
- detached: true
124
- });
125
- childProcess.unref();
126
- }
64
+ // src/client.ts
65
+ import { parseMultipartStream } from "mixpart";
66
+
67
+ // src/oidc.ts
68
+ import { getVercelOidcToken } from "@vercel/oidc";
127
69
 
128
70
  // src/types.ts
129
71
  var MessageNotFoundError = class extends Error {
@@ -140,16 +82,6 @@ var MessageNotAvailableError = class extends Error {
140
82
  this.name = "MessageNotAvailableError";
141
83
  }
142
84
  };
143
- 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
- );
149
- this.name = "FifoOrderingViolationError";
150
- this.nextMessageId = nextMessageId;
151
- }
152
- };
153
85
  var MessageCorruptedError = class extends Error {
154
86
  constructor(messageId, reason) {
155
87
  super(`Message ${messageId} is corrupted: ${reason}`);
@@ -191,16 +123,6 @@ var BadRequestError = class extends Error {
191
123
  this.name = "BadRequestError";
192
124
  }
193
125
  };
194
- var FailedDependencyError = class extends Error {
195
- nextMessageId;
196
- constructor(messageId, nextMessageId) {
197
- super(
198
- `Failed dependency: FIFO ordering violation for message ${messageId}. Must process message ${nextMessageId} first.`
199
- );
200
- this.name = "FailedDependencyError";
201
- this.nextMessageId = nextMessageId;
202
- }
203
- };
204
126
  var InternalServerError = class extends Error {
205
127
  constructor(message = "Unexpected server error") {
206
128
  super(message);
@@ -213,12 +135,6 @@ var InvalidLimitError = class extends Error {
213
135
  this.name = "InvalidLimitError";
214
136
  }
215
137
  };
216
- var InvalidCallbackError = class extends Error {
217
- constructor(message) {
218
- super(message);
219
- this.name = "InvalidCallbackError";
220
- }
221
- };
222
138
 
223
139
  // src/client.ts
224
140
  async function consumeStream(stream) {
@@ -248,40 +164,39 @@ function parseQueueHeaders(headers) {
248
164
  return {
249
165
  messageId,
250
166
  deliveryCount,
251
- timestamp,
167
+ createdAt: new Date(timestamp),
252
168
  contentType,
253
169
  ticket
254
170
  };
255
171
  }
256
- var QueueClient = class _QueueClient {
172
+ var QueueClient = class {
257
173
  baseUrl;
258
- token;
174
+ basePath;
175
+ customHeaders = {};
259
176
  /**
260
177
  * Create a new Vercel Queue Service client
261
178
  * @param options Client configuration options
262
179
  */
263
- constructor(options) {
264
- this.baseUrl = options.baseUrl || "https://vqs.vercel.sh";
265
- this.token = options.token;
180
+ constructor(options = {}) {
181
+ this.baseUrl = options.baseUrl || process.env.VERCEL_QUEUE_BASE_URL || "https://vercel-queue.com";
182
+ this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v2/messages";
183
+ const VERCEL_QUEUE_HEADER_PREFIX = "VERCEL_QUEUE_HEADER_";
184
+ this.customHeaders = Object.fromEntries(
185
+ Object.entries(process.env).filter(([key]) => key.startsWith(VERCEL_QUEUE_HEADER_PREFIX)).map(([key, value]) => [
186
+ // This allows headers to use dashes independent of shell used
187
+ key.replace(VERCEL_QUEUE_HEADER_PREFIX, "").replaceAll("__", "-"),
188
+ value || ""
189
+ ])
190
+ );
266
191
  }
267
- /**
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
273
- */
274
- static async fromVercelFunction(baseUrl) {
192
+ async getToken() {
275
193
  const token = await getVercelOidcToken();
276
194
  if (!token) {
277
195
  throw new Error(
278
- "Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment."
196
+ "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'"
279
197
  );
280
198
  }
281
- return new _QueueClient({
282
- token,
283
- baseUrl
284
- });
199
+ return token;
285
200
  }
286
201
  /**
287
202
  * Send a message to a queue
@@ -294,51 +209,28 @@ var QueueClient = class _QueueClient {
294
209
  * @throws {InternalServerError} When server encounters an error
295
210
  */
296
211
  async sendMessage(options, transport) {
297
- const { queueName, payload, idempotencyKey, retentionSeconds, callback } = options;
212
+ const { queueName, payload, idempotencyKey, retentionSeconds } = options;
298
213
  const headers = new Headers({
299
- Authorization: `Bearer ${this.token}`,
214
+ Authorization: `Bearer ${await this.getToken()}`,
300
215
  "Vqs-Queue-Name": queueName,
301
- "Content-Type": transport.contentType
216
+ "Content-Type": transport.contentType,
217
+ ...this.customHeaders
302
218
  });
219
+ const deploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
220
+ if (deploymentId) {
221
+ headers.set("Vqs-Deployment-Id", deploymentId);
222
+ }
303
223
  if (idempotencyKey) {
304
224
  headers.set("Vqs-Idempotency-Key", idempotencyKey);
305
225
  }
306
226
  if (retentionSeconds !== void 0) {
307
227
  headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
308
228
  }
309
- let normalizedCallbacks;
310
- if (callback) {
311
- if ("url" in callback && typeof callback.url === "string") {
312
- normalizedCallbacks = { default: callback };
313
- } else {
314
- normalizedCallbacks = callback;
315
- }
316
- }
317
- let localhostCallbacks = [];
318
- if (normalizedCallbacks) {
319
- const isDevelopment = process.env.NODE_ENV === "development";
320
- if (isDevelopment) {
321
- localhostCallbacks = processDevelopmentCallbacks(normalizedCallbacks);
322
- } else {
323
- const endpoints = Object.entries(normalizedCallbacks).map(
324
- ([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
325
- ).join(",");
326
- headers.set("Vqs-Callback-Url", endpoints);
327
- const delays = Object.entries(normalizedCallbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
328
- if (delays) {
329
- headers.set("Vqs-Callback-Delay", delays);
330
- }
331
- const frequencies = Object.entries(normalizedCallbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
332
- if (frequencies) {
333
- headers.set("Vqs-Callback-Frequency", frequencies);
334
- }
335
- }
336
- }
337
229
  const body = transport.serialize(payload);
338
- const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
230
+ const response = await fetch(`${this.baseUrl}${this.basePath}`, {
339
231
  method: "POST",
340
- headers,
341
- body
232
+ body,
233
+ headers
342
234
  });
343
235
  if (!response.ok) {
344
236
  if (response.status === 400) {
@@ -364,9 +256,6 @@ var QueueClient = class _QueueClient {
364
256
  );
365
257
  }
366
258
  const responseData = await response.json();
367
- if (localhostCallbacks.length > 0) {
368
- fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
369
- }
370
259
  return responseData;
371
260
  }
372
261
  /**
@@ -376,7 +265,7 @@ var QueueClient = class _QueueClient {
376
265
  * @returns AsyncGenerator that yields messages as they arrive
377
266
  * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
378
267
  * @throws {QueueEmptyError} When no messages are available (204)
379
- * @throws {MessageLockedError} When FIFO queue has locked messages (423)
268
+ * @throws {MessageLockedError} When messages are temporarily locked (423)
380
269
  * @throws {BadRequestError} When request parameters are invalid
381
270
  * @throws {UnauthorizedError} When authentication fails
382
271
  * @throws {ForbiddenError} When access is denied (environment mismatch)
@@ -388,10 +277,11 @@ var QueueClient = class _QueueClient {
388
277
  throw new InvalidLimitError(limit);
389
278
  }
390
279
  const headers = new Headers({
391
- Authorization: `Bearer ${this.token}`,
280
+ Authorization: `Bearer ${await this.getToken()}`,
392
281
  "Vqs-Queue-Name": queueName,
393
282
  "Vqs-Consumer-Group": consumerGroup,
394
- Accept: "multipart/mixed"
283
+ Accept: "multipart/mixed",
284
+ ...this.customHeaders
395
285
  });
396
286
  if (visibilityTimeoutSeconds !== void 0) {
397
287
  headers.set(
@@ -402,7 +292,7 @@ var QueueClient = class _QueueClient {
402
292
  if (limit !== void 0) {
403
293
  headers.set("Vqs-Limit", limit.toString());
404
294
  }
405
- const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
295
+ const response = await fetch(`${this.baseUrl}${this.basePath}`, {
406
296
  method: "GET",
407
297
  headers
408
298
  });
@@ -427,7 +317,7 @@ var QueueClient = class _QueueClient {
427
317
  const parsed = parseInt(retryAfterHeader, 10);
428
318
  retryAfter = isNaN(parsed) ? void 0 : parsed;
429
319
  }
430
- throw new MessageLockedError("next message in FIFO queue", retryAfter);
320
+ throw new MessageLockedError("next message", retryAfter);
431
321
  }
432
322
  if (response.status >= 500) {
433
323
  throw new InternalServerError(
@@ -469,10 +359,11 @@ var QueueClient = class _QueueClient {
469
359
  skipPayload
470
360
  } = options;
471
361
  const headers = new Headers({
472
- Authorization: `Bearer ${this.token}`,
362
+ Authorization: `Bearer ${await this.getToken()}`,
473
363
  "Vqs-Queue-Name": queueName,
474
364
  "Vqs-Consumer-Group": consumerGroup,
475
- Accept: "multipart/mixed"
365
+ Accept: "multipart/mixed",
366
+ ...this.customHeaders
476
367
  });
477
368
  if (visibilityTimeoutSeconds !== void 0) {
478
369
  headers.set(
@@ -484,7 +375,7 @@ var QueueClient = class _QueueClient {
484
375
  headers.set("Vqs-Skip-Payload", "1");
485
376
  }
486
377
  const response = await fetch(
487
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
378
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
488
379
  {
489
380
  method: "GET",
490
381
  headers
@@ -513,37 +404,7 @@ var QueueClient = class _QueueClient {
513
404
  }
514
405
  throw new MessageLockedError(messageId, retryAfter);
515
406
  }
516
- 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
- );
534
- }
535
407
  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
408
  throw new MessageNotAvailableError(messageId);
548
409
  }
549
410
  if (response.status >= 500) {
@@ -623,14 +484,15 @@ var QueueClient = class _QueueClient {
623
484
  async deleteMessage(options) {
624
485
  const { queueName, consumerGroup, messageId, ticket } = options;
625
486
  const response = await fetch(
626
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
487
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
627
488
  {
628
489
  method: "DELETE",
629
490
  headers: new Headers({
630
- Authorization: `Bearer ${this.token}`,
491
+ Authorization: `Bearer ${await this.getToken()}`,
631
492
  "Vqs-Queue-Name": queueName,
632
493
  "Vqs-Consumer-Group": consumerGroup,
633
- "Vqs-Ticket": ticket
494
+ "Vqs-Ticket": ticket,
495
+ ...this.customHeaders
634
496
  })
635
497
  }
636
498
  );
@@ -684,15 +546,16 @@ var QueueClient = class _QueueClient {
684
546
  visibilityTimeoutSeconds
685
547
  } = options;
686
548
  const response = await fetch(
687
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
549
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
688
550
  {
689
551
  method: "PATCH",
690
552
  headers: new Headers({
691
- Authorization: `Bearer ${this.token}`,
553
+ Authorization: `Bearer ${await this.getToken()}`,
692
554
  "Vqs-Queue-Name": queueName,
693
555
  "Vqs-Consumer-Group": consumerGroup,
694
556
  "Vqs-Ticket": ticket,
695
- "Vqs-Visibility-Timeout": visibilityTimeoutSeconds.toString()
557
+ "Vqs-Visibility-Timeout": visibilityTimeoutSeconds.toString(),
558
+ ...this.customHeaders
696
559
  })
697
560
  }
698
561
  );
@@ -730,81 +593,303 @@ var QueueClient = class _QueueClient {
730
593
  }
731
594
  };
732
595
 
733
- // src/transports.ts
734
- var JsonTransport = class {
735
- contentType = "application/json";
736
- serialize(value) {
737
- return Buffer.from(JSON.stringify(value), "utf8");
596
+ // src/callback.ts
597
+ function validateWildcardPattern(pattern) {
598
+ const firstIndex = pattern.indexOf("*");
599
+ const lastIndex = pattern.lastIndexOf("*");
600
+ if (firstIndex !== lastIndex) {
601
+ return false;
738
602
  }
739
- async deserialize(stream) {
740
- const reader = stream.getReader();
741
- const chunks = [];
742
- try {
743
- while (true) {
744
- const { done, value } = await reader.read();
745
- if (done) break;
746
- chunks.push(value);
603
+ if (firstIndex === -1) {
604
+ return false;
605
+ }
606
+ if (firstIndex !== pattern.length - 1) {
607
+ return false;
608
+ }
609
+ return true;
610
+ }
611
+ function matchesWildcardPattern(topicName, pattern) {
612
+ const prefix = pattern.slice(0, -1);
613
+ return topicName.startsWith(prefix);
614
+ }
615
+ function findTopicHandler(queueName, handlers) {
616
+ const exactHandler = handlers[queueName];
617
+ if (exactHandler) {
618
+ return exactHandler;
619
+ }
620
+ for (const pattern in handlers) {
621
+ if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
622
+ return handlers[pattern];
623
+ }
624
+ }
625
+ return null;
626
+ }
627
+ async function parseCallback(request) {
628
+ const contentType = request.headers.get("content-type");
629
+ if (!contentType || !contentType.includes("application/cloudevents+json")) {
630
+ throw new Error(
631
+ "Invalid content type: expected 'application/cloudevents+json'"
632
+ );
633
+ }
634
+ let cloudEvent;
635
+ try {
636
+ cloudEvent = await request.json();
637
+ } catch (error) {
638
+ throw new Error("Failed to parse CloudEvent from request body");
639
+ }
640
+ if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
641
+ throw new Error("Invalid CloudEvent: missing required fields");
642
+ }
643
+ if (cloudEvent.type !== "com.vercel.queue.v1beta") {
644
+ throw new Error(
645
+ `Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
646
+ );
647
+ }
648
+ const missingFields = [];
649
+ if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
650
+ if (!("consumerGroup" in cloudEvent.data))
651
+ missingFields.push("consumerGroup");
652
+ if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
653
+ if (missingFields.length > 0) {
654
+ throw new Error(
655
+ `Missing required CloudEvent data fields: ${missingFields.join(", ")}`
656
+ );
657
+ }
658
+ const { messageId, queueName, consumerGroup } = cloudEvent.data;
659
+ return {
660
+ queueName,
661
+ consumerGroup,
662
+ messageId
663
+ };
664
+ }
665
+ function handleCallback(handlers) {
666
+ for (const topicPattern in handlers) {
667
+ if (topicPattern.includes("*")) {
668
+ if (!validateWildcardPattern(topicPattern)) {
669
+ throw new Error(
670
+ `Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
671
+ );
747
672
  }
748
- } finally {
749
- reader.releaseLock();
750
673
  }
751
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
752
- const buffer = new Uint8Array(totalLength);
753
- let offset = 0;
754
- for (const chunk of chunks) {
755
- buffer.set(chunk, offset);
756
- offset += chunk.length;
674
+ }
675
+ const routeHandler = async (request) => {
676
+ try {
677
+ const { queueName, consumerGroup, messageId } = await parseCallback(request);
678
+ const topicHandler = findTopicHandler(queueName, handlers);
679
+ if (!topicHandler) {
680
+ const availableTopics = Object.keys(handlers).join(", ");
681
+ return Response.json(
682
+ {
683
+ error: `No handler found for topic: ${queueName}`,
684
+ availableTopics
685
+ },
686
+ { status: 404 }
687
+ );
688
+ }
689
+ const consumerGroupHandler = topicHandler[consumerGroup];
690
+ if (!consumerGroupHandler) {
691
+ const availableGroups = Object.keys(topicHandler).join(", ");
692
+ return Response.json(
693
+ {
694
+ error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
695
+ availableGroups
696
+ },
697
+ { status: 404 }
698
+ );
699
+ }
700
+ const client = new QueueClient();
701
+ const topic = new Topic(client, queueName);
702
+ const cg = topic.consumerGroup(consumerGroup);
703
+ await cg.consume(consumerGroupHandler, { messageId });
704
+ return Response.json({ status: "success" });
705
+ } catch (error) {
706
+ console.error("Queue callback error:", error);
707
+ 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"))) {
708
+ return Response.json({ error: error.message }, { status: 400 });
709
+ }
710
+ return Response.json(
711
+ { error: "Failed to process queue message" },
712
+ { status: 500 }
713
+ );
757
714
  }
758
- return JSON.parse(Buffer.from(buffer).toString("utf8"));
715
+ };
716
+ if (isDevMode()) {
717
+ registerDevRouteHandler(routeHandler, handlers);
759
718
  }
760
- };
761
- var BufferTransport = class {
762
- contentType = "application/octet-stream";
763
- serialize(value) {
764
- return value;
719
+ return routeHandler;
720
+ }
721
+
722
+ // src/dev.ts
723
+ var devRouteHandlers = /* @__PURE__ */ new Map();
724
+ var wildcardRouteHandlers = /* @__PURE__ */ new Map();
725
+ function cleanupDeadRefs(key, refs) {
726
+ const aliveRefs = refs.filter((ref) => ref.deref() !== void 0);
727
+ if (aliveRefs.length === 0) {
728
+ wildcardRouteHandlers.delete(key);
729
+ } else if (aliveRefs.length < refs.length) {
730
+ wildcardRouteHandlers.set(key, aliveRefs);
765
731
  }
766
- async deserialize(stream) {
767
- const reader = stream.getReader();
768
- const chunks = [];
769
- try {
770
- while (true) {
771
- const { done, value } = await reader.read();
772
- if (done) break;
773
- chunks.push(value);
732
+ }
733
+ function isDevMode() {
734
+ return process.env.NODE_ENV === "development";
735
+ }
736
+ function registerDevRouteHandler(routeHandler, handlers) {
737
+ for (const topicName in handlers) {
738
+ for (const consumerGroup in handlers[topicName]) {
739
+ const key = `${topicName}:${consumerGroup}`;
740
+ if (topicName.includes("*")) {
741
+ const existing = wildcardRouteHandlers.get(key) || [];
742
+ cleanupDeadRefs(key, existing);
743
+ const cleanedRefs = wildcardRouteHandlers.get(key) || [];
744
+ const weakRef = new WeakRef(routeHandler);
745
+ cleanedRefs.push(weakRef);
746
+ wildcardRouteHandlers.set(key, cleanedRefs);
747
+ } else {
748
+ devRouteHandlers.set(key, {
749
+ routeHandler,
750
+ topicPattern: topicName
751
+ });
774
752
  }
775
- } finally {
776
- reader.releaseLock();
777
753
  }
778
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
779
- const buffer = new Uint8Array(totalLength);
780
- let offset = 0;
781
- for (const chunk of chunks) {
782
- buffer.set(chunk, offset);
783
- offset += chunk.length;
754
+ }
755
+ }
756
+ function findRouteHandlersForTopic(topicName) {
757
+ const handlersMap = /* @__PURE__ */ new Map();
758
+ for (const [
759
+ key,
760
+ { routeHandler, topicPattern }
761
+ ] of devRouteHandlers.entries()) {
762
+ const [_, consumerGroup] = key.split(":");
763
+ if (topicPattern === topicName) {
764
+ if (!handlersMap.has(routeHandler)) {
765
+ handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
766
+ }
767
+ handlersMap.get(routeHandler).add(consumerGroup);
784
768
  }
785
- return Buffer.from(buffer);
786
769
  }
787
- };
788
- var StreamTransport = class {
789
- contentType = "application/octet-stream";
790
- serialize(value) {
791
- return value;
770
+ for (const [key, refs] of wildcardRouteHandlers.entries()) {
771
+ const [pattern, consumerGroup] = key.split(":");
772
+ if (matchesWildcardPattern(topicName, pattern)) {
773
+ cleanupDeadRefs(key, refs);
774
+ const cleanedRefs = wildcardRouteHandlers.get(key) || [];
775
+ for (const ref of cleanedRefs) {
776
+ const routeHandler = ref.deref();
777
+ if (routeHandler) {
778
+ if (!handlersMap.has(routeHandler)) {
779
+ handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
780
+ }
781
+ handlersMap.get(routeHandler).add(consumerGroup);
782
+ }
783
+ }
784
+ }
792
785
  }
793
- async deserialize(stream) {
794
- return stream;
786
+ return handlersMap;
787
+ }
788
+ function createMockCloudEventRequest(topicName, consumerGroup, messageId) {
789
+ const cloudEvent = {
790
+ type: "com.vercel.queue.v1beta",
791
+ source: `/topic/${topicName}/consumer/${consumerGroup}`,
792
+ id: messageId,
793
+ datacontenttype: "application/json",
794
+ data: {
795
+ messageId,
796
+ queueName: topicName,
797
+ consumerGroup
798
+ },
799
+ time: (/* @__PURE__ */ new Date()).toISOString(),
800
+ specversion: "1.0"
801
+ };
802
+ return new Request("https://localhost/api/queue/callback", {
803
+ method: "POST",
804
+ headers: {
805
+ "Content-Type": "application/cloudevents+json"
806
+ },
807
+ body: JSON.stringify(cloudEvent)
808
+ });
809
+ }
810
+ var DEV_CALLBACK_DELAY = 1e3;
811
+ function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
812
+ console.log(
813
+ `[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
814
+ );
815
+ setTimeout(
816
+ () => {
817
+ console.log(
818
+ `[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
819
+ );
820
+ triggerDevCallbacks(topicName, messageId);
821
+ },
822
+ timeoutSeconds * 1e3 + DEV_CALLBACK_DELAY
823
+ );
824
+ }
825
+ function triggerDevCallbacks(topicName, messageId) {
826
+ const handlersMap = findRouteHandlersForTopic(topicName);
827
+ if (handlersMap.size === 0) {
828
+ return;
795
829
  }
796
- async finalize(payload) {
797
- const reader = payload.getReader();
798
- try {
799
- while (true) {
800
- const { done } = await reader.read();
801
- if (done) break;
830
+ const consumerGroups = Array.from(
831
+ new Set(
832
+ Array.from(handlersMap.values()).flatMap((groups) => Array.from(groups))
833
+ )
834
+ );
835
+ console.log(
836
+ `[Dev Mode] Triggering local callbacks for topic "${topicName}" \u2192 consumers: ${consumerGroups.join(", ")}`
837
+ );
838
+ setTimeout(async () => {
839
+ for (const [routeHandler, consumerGroups2] of handlersMap.entries()) {
840
+ for (const consumerGroup of consumerGroups2) {
841
+ try {
842
+ const request = createMockCloudEventRequest(
843
+ topicName,
844
+ consumerGroup,
845
+ messageId
846
+ );
847
+ const response = await routeHandler(request);
848
+ if (response.ok) {
849
+ try {
850
+ const responseData = await response.json();
851
+ if (responseData.status === "success") {
852
+ console.log(
853
+ `[Dev Mode] Message processed for ${topicName}/${consumerGroup}`
854
+ );
855
+ }
856
+ } catch (jsonError) {
857
+ console.error(
858
+ `[Dev Mode] Failed to parse success response for ${topicName}/${consumerGroup}:`,
859
+ jsonError
860
+ );
861
+ }
862
+ } else {
863
+ try {
864
+ const errorData = await response.json();
865
+ console.error(
866
+ `[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
867
+ errorData.error || response.statusText
868
+ );
869
+ } catch (jsonError) {
870
+ console.error(
871
+ `[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
872
+ response.statusText
873
+ );
874
+ }
875
+ }
876
+ } catch (error) {
877
+ console.error(
878
+ `[Dev Mode] Error triggering callback for ${topicName}/${consumerGroup}:`,
879
+ error
880
+ );
881
+ }
802
882
  }
803
- } finally {
804
- reader.releaseLock();
805
883
  }
806
- }
807
- };
884
+ }, DEV_CALLBACK_DELAY);
885
+ }
886
+ function clearDevHandlers() {
887
+ devRouteHandlers.clear();
888
+ wildcardRouteHandlers.clear();
889
+ }
890
+ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
891
+ globalThis.__clearDevHandlers = clearDevHandlers;
892
+ }
808
893
 
809
894
  // src/consumer-group.ts
810
895
  var ConsumerGroup = class {
@@ -905,7 +990,13 @@ var ConsumerGroup = class {
905
990
  message.ticket
906
991
  );
907
992
  try {
908
- const result = await handler(message);
993
+ const result = await handler(message.payload, {
994
+ messageId: message.messageId,
995
+ deliveryCount: message.deliveryCount,
996
+ createdAt: message.createdAt,
997
+ topicName: this.topicName,
998
+ consumerGroup: this.consumerGroupName
999
+ });
909
1000
  await stopExtension();
910
1001
  if (result && "timeoutSeconds" in result) {
911
1002
  await this.client.changeVisibility({
@@ -915,6 +1006,13 @@ var ConsumerGroup = class {
915
1006
  ticket: message.ticket,
916
1007
  visibilityTimeoutSeconds: result.timeoutSeconds
917
1008
  });
1009
+ if (isDevMode()) {
1010
+ scheduleDevTimeout(
1011
+ this.topicName,
1012
+ message.messageId,
1013
+ result.timeoutSeconds
1014
+ );
1015
+ }
918
1016
  } else {
919
1017
  await this.client.deleteMessage({
920
1018
  queueName: this.topicName,
@@ -935,219 +1033,58 @@ var ConsumerGroup = class {
935
1033
  throw error;
936
1034
  }
937
1035
  }
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(
1036
+ async consume(handler, options) {
1037
+ if (options?.messageId) {
1038
+ if (options.skipPayload) {
1039
+ const response = await this.client.receiveMessageById(
950
1040
  {
951
1041
  queueName: this.topicName,
952
1042
  consumerGroup: this.consumerGroupName,
1043
+ messageId: options.messageId,
953
1044
  visibilityTimeoutSeconds: this.visibilityTimeout,
954
- limit: 1
955
- // Always process one message at a time
1045
+ skipPayload: true
956
1046
  },
957
1047
  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;
1048
+ );
1049
+ await this.processMessage(
1050
+ response.message,
1051
+ handler
1052
+ );
1053
+ } else {
1054
+ const response = await this.client.receiveMessageById(
1055
+ {
1056
+ queueName: this.topicName,
1057
+ consumerGroup: this.consumerGroupName,
1058
+ messageId: options.messageId,
1059
+ visibilityTimeoutSeconds: this.visibilityTimeout
1060
+ },
1061
+ this.transport
1062
+ );
1063
+ await this.processMessage(
1064
+ response.message,
1065
+ handler
1066
+ );
1067
+ }
1068
+ } else {
1069
+ let messageFound = false;
1070
+ for await (const message of this.client.receiveMessages(
1071
+ {
1072
+ queueName: this.topicName,
1073
+ consumerGroup: this.consumerGroupName,
1074
+ visibilityTimeoutSeconds: this.visibilityTimeout,
1075
+ limit: 1
1076
+ },
1077
+ this.transport
1078
+ )) {
1079
+ messageFound = true;
1080
+ await this.processMessage(message, handler);
1081
+ break;
1082
+ }
1083
+ if (!messageFound) {
1084
+ throw new Error("No messages available");
1017
1085
  }
1018
1086
  }
1019
1087
  }
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
1088
  /**
1152
1089
  * Get the consumer group name
1153
1090
  */
@@ -1195,10 +1132,13 @@ var Topic = class {
1195
1132
  payload,
1196
1133
  idempotencyKey: options?.idempotencyKey,
1197
1134
  retentionSeconds: options?.retentionSeconds,
1198
- callback: options?.callback
1135
+ deploymentId: options?.deploymentId
1199
1136
  },
1200
1137
  this.transport
1201
1138
  );
1139
+ if (isDevMode()) {
1140
+ triggerDevCallbacks(this.topicName, result.messageId);
1141
+ }
1202
1142
  return { messageId: result.messageId };
1203
1143
  }
1204
1144
  /**
@@ -1234,107 +1174,60 @@ var Topic = class {
1234
1174
  };
1235
1175
 
1236
1176
  // src/factory.ts
1237
- function createTopic(client, topicName, transport) {
1238
- return new Topic(client, topicName, transport);
1239
- }
1240
-
1241
- // src/callback.ts
1242
- function parseCallbackRequest(request) {
1243
- const headers = request.headers;
1244
- const messageId = headers.get("Vqs-Message-Id");
1245
- const queueName = headers.get("Vqs-Queue-Name");
1246
- const consumerGroup = headers.get("Vqs-Consumer-Group");
1247
- const missingHeaders = [];
1248
- if (!messageId) missingHeaders.push("Vqs-Message-Id");
1249
- if (!queueName) missingHeaders.push("Vqs-Queue-Name");
1250
- if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
1251
- if (missingHeaders.length > 0) {
1252
- throw new InvalidCallbackError(
1253
- `Missing required queue callback headers: ${missingHeaders.join(", ")}`
1254
- );
1177
+ async function send(topicName, payload, options) {
1178
+ const transport = options?.transport || new JsonTransport();
1179
+ const client = new QueueClient();
1180
+ const result = await client.sendMessage(
1181
+ {
1182
+ queueName: topicName,
1183
+ payload,
1184
+ idempotencyKey: options?.idempotencyKey,
1185
+ retentionSeconds: options?.retentionSeconds,
1186
+ deploymentId: options?.deploymentId
1187
+ },
1188
+ transport
1189
+ );
1190
+ if (isDevMode()) {
1191
+ triggerDevCallbacks(topicName, result.messageId);
1255
1192
  }
1256
- return {
1257
- messageId,
1258
- queueName,
1259
- consumerGroup
1260
- };
1193
+ return { messageId: result.messageId };
1261
1194
  }
1262
- function handleCallback(handlers) {
1263
- return async (request) => {
1264
- try {
1265
- const { queueName, consumerGroup, messageId } = parseCallbackRequest(request);
1266
- const topicHandler = handlers[queueName];
1267
- if (!topicHandler) {
1268
- throw new Error(`No handler found for topic: ${queueName}`);
1269
- }
1270
- let actualHandler;
1271
- if (typeof topicHandler === "function") {
1272
- if (consumerGroup !== "default") {
1273
- throw new Error(
1274
- `Topic "${queueName}" has a single handler but received consumer group "${consumerGroup}". Expected "default".`
1275
- );
1276
- }
1277
- actualHandler = topicHandler;
1278
- } else {
1279
- const consumerGroupHandler = topicHandler[consumerGroup];
1280
- if (!consumerGroupHandler) {
1281
- const availableGroups = Object.keys(topicHandler).join(", ");
1282
- throw new Error(
1283
- `No handler found for consumer group "${consumerGroup}" in topic "${queueName}". Available groups: ${availableGroups}`
1284
- );
1285
- }
1286
- actualHandler = consumerGroupHandler;
1287
- }
1288
- const client = await QueueClient.fromVercelFunction();
1289
- const topic = new Topic(client, queueName);
1290
- 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);
1195
+ async function receive(topicName, consumerGroup, handler, options) {
1196
+ const transport = options?.transport || new JsonTransport();
1197
+ const client = new QueueClient();
1198
+ const topic = new Topic(client, topicName, transport);
1199
+ const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
1200
+ const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
1201
+ if (messageId) {
1202
+ if (skipPayload) {
1203
+ return consumer.consume(handler, {
1204
+ messageId,
1205
+ skipPayload: true
1298
1206
  });
1299
- return Response.json({ status: "success" });
1300
- } catch (error) {
1301
- console.error("Callback error:", error);
1302
- if (error instanceof InvalidCallbackError) {
1303
- return Response.json(
1304
- { error: "Invalid callback request" },
1305
- { status: 400 }
1306
- );
1307
- }
1308
- return Response.json(
1309
- { error: "Failed to process callback" },
1310
- { status: 500 }
1311
- );
1207
+ } else {
1208
+ return consumer.consume(handler, { messageId });
1312
1209
  }
1313
- };
1210
+ } else {
1211
+ return consumer.consume(handler);
1212
+ }
1314
1213
  }
1315
1214
  export {
1316
1215
  BadRequestError,
1317
1216
  BufferTransport,
1318
- ConsumerGroup,
1319
- FailedDependencyError,
1320
- FifoOrderingViolationError,
1321
1217
  ForbiddenError,
1322
1218
  InternalServerError,
1323
- InvalidCallbackError,
1324
1219
  InvalidLimitError,
1325
1220
  JsonTransport,
1326
1221
  MessageCorruptedError,
1327
1222
  MessageLockedError,
1328
1223
  MessageNotAvailableError,
1329
1224
  MessageNotFoundError,
1330
- QueueClient,
1331
1225
  QueueEmptyError,
1332
1226
  StreamTransport,
1333
- Topic,
1334
1227
  UnauthorizedError,
1335
- createTopic,
1336
- getVercelOidcToken,
1337
1228
  handleCallback,
1338
- parseCallbackRequest
1229
+ parseCallback,
1230
+ receive,
1231
+ send
1339
1232
  };
1340
1233
  //# sourceMappingURL=index.mjs.map