@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.js CHANGED
@@ -22,156 +22,92 @@ 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,
34
30
  MessageLockedError: () => MessageLockedError,
35
31
  MessageNotAvailableError: () => MessageNotAvailableError,
36
32
  MessageNotFoundError: () => MessageNotFoundError,
37
- QueueClient: () => QueueClient,
38
33
  QueueEmptyError: () => QueueEmptyError,
39
34
  StreamTransport: () => StreamTransport,
40
- Topic: () => Topic,
41
35
  UnauthorizedError: () => UnauthorizedError,
42
- createTopic: () => createTopic,
43
- getVercelOidcToken: () => getVercelOidcToken,
44
36
  handleCallback: () => handleCallback,
45
- parseCallbackRequest: () => parseCallbackRequest
37
+ parseCallback: () => parseCallback,
38
+ receive: () => receive,
39
+ send: () => send
46
40
  });
47
41
  module.exports = __toCommonJS(index_exports);
48
42
 
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
- // src/client.ts
64
- var import_mixpart = require("mixpart");
65
-
66
- // src/local.ts
67
- var import_node_child_process = require("child_process");
68
- function isLocalhostWithPort(url) {
43
+ // src/transports.ts
44
+ async function streamToBuffer(stream) {
45
+ let totalLength = 0;
46
+ const reader = stream.getReader();
47
+ const chunks = [];
69
48
  try {
70
- const parsedUrl = new URL(url);
71
- const isLocalhost = parsedUrl.hostname === "localhost";
72
- const port = parsedUrl.port ? parseInt(parsedUrl.port, 10) : 0;
73
- return { isLocalhost, port };
74
- } catch {
75
- return { isLocalhost: false };
49
+ while (true) {
50
+ const { done, value } = await reader.read();
51
+ if (done) break;
52
+ chunks.push(value);
53
+ totalLength += value.length;
54
+ }
55
+ } finally {
56
+ reader.releaseLock();
76
57
  }
58
+ return Buffer.concat(chunks, totalLength);
77
59
  }
78
- function isSupportedPlatform() {
79
- const platform = process.platform;
80
- return platform === "darwin" || platform === "linux";
81
- }
82
- function processDevelopmentCallbacks(callbacks) {
83
- const isDevelopment = process.env.NODE_ENV === "development";
84
- if (!isDevelopment) {
85
- return [];
60
+ var JsonTransport = class {
61
+ contentType = "application/json";
62
+ replacer;
63
+ reviver;
64
+ constructor(options = {}) {
65
+ this.replacer = options.replacer;
66
+ this.reviver = options.reviver;
86
67
  }
87
- if (!isSupportedPlatform()) {
88
- const hasLocalhostCallbacks = Object.values(callbacks).some((config) => {
89
- const { isLocalhost } = isLocalhostWithPort(config.url);
90
- return isLocalhost;
91
- });
92
- if (hasLocalhostCallbacks) {
93
- console.warn(
94
- `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.`
95
- );
96
- }
97
- return [];
68
+ serialize(value) {
69
+ return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
98
70
  }
99
- const localhostCallbacks = [];
100
- Object.entries(callbacks).forEach(([group, config]) => {
101
- const { isLocalhost, port } = isLocalhostWithPort(config.url);
102
- if (isLocalhost && port && port > 0) {
103
- localhostCallbacks.push({ group, config, port });
104
- } else {
105
- console.warn(
106
- `Queue Development Mode: Skipping non-localhost callback for group "${group}": ${config.url}. Only localhost callbacks with explicit ports are supported in development.`
107
- );
108
- }
109
- });
110
- return localhostCallbacks;
111
- }
112
- function fireLocalhostCallbacks(localhostCallbacks, queueName, responseData) {
113
- localhostCallbacks.forEach(({ group, config, port }) => {
114
- const callbackHeaders = new Headers();
115
- callbackHeaders.set("Vqs-Message-Id", responseData.messageId);
116
- callbackHeaders.set("Vqs-Queue-Name", queueName);
117
- callbackHeaders.set("Vqs-Consumer-Group", group);
118
- fireAndForgetWaitForHttpReady(
119
- config.url,
120
- port,
121
- config.delay || 0,
122
- 3,
123
- // Default retry frequency
124
- callbackHeaders
125
- );
126
- });
127
- }
128
- function fireAndForgetWaitForHttpReady(url, port, initialDelaySeconds = 0, retryFrequencySeconds = 3, headers) {
129
- if (!isSupportedPlatform()) {
130
- console.warn(
131
- `Queue: fireAndForgetWaitForHttpReady is not supported on ${process.platform}. This function requires bash, nc, and curl which are available on macOS and Linux only.`
132
- );
133
- return;
71
+ async deserialize(stream) {
72
+ const buffer = await streamToBuffer(stream);
73
+ return JSON.parse(buffer.toString("utf8"), this.reviver);
134
74
  }
135
- let headerArgs = "";
136
- if (headers) {
137
- const headerArray = [];
138
- headers.forEach((value, key) => {
139
- headerArray.push(`-H '${key}: ${value}'`);
140
- });
141
- headerArgs = headerArray.join(" ");
75
+ };
76
+ var BufferTransport = class {
77
+ contentType = "application/octet-stream";
78
+ serialize(value) {
79
+ return value;
80
+ }
81
+ async deserialize(stream) {
82
+ return await streamToBuffer(stream);
83
+ }
84
+ };
85
+ var StreamTransport = class {
86
+ contentType = "application/octet-stream";
87
+ serialize(value) {
88
+ return value;
89
+ }
90
+ async deserialize(stream) {
91
+ return stream;
142
92
  }
143
- const bashScript = `
144
- # Wait for any initial boot time
145
- sleep ${initialDelaySeconds}
93
+ async finalize(payload) {
94
+ const reader = payload.getReader();
95
+ try {
96
+ while (true) {
97
+ const { done } = await reader.read();
98
+ if (done) break;
99
+ }
100
+ } finally {
101
+ reader.releaseLock();
102
+ }
103
+ }
104
+ };
146
105
 
147
- missed=0
148
- while true; do
149
- # 1) Check if TCP port is listening
150
- if nc -z localhost ${port} 2>/dev/null; then
151
- missed=0
152
- # 2) If port is open, try HTTP POST check
153
- if curl -sSL --fail -o /dev/null -X POST ${headerArgs} "${url}"; then
154
- # Success: port is up AND HTTP returned 2xx (following redirects)
155
- exit 0
156
- fi
157
- else
158
- # Port was closed\u2014increment miss counter
159
- ((missed+=1))
160
- # If closed twice in a row, give up immediately
161
- if [ "$missed" -ge 2 ]; then
162
- exit 1
163
- fi
164
- fi
165
- # Wait before next cycle
166
- sleep ${retryFrequencySeconds}
167
- done
168
- `;
169
- const childProcess = (0, import_node_child_process.spawn)("bash", ["-c", bashScript], {
170
- stdio: "ignore",
171
- detached: true
172
- });
173
- childProcess.unref();
174
- }
106
+ // src/client.ts
107
+ var import_mixpart = require("mixpart");
108
+
109
+ // src/oidc.ts
110
+ var import_oidc = require("@vercel/oidc");
175
111
 
176
112
  // src/types.ts
177
113
  var MessageNotFoundError = class extends Error {
@@ -188,16 +124,6 @@ var MessageNotAvailableError = class extends Error {
188
124
  this.name = "MessageNotAvailableError";
189
125
  }
190
126
  };
191
- 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
- );
197
- this.name = "FifoOrderingViolationError";
198
- this.nextMessageId = nextMessageId;
199
- }
200
- };
201
127
  var MessageCorruptedError = class extends Error {
202
128
  constructor(messageId, reason) {
203
129
  super(`Message ${messageId} is corrupted: ${reason}`);
@@ -239,16 +165,6 @@ var BadRequestError = class extends Error {
239
165
  this.name = "BadRequestError";
240
166
  }
241
167
  };
242
- var FailedDependencyError = class extends Error {
243
- nextMessageId;
244
- constructor(messageId, nextMessageId) {
245
- super(
246
- `Failed dependency: FIFO ordering violation for message ${messageId}. Must process message ${nextMessageId} first.`
247
- );
248
- this.name = "FailedDependencyError";
249
- this.nextMessageId = nextMessageId;
250
- }
251
- };
252
168
  var InternalServerError = class extends Error {
253
169
  constructor(message = "Unexpected server error") {
254
170
  super(message);
@@ -261,12 +177,6 @@ var InvalidLimitError = class extends Error {
261
177
  this.name = "InvalidLimitError";
262
178
  }
263
179
  };
264
- var InvalidCallbackError = class extends Error {
265
- constructor(message) {
266
- super(message);
267
- this.name = "InvalidCallbackError";
268
- }
269
- };
270
180
 
271
181
  // src/client.ts
272
182
  async function consumeStream(stream) {
@@ -296,40 +206,39 @@ function parseQueueHeaders(headers) {
296
206
  return {
297
207
  messageId,
298
208
  deliveryCount,
299
- timestamp,
209
+ createdAt: new Date(timestamp),
300
210
  contentType,
301
211
  ticket
302
212
  };
303
213
  }
304
- var QueueClient = class _QueueClient {
214
+ var QueueClient = class {
305
215
  baseUrl;
306
- token;
216
+ basePath;
217
+ customHeaders = {};
307
218
  /**
308
219
  * Create a new Vercel Queue Service client
309
220
  * @param options Client configuration options
310
221
  */
311
- constructor(options) {
312
- this.baseUrl = options.baseUrl || "https://vqs.vercel.sh";
313
- this.token = options.token;
222
+ constructor(options = {}) {
223
+ this.baseUrl = options.baseUrl || process.env.VERCEL_QUEUE_BASE_URL || "https://vercel-queue.com";
224
+ this.basePath = options.basePath || process.env.VERCEL_QUEUE_BASE_PATH || "/api/v2/messages";
225
+ const VERCEL_QUEUE_HEADER_PREFIX = "VERCEL_QUEUE_HEADER_";
226
+ this.customHeaders = Object.fromEntries(
227
+ Object.entries(process.env).filter(([key]) => key.startsWith(VERCEL_QUEUE_HEADER_PREFIX)).map(([key, value]) => [
228
+ // This allows headers to use dashes independent of shell used
229
+ key.replace(VERCEL_QUEUE_HEADER_PREFIX, "").replaceAll("__", "-"),
230
+ value || ""
231
+ ])
232
+ );
314
233
  }
315
- /**
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
321
- */
322
- static async fromVercelFunction(baseUrl) {
323
- const token = await getVercelOidcToken();
234
+ async getToken() {
235
+ const token = await (0, import_oidc.getVercelOidcToken)();
324
236
  if (!token) {
325
237
  throw new Error(
326
- "Failed to get OIDC token from Vercel Functions. Make sure you are running in a Vercel Function environment."
238
+ "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'"
327
239
  );
328
240
  }
329
- return new _QueueClient({
330
- token,
331
- baseUrl
332
- });
241
+ return token;
333
242
  }
334
243
  /**
335
244
  * Send a message to a queue
@@ -342,51 +251,28 @@ var QueueClient = class _QueueClient {
342
251
  * @throws {InternalServerError} When server encounters an error
343
252
  */
344
253
  async sendMessage(options, transport) {
345
- const { queueName, payload, idempotencyKey, retentionSeconds, callback } = options;
254
+ const { queueName, payload, idempotencyKey, retentionSeconds } = options;
346
255
  const headers = new Headers({
347
- Authorization: `Bearer ${this.token}`,
256
+ Authorization: `Bearer ${await this.getToken()}`,
348
257
  "Vqs-Queue-Name": queueName,
349
- "Content-Type": transport.contentType
258
+ "Content-Type": transport.contentType,
259
+ ...this.customHeaders
350
260
  });
261
+ const deploymentId = options.deploymentId || process.env.VERCEL_DEPLOYMENT_ID;
262
+ if (deploymentId) {
263
+ headers.set("Vqs-Deployment-Id", deploymentId);
264
+ }
351
265
  if (idempotencyKey) {
352
266
  headers.set("Vqs-Idempotency-Key", idempotencyKey);
353
267
  }
354
268
  if (retentionSeconds !== void 0) {
355
269
  headers.set("Vqs-Retention-Seconds", retentionSeconds.toString());
356
270
  }
357
- let normalizedCallbacks;
358
- if (callback) {
359
- if ("url" in callback && typeof callback.url === "string") {
360
- normalizedCallbacks = { default: callback };
361
- } else {
362
- normalizedCallbacks = callback;
363
- }
364
- }
365
- let localhostCallbacks = [];
366
- if (normalizedCallbacks) {
367
- const isDevelopment = process.env.NODE_ENV === "development";
368
- if (isDevelopment) {
369
- localhostCallbacks = processDevelopmentCallbacks(normalizedCallbacks);
370
- } else {
371
- const endpoints = Object.entries(normalizedCallbacks).map(
372
- ([group, config]) => `${group}=${Buffer.from(config.url).toString("base64")}`
373
- ).join(",");
374
- headers.set("Vqs-Callback-Url", endpoints);
375
- const delays = Object.entries(normalizedCallbacks).filter(([, config]) => config.delay !== void 0).map(([group, config]) => `${group}=${config.delay}`).join(",");
376
- if (delays) {
377
- headers.set("Vqs-Callback-Delay", delays);
378
- }
379
- const frequencies = Object.entries(normalizedCallbacks).filter(([, config]) => config.frequency !== void 0).map(([group, config]) => `${group}=${config.frequency}`).join(",");
380
- if (frequencies) {
381
- headers.set("Vqs-Callback-Frequency", frequencies);
382
- }
383
- }
384
- }
385
271
  const body = transport.serialize(payload);
386
- const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
272
+ const response = await fetch(`${this.baseUrl}${this.basePath}`, {
387
273
  method: "POST",
388
- headers,
389
- body
274
+ body,
275
+ headers
390
276
  });
391
277
  if (!response.ok) {
392
278
  if (response.status === 400) {
@@ -412,9 +298,6 @@ var QueueClient = class _QueueClient {
412
298
  );
413
299
  }
414
300
  const responseData = await response.json();
415
- if (localhostCallbacks.length > 0) {
416
- fireLocalhostCallbacks(localhostCallbacks, queueName, responseData);
417
- }
418
301
  return responseData;
419
302
  }
420
303
  /**
@@ -424,7 +307,7 @@ var QueueClient = class _QueueClient {
424
307
  * @returns AsyncGenerator that yields messages as they arrive
425
308
  * @throws {InvalidLimitError} When limit parameter is not between 1 and 10
426
309
  * @throws {QueueEmptyError} When no messages are available (204)
427
- * @throws {MessageLockedError} When FIFO queue has locked messages (423)
310
+ * @throws {MessageLockedError} When messages are temporarily locked (423)
428
311
  * @throws {BadRequestError} When request parameters are invalid
429
312
  * @throws {UnauthorizedError} When authentication fails
430
313
  * @throws {ForbiddenError} When access is denied (environment mismatch)
@@ -436,10 +319,11 @@ var QueueClient = class _QueueClient {
436
319
  throw new InvalidLimitError(limit);
437
320
  }
438
321
  const headers = new Headers({
439
- Authorization: `Bearer ${this.token}`,
322
+ Authorization: `Bearer ${await this.getToken()}`,
440
323
  "Vqs-Queue-Name": queueName,
441
324
  "Vqs-Consumer-Group": consumerGroup,
442
- Accept: "multipart/mixed"
325
+ Accept: "multipart/mixed",
326
+ ...this.customHeaders
443
327
  });
444
328
  if (visibilityTimeoutSeconds !== void 0) {
445
329
  headers.set(
@@ -450,7 +334,7 @@ var QueueClient = class _QueueClient {
450
334
  if (limit !== void 0) {
451
335
  headers.set("Vqs-Limit", limit.toString());
452
336
  }
453
- const response = await fetch(`${this.baseUrl}/api/v2/messages`, {
337
+ const response = await fetch(`${this.baseUrl}${this.basePath}`, {
454
338
  method: "GET",
455
339
  headers
456
340
  });
@@ -475,7 +359,7 @@ var QueueClient = class _QueueClient {
475
359
  const parsed = parseInt(retryAfterHeader, 10);
476
360
  retryAfter = isNaN(parsed) ? void 0 : parsed;
477
361
  }
478
- throw new MessageLockedError("next message in FIFO queue", retryAfter);
362
+ throw new MessageLockedError("next message", retryAfter);
479
363
  }
480
364
  if (response.status >= 500) {
481
365
  throw new InternalServerError(
@@ -517,10 +401,11 @@ var QueueClient = class _QueueClient {
517
401
  skipPayload
518
402
  } = options;
519
403
  const headers = new Headers({
520
- Authorization: `Bearer ${this.token}`,
404
+ Authorization: `Bearer ${await this.getToken()}`,
521
405
  "Vqs-Queue-Name": queueName,
522
406
  "Vqs-Consumer-Group": consumerGroup,
523
- Accept: "multipart/mixed"
407
+ Accept: "multipart/mixed",
408
+ ...this.customHeaders
524
409
  });
525
410
  if (visibilityTimeoutSeconds !== void 0) {
526
411
  headers.set(
@@ -532,7 +417,7 @@ var QueueClient = class _QueueClient {
532
417
  headers.set("Vqs-Skip-Payload", "1");
533
418
  }
534
419
  const response = await fetch(
535
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
420
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
536
421
  {
537
422
  method: "GET",
538
423
  headers
@@ -561,37 +446,7 @@ var QueueClient = class _QueueClient {
561
446
  }
562
447
  throw new MessageLockedError(messageId, retryAfter);
563
448
  }
564
- 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
- );
582
- }
583
449
  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
450
  throw new MessageNotAvailableError(messageId);
596
451
  }
597
452
  if (response.status >= 500) {
@@ -671,14 +526,15 @@ var QueueClient = class _QueueClient {
671
526
  async deleteMessage(options) {
672
527
  const { queueName, consumerGroup, messageId, ticket } = options;
673
528
  const response = await fetch(
674
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
529
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
675
530
  {
676
531
  method: "DELETE",
677
532
  headers: new Headers({
678
- Authorization: `Bearer ${this.token}`,
533
+ Authorization: `Bearer ${await this.getToken()}`,
679
534
  "Vqs-Queue-Name": queueName,
680
535
  "Vqs-Consumer-Group": consumerGroup,
681
- "Vqs-Ticket": ticket
536
+ "Vqs-Ticket": ticket,
537
+ ...this.customHeaders
682
538
  })
683
539
  }
684
540
  );
@@ -732,15 +588,16 @@ var QueueClient = class _QueueClient {
732
588
  visibilityTimeoutSeconds
733
589
  } = options;
734
590
  const response = await fetch(
735
- `${this.baseUrl}/api/v2/messages/${encodeURIComponent(messageId)}`,
591
+ `${this.baseUrl}${this.basePath}/${encodeURIComponent(messageId)}`,
736
592
  {
737
593
  method: "PATCH",
738
594
  headers: new Headers({
739
- Authorization: `Bearer ${this.token}`,
595
+ Authorization: `Bearer ${await this.getToken()}`,
740
596
  "Vqs-Queue-Name": queueName,
741
597
  "Vqs-Consumer-Group": consumerGroup,
742
598
  "Vqs-Ticket": ticket,
743
- "Vqs-Visibility-Timeout": visibilityTimeoutSeconds.toString()
599
+ "Vqs-Visibility-Timeout": visibilityTimeoutSeconds.toString(),
600
+ ...this.customHeaders
744
601
  })
745
602
  }
746
603
  );
@@ -778,81 +635,303 @@ var QueueClient = class _QueueClient {
778
635
  }
779
636
  };
780
637
 
781
- // src/transports.ts
782
- var JsonTransport = class {
783
- contentType = "application/json";
784
- serialize(value) {
785
- return Buffer.from(JSON.stringify(value), "utf8");
638
+ // src/callback.ts
639
+ function validateWildcardPattern(pattern) {
640
+ const firstIndex = pattern.indexOf("*");
641
+ const lastIndex = pattern.lastIndexOf("*");
642
+ if (firstIndex !== lastIndex) {
643
+ return false;
786
644
  }
787
- async deserialize(stream) {
788
- const reader = stream.getReader();
789
- const chunks = [];
790
- try {
791
- while (true) {
792
- const { done, value } = await reader.read();
793
- if (done) break;
794
- chunks.push(value);
645
+ if (firstIndex === -1) {
646
+ return false;
647
+ }
648
+ if (firstIndex !== pattern.length - 1) {
649
+ return false;
650
+ }
651
+ return true;
652
+ }
653
+ function matchesWildcardPattern(topicName, pattern) {
654
+ const prefix = pattern.slice(0, -1);
655
+ return topicName.startsWith(prefix);
656
+ }
657
+ function findTopicHandler(queueName, handlers) {
658
+ const exactHandler = handlers[queueName];
659
+ if (exactHandler) {
660
+ return exactHandler;
661
+ }
662
+ for (const pattern in handlers) {
663
+ if (pattern.includes("*") && matchesWildcardPattern(queueName, pattern)) {
664
+ return handlers[pattern];
665
+ }
666
+ }
667
+ return null;
668
+ }
669
+ async function parseCallback(request) {
670
+ const contentType = request.headers.get("content-type");
671
+ if (!contentType || !contentType.includes("application/cloudevents+json")) {
672
+ throw new Error(
673
+ "Invalid content type: expected 'application/cloudevents+json'"
674
+ );
675
+ }
676
+ let cloudEvent;
677
+ try {
678
+ cloudEvent = await request.json();
679
+ } catch (error) {
680
+ throw new Error("Failed to parse CloudEvent from request body");
681
+ }
682
+ if (!cloudEvent.type || !cloudEvent.source || !cloudEvent.id || typeof cloudEvent.data !== "object" || cloudEvent.data == null) {
683
+ throw new Error("Invalid CloudEvent: missing required fields");
684
+ }
685
+ if (cloudEvent.type !== "com.vercel.queue.v1beta") {
686
+ throw new Error(
687
+ `Invalid CloudEvent type: expected 'com.vercel.queue.v1beta', got '${cloudEvent.type}'`
688
+ );
689
+ }
690
+ const missingFields = [];
691
+ if (!("queueName" in cloudEvent.data)) missingFields.push("queueName");
692
+ if (!("consumerGroup" in cloudEvent.data))
693
+ missingFields.push("consumerGroup");
694
+ if (!("messageId" in cloudEvent.data)) missingFields.push("messageId");
695
+ if (missingFields.length > 0) {
696
+ throw new Error(
697
+ `Missing required CloudEvent data fields: ${missingFields.join(", ")}`
698
+ );
699
+ }
700
+ const { messageId, queueName, consumerGroup } = cloudEvent.data;
701
+ return {
702
+ queueName,
703
+ consumerGroup,
704
+ messageId
705
+ };
706
+ }
707
+ function handleCallback(handlers) {
708
+ for (const topicPattern in handlers) {
709
+ if (topicPattern.includes("*")) {
710
+ if (!validateWildcardPattern(topicPattern)) {
711
+ throw new Error(
712
+ `Invalid wildcard pattern "${topicPattern}": * may only appear once and must be at the end of the topic name`
713
+ );
795
714
  }
796
- } finally {
797
- reader.releaseLock();
798
715
  }
799
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
800
- const buffer = new Uint8Array(totalLength);
801
- let offset = 0;
802
- for (const chunk of chunks) {
803
- buffer.set(chunk, offset);
804
- offset += chunk.length;
716
+ }
717
+ const routeHandler = async (request) => {
718
+ try {
719
+ const { queueName, consumerGroup, messageId } = await parseCallback(request);
720
+ const topicHandler = findTopicHandler(queueName, handlers);
721
+ if (!topicHandler) {
722
+ const availableTopics = Object.keys(handlers).join(", ");
723
+ return Response.json(
724
+ {
725
+ error: `No handler found for topic: ${queueName}`,
726
+ availableTopics
727
+ },
728
+ { status: 404 }
729
+ );
730
+ }
731
+ const consumerGroupHandler = topicHandler[consumerGroup];
732
+ if (!consumerGroupHandler) {
733
+ const availableGroups = Object.keys(topicHandler).join(", ");
734
+ return Response.json(
735
+ {
736
+ error: `No handler found for consumer group "${consumerGroup}" in topic "${queueName}".`,
737
+ availableGroups
738
+ },
739
+ { status: 404 }
740
+ );
741
+ }
742
+ const client = new QueueClient();
743
+ const topic = new Topic(client, queueName);
744
+ const cg = topic.consumerGroup(consumerGroup);
745
+ await cg.consume(consumerGroupHandler, { messageId });
746
+ return Response.json({ status: "success" });
747
+ } catch (error) {
748
+ console.error("Queue callback error:", error);
749
+ 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"))) {
750
+ return Response.json({ error: error.message }, { status: 400 });
751
+ }
752
+ return Response.json(
753
+ { error: "Failed to process queue message" },
754
+ { status: 500 }
755
+ );
805
756
  }
806
- return JSON.parse(Buffer.from(buffer).toString("utf8"));
757
+ };
758
+ if (isDevMode()) {
759
+ registerDevRouteHandler(routeHandler, handlers);
807
760
  }
808
- };
809
- var BufferTransport = class {
810
- contentType = "application/octet-stream";
811
- serialize(value) {
812
- return value;
761
+ return routeHandler;
762
+ }
763
+
764
+ // src/dev.ts
765
+ var devRouteHandlers = /* @__PURE__ */ new Map();
766
+ var wildcardRouteHandlers = /* @__PURE__ */ new Map();
767
+ function cleanupDeadRefs(key, refs) {
768
+ const aliveRefs = refs.filter((ref) => ref.deref() !== void 0);
769
+ if (aliveRefs.length === 0) {
770
+ wildcardRouteHandlers.delete(key);
771
+ } else if (aliveRefs.length < refs.length) {
772
+ wildcardRouteHandlers.set(key, aliveRefs);
813
773
  }
814
- async deserialize(stream) {
815
- const reader = stream.getReader();
816
- const chunks = [];
817
- try {
818
- while (true) {
819
- const { done, value } = await reader.read();
820
- if (done) break;
821
- chunks.push(value);
774
+ }
775
+ function isDevMode() {
776
+ return process.env.NODE_ENV === "development";
777
+ }
778
+ function registerDevRouteHandler(routeHandler, handlers) {
779
+ for (const topicName in handlers) {
780
+ for (const consumerGroup in handlers[topicName]) {
781
+ const key = `${topicName}:${consumerGroup}`;
782
+ if (topicName.includes("*")) {
783
+ const existing = wildcardRouteHandlers.get(key) || [];
784
+ cleanupDeadRefs(key, existing);
785
+ const cleanedRefs = wildcardRouteHandlers.get(key) || [];
786
+ const weakRef = new WeakRef(routeHandler);
787
+ cleanedRefs.push(weakRef);
788
+ wildcardRouteHandlers.set(key, cleanedRefs);
789
+ } else {
790
+ devRouteHandlers.set(key, {
791
+ routeHandler,
792
+ topicPattern: topicName
793
+ });
822
794
  }
823
- } finally {
824
- reader.releaseLock();
825
795
  }
826
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
827
- const buffer = new Uint8Array(totalLength);
828
- let offset = 0;
829
- for (const chunk of chunks) {
830
- buffer.set(chunk, offset);
831
- offset += chunk.length;
796
+ }
797
+ }
798
+ function findRouteHandlersForTopic(topicName) {
799
+ const handlersMap = /* @__PURE__ */ new Map();
800
+ for (const [
801
+ key,
802
+ { routeHandler, topicPattern }
803
+ ] of devRouteHandlers.entries()) {
804
+ const [_, consumerGroup] = key.split(":");
805
+ if (topicPattern === topicName) {
806
+ if (!handlersMap.has(routeHandler)) {
807
+ handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
808
+ }
809
+ handlersMap.get(routeHandler).add(consumerGroup);
832
810
  }
833
- return Buffer.from(buffer);
834
811
  }
835
- };
836
- var StreamTransport = class {
837
- contentType = "application/octet-stream";
838
- serialize(value) {
839
- return value;
812
+ for (const [key, refs] of wildcardRouteHandlers.entries()) {
813
+ const [pattern, consumerGroup] = key.split(":");
814
+ if (matchesWildcardPattern(topicName, pattern)) {
815
+ cleanupDeadRefs(key, refs);
816
+ const cleanedRefs = wildcardRouteHandlers.get(key) || [];
817
+ for (const ref of cleanedRefs) {
818
+ const routeHandler = ref.deref();
819
+ if (routeHandler) {
820
+ if (!handlersMap.has(routeHandler)) {
821
+ handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
822
+ }
823
+ handlersMap.get(routeHandler).add(consumerGroup);
824
+ }
825
+ }
826
+ }
840
827
  }
841
- async deserialize(stream) {
842
- return stream;
828
+ return handlersMap;
829
+ }
830
+ function createMockCloudEventRequest(topicName, consumerGroup, messageId) {
831
+ const cloudEvent = {
832
+ type: "com.vercel.queue.v1beta",
833
+ source: `/topic/${topicName}/consumer/${consumerGroup}`,
834
+ id: messageId,
835
+ datacontenttype: "application/json",
836
+ data: {
837
+ messageId,
838
+ queueName: topicName,
839
+ consumerGroup
840
+ },
841
+ time: (/* @__PURE__ */ new Date()).toISOString(),
842
+ specversion: "1.0"
843
+ };
844
+ return new Request("https://localhost/api/queue/callback", {
845
+ method: "POST",
846
+ headers: {
847
+ "Content-Type": "application/cloudevents+json"
848
+ },
849
+ body: JSON.stringify(cloudEvent)
850
+ });
851
+ }
852
+ var DEV_CALLBACK_DELAY = 1e3;
853
+ function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
854
+ console.log(
855
+ `[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
856
+ );
857
+ setTimeout(
858
+ () => {
859
+ console.log(
860
+ `[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
861
+ );
862
+ triggerDevCallbacks(topicName, messageId);
863
+ },
864
+ timeoutSeconds * 1e3 + DEV_CALLBACK_DELAY
865
+ );
866
+ }
867
+ function triggerDevCallbacks(topicName, messageId) {
868
+ const handlersMap = findRouteHandlersForTopic(topicName);
869
+ if (handlersMap.size === 0) {
870
+ return;
843
871
  }
844
- async finalize(payload) {
845
- const reader = payload.getReader();
846
- try {
847
- while (true) {
848
- const { done } = await reader.read();
849
- if (done) break;
872
+ const consumerGroups = Array.from(
873
+ new Set(
874
+ Array.from(handlersMap.values()).flatMap((groups) => Array.from(groups))
875
+ )
876
+ );
877
+ console.log(
878
+ `[Dev Mode] Triggering local callbacks for topic "${topicName}" \u2192 consumers: ${consumerGroups.join(", ")}`
879
+ );
880
+ setTimeout(async () => {
881
+ for (const [routeHandler, consumerGroups2] of handlersMap.entries()) {
882
+ for (const consumerGroup of consumerGroups2) {
883
+ try {
884
+ const request = createMockCloudEventRequest(
885
+ topicName,
886
+ consumerGroup,
887
+ messageId
888
+ );
889
+ const response = await routeHandler(request);
890
+ if (response.ok) {
891
+ try {
892
+ const responseData = await response.json();
893
+ if (responseData.status === "success") {
894
+ console.log(
895
+ `[Dev Mode] Message processed for ${topicName}/${consumerGroup}`
896
+ );
897
+ }
898
+ } catch (jsonError) {
899
+ console.error(
900
+ `[Dev Mode] Failed to parse success response for ${topicName}/${consumerGroup}:`,
901
+ jsonError
902
+ );
903
+ }
904
+ } else {
905
+ try {
906
+ const errorData = await response.json();
907
+ console.error(
908
+ `[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
909
+ errorData.error || response.statusText
910
+ );
911
+ } catch (jsonError) {
912
+ console.error(
913
+ `[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
914
+ response.statusText
915
+ );
916
+ }
917
+ }
918
+ } catch (error) {
919
+ console.error(
920
+ `[Dev Mode] Error triggering callback for ${topicName}/${consumerGroup}:`,
921
+ error
922
+ );
923
+ }
850
924
  }
851
- } finally {
852
- reader.releaseLock();
853
925
  }
854
- }
855
- };
926
+ }, DEV_CALLBACK_DELAY);
927
+ }
928
+ function clearDevHandlers() {
929
+ devRouteHandlers.clear();
930
+ wildcardRouteHandlers.clear();
931
+ }
932
+ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
933
+ globalThis.__clearDevHandlers = clearDevHandlers;
934
+ }
856
935
 
857
936
  // src/consumer-group.ts
858
937
  var ConsumerGroup = class {
@@ -953,7 +1032,13 @@ var ConsumerGroup = class {
953
1032
  message.ticket
954
1033
  );
955
1034
  try {
956
- const result = await handler(message);
1035
+ const result = await handler(message.payload, {
1036
+ messageId: message.messageId,
1037
+ deliveryCount: message.deliveryCount,
1038
+ createdAt: message.createdAt,
1039
+ topicName: this.topicName,
1040
+ consumerGroup: this.consumerGroupName
1041
+ });
957
1042
  await stopExtension();
958
1043
  if (result && "timeoutSeconds" in result) {
959
1044
  await this.client.changeVisibility({
@@ -963,6 +1048,13 @@ var ConsumerGroup = class {
963
1048
  ticket: message.ticket,
964
1049
  visibilityTimeoutSeconds: result.timeoutSeconds
965
1050
  });
1051
+ if (isDevMode()) {
1052
+ scheduleDevTimeout(
1053
+ this.topicName,
1054
+ message.messageId,
1055
+ result.timeoutSeconds
1056
+ );
1057
+ }
966
1058
  } else {
967
1059
  await this.client.deleteMessage({
968
1060
  queueName: this.topicName,
@@ -983,219 +1075,58 @@ var ConsumerGroup = class {
983
1075
  throw error;
984
1076
  }
985
1077
  }
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(
1078
+ async consume(handler, options) {
1079
+ if (options?.messageId) {
1080
+ if (options.skipPayload) {
1081
+ const response = await this.client.receiveMessageById(
998
1082
  {
999
1083
  queueName: this.topicName,
1000
1084
  consumerGroup: this.consumerGroupName,
1085
+ messageId: options.messageId,
1001
1086
  visibilityTimeoutSeconds: this.visibilityTimeout,
1002
- limit: 1
1003
- // Always process one message at a time
1087
+ skipPayload: true
1004
1088
  },
1005
1089
  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;
1090
+ );
1091
+ await this.processMessage(
1092
+ response.message,
1093
+ handler
1094
+ );
1095
+ } else {
1096
+ const response = await this.client.receiveMessageById(
1097
+ {
1098
+ queueName: this.topicName,
1099
+ consumerGroup: this.consumerGroupName,
1100
+ messageId: options.messageId,
1101
+ visibilityTimeoutSeconds: this.visibilityTimeout
1102
+ },
1103
+ this.transport
1104
+ );
1105
+ await this.processMessage(
1106
+ response.message,
1107
+ handler
1108
+ );
1109
+ }
1110
+ } else {
1111
+ let messageFound = false;
1112
+ for await (const message of this.client.receiveMessages(
1113
+ {
1114
+ queueName: this.topicName,
1115
+ consumerGroup: this.consumerGroupName,
1116
+ visibilityTimeoutSeconds: this.visibilityTimeout,
1117
+ limit: 1
1118
+ },
1119
+ this.transport
1120
+ )) {
1121
+ messageFound = true;
1122
+ await this.processMessage(message, handler);
1123
+ break;
1124
+ }
1125
+ if (!messageFound) {
1126
+ throw new Error("No messages available");
1065
1127
  }
1066
1128
  }
1067
1129
  }
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
1130
  /**
1200
1131
  * Get the consumer group name
1201
1132
  */
@@ -1243,10 +1174,13 @@ var Topic = class {
1243
1174
  payload,
1244
1175
  idempotencyKey: options?.idempotencyKey,
1245
1176
  retentionSeconds: options?.retentionSeconds,
1246
- callback: options?.callback
1177
+ deploymentId: options?.deploymentId
1247
1178
  },
1248
1179
  this.transport
1249
1180
  );
1181
+ if (isDevMode()) {
1182
+ triggerDevCallbacks(this.topicName, result.messageId);
1183
+ }
1250
1184
  return { messageId: result.messageId };
1251
1185
  }
1252
1186
  /**
@@ -1282,108 +1216,61 @@ var Topic = class {
1282
1216
  };
1283
1217
 
1284
1218
  // src/factory.ts
1285
- function createTopic(client, topicName, transport) {
1286
- return new Topic(client, topicName, transport);
1287
- }
1288
-
1289
- // src/callback.ts
1290
- function parseCallbackRequest(request) {
1291
- const headers = request.headers;
1292
- const messageId = headers.get("Vqs-Message-Id");
1293
- const queueName = headers.get("Vqs-Queue-Name");
1294
- const consumerGroup = headers.get("Vqs-Consumer-Group");
1295
- const missingHeaders = [];
1296
- if (!messageId) missingHeaders.push("Vqs-Message-Id");
1297
- if (!queueName) missingHeaders.push("Vqs-Queue-Name");
1298
- if (!consumerGroup) missingHeaders.push("Vqs-Consumer-Group");
1299
- if (missingHeaders.length > 0) {
1300
- throw new InvalidCallbackError(
1301
- `Missing required queue callback headers: ${missingHeaders.join(", ")}`
1302
- );
1219
+ async function send(topicName, payload, options) {
1220
+ const transport = options?.transport || new JsonTransport();
1221
+ const client = new QueueClient();
1222
+ const result = await client.sendMessage(
1223
+ {
1224
+ queueName: topicName,
1225
+ payload,
1226
+ idempotencyKey: options?.idempotencyKey,
1227
+ retentionSeconds: options?.retentionSeconds,
1228
+ deploymentId: options?.deploymentId
1229
+ },
1230
+ transport
1231
+ );
1232
+ if (isDevMode()) {
1233
+ triggerDevCallbacks(topicName, result.messageId);
1303
1234
  }
1304
- return {
1305
- messageId,
1306
- queueName,
1307
- consumerGroup
1308
- };
1235
+ return { messageId: result.messageId };
1309
1236
  }
1310
- function handleCallback(handlers) {
1311
- return async (request) => {
1312
- try {
1313
- const { queueName, consumerGroup, messageId } = parseCallbackRequest(request);
1314
- const topicHandler = handlers[queueName];
1315
- if (!topicHandler) {
1316
- throw new Error(`No handler found for topic: ${queueName}`);
1317
- }
1318
- let actualHandler;
1319
- if (typeof topicHandler === "function") {
1320
- if (consumerGroup !== "default") {
1321
- throw new Error(
1322
- `Topic "${queueName}" has a single handler but received consumer group "${consumerGroup}". Expected "default".`
1323
- );
1324
- }
1325
- actualHandler = topicHandler;
1326
- } else {
1327
- const consumerGroupHandler = topicHandler[consumerGroup];
1328
- if (!consumerGroupHandler) {
1329
- const availableGroups = Object.keys(topicHandler).join(", ");
1330
- throw new Error(
1331
- `No handler found for consumer group "${consumerGroup}" in topic "${queueName}". Available groups: ${availableGroups}`
1332
- );
1333
- }
1334
- actualHandler = consumerGroupHandler;
1335
- }
1336
- const client = await QueueClient.fromVercelFunction();
1337
- const topic = new Topic(client, queueName);
1338
- 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);
1237
+ async function receive(topicName, consumerGroup, handler, options) {
1238
+ const transport = options?.transport || new JsonTransport();
1239
+ const client = new QueueClient();
1240
+ const topic = new Topic(client, topicName, transport);
1241
+ const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
1242
+ const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
1243
+ if (messageId) {
1244
+ if (skipPayload) {
1245
+ return consumer.consume(handler, {
1246
+ messageId,
1247
+ skipPayload: true
1346
1248
  });
1347
- return Response.json({ status: "success" });
1348
- } catch (error) {
1349
- console.error("Callback error:", error);
1350
- if (error instanceof InvalidCallbackError) {
1351
- return Response.json(
1352
- { error: "Invalid callback request" },
1353
- { status: 400 }
1354
- );
1355
- }
1356
- return Response.json(
1357
- { error: "Failed to process callback" },
1358
- { status: 500 }
1359
- );
1249
+ } else {
1250
+ return consumer.consume(handler, { messageId });
1360
1251
  }
1361
- };
1252
+ } else {
1253
+ return consumer.consume(handler);
1254
+ }
1362
1255
  }
1363
1256
  // Annotate the CommonJS export names for ESM import in node:
1364
1257
  0 && (module.exports = {
1365
1258
  BadRequestError,
1366
1259
  BufferTransport,
1367
- ConsumerGroup,
1368
- FailedDependencyError,
1369
- FifoOrderingViolationError,
1370
1260
  ForbiddenError,
1371
1261
  InternalServerError,
1372
- InvalidCallbackError,
1373
1262
  InvalidLimitError,
1374
1263
  JsonTransport,
1375
1264
  MessageCorruptedError,
1376
1265
  MessageLockedError,
1377
1266
  MessageNotAvailableError,
1378
1267
  MessageNotFoundError,
1379
- QueueClient,
1380
1268
  QueueEmptyError,
1381
1269
  StreamTransport,
1382
- Topic,
1383
1270
  UnauthorizedError,
1384
- createTopic,
1385
- getVercelOidcToken,
1386
1271
  handleCallback,
1387
- parseCallbackRequest
1272
+ parseCallback,
1273
+ receive,
1274
+ send
1388
1275
  });
1389
1276
  //# sourceMappingURL=index.js.map