@vercel/queue 0.0.0-alpha.9 → 0.0.2

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.d.mts CHANGED
@@ -1,37 +1,83 @@
1
1
  /**
2
- * Serializer/Deserializer interface for message payloads
2
+ * Serializer/Deserializer interface for message payloads.
3
+ *
4
+ * Built-in implementations:
5
+ * - `JsonTransport` - JSON serialization (default, buffers entire payload)
6
+ * - `BufferTransport` - Binary data (buffers entire payload)
7
+ * - `StreamTransport` - Pass-through streaming (no buffering, ideal for large payloads)
3
8
  */
4
9
  interface Transport<T = unknown> {
5
10
  /**
6
- * Serialize a value to a buffer or stream for transmission
11
+ * Serialize a value to a buffer or stream for transmission.
12
+ * The result is sent as the request body with the `contentType` header.
7
13
  */
8
14
  serialize(value: T): Buffer | ReadableStream<Uint8Array>;
9
15
  /**
10
- * Deserialize a readable stream back to the original value
16
+ * Deserialize a readable stream back to the original value.
17
+ * Called when receiving messages from the queue.
11
18
  */
12
19
  deserialize(stream: ReadableStream<Uint8Array>): Promise<T>;
13
20
  /**
14
- * Optional cleanup method for deserialized payloads that may contain resources
15
- * Should be called when the payload is no longer needed, especially in error cases
21
+ * Optional cleanup method for deserialized payloads that may contain resources.
22
+ * Called automatically by ConsumerGroup on error; manual cleanup required for direct client usage.
16
23
  * @param payload The deserialized payload to clean up
17
24
  */
18
25
  finalize?(payload: T): Promise<void>;
19
26
  /**
20
- * MIME type for this serialization format
27
+ * MIME type for this serialization format.
28
+ * Sent as the Content-Type header when publishing messages.
21
29
  */
22
30
  contentType: string;
23
31
  }
24
32
  /**
25
- * Built-in JSON serializer/deserializer
26
- * This implementation reads the entire stream into memory for JSON parsing
33
+ * JSON serializer/deserializer (default transport).
34
+ *
35
+ * Ideal for structured data. Buffers the entire payload in memory for parsing.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // Default (JsonTransport is used automatically)
40
+ * const queue = new QueueClient({ region: process.env.QUEUE_REGION! });
41
+ * await queue.send("topic", { data: "example" });
42
+ *
43
+ * // With custom serialization
44
+ * const queue = new QueueClient({
45
+ * region: process.env.QUEUE_REGION!,
46
+ * transport: new JsonTransport({
47
+ * replacer: (key, value) => key === "password" ? undefined : value,
48
+ * reviver: (key, value) => key === "date" ? new Date(value) : value,
49
+ * }),
50
+ * });
51
+ * await queue.send("topic", { data: "example" });
52
+ * ```
27
53
  */
28
54
  declare class JsonTransport<T = unknown> implements Transport<T> {
29
55
  readonly contentType = "application/json";
56
+ readonly replacer?: Parameters<typeof JSON.stringify>[1];
57
+ readonly reviver?: Parameters<typeof JSON.parse>[1];
58
+ /**
59
+ * Create a new JsonTransport.
60
+ * @param options - Optional JSON serialization options
61
+ * @param options.replacer - Custom replacer for JSON.stringify
62
+ * @param options.reviver - Custom reviver for JSON.parse
63
+ */
64
+ constructor(options?: {
65
+ replacer?: Parameters<typeof JSON.stringify>[1];
66
+ reviver?: Parameters<typeof JSON.parse>[1];
67
+ });
30
68
  serialize(value: T): Buffer;
31
69
  deserialize(stream: ReadableStream<Uint8Array>): Promise<T>;
32
70
  }
33
71
  /**
34
- * Built-in Buffer serializer/deserializer (reads entire stream into a Buffer)
72
+ * Binary data serializer/deserializer.
73
+ *
74
+ * Ideal for binary data that fits in memory. Buffers the entire payload.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const queue = new QueueClient({ region: "iad1", transport: new BufferTransport() });
79
+ * await queue.send("binary-topic", myBuffer);
80
+ * ```
35
81
  */
36
82
  declare class BufferTransport implements Transport<Buffer> {
37
83
  readonly contentType = "application/octet-stream";
@@ -39,31 +85,36 @@ declare class BufferTransport implements Transport<Buffer> {
39
85
  deserialize(stream: ReadableStream<Uint8Array>): Promise<Buffer>;
40
86
  }
41
87
  /**
42
- * Stream serializer/deserializer (pass-through for streaming binary data)
43
- * This is ideal for large payloads that don't need to be buffered in memory
88
+ * Pass-through stream serializer/deserializer.
44
89
  *
45
- * IMPORTANT: When using StreamTransport, you must call finalize(payload) when done
46
- * processing the message to prevent resource leaks, especially in error cases.
47
- * ConsumerGroup handles this automatically, but direct QueueClient usage requires manual cleanup.
90
+ * Ideal for large files and real-time streams. Does not buffer data in memory.
48
91
  *
49
- * Example usage:
92
+ * **Important:** When using StreamTransport, you must call `finalize(payload)` when done
93
+ * processing to prevent resource leaks. ConsumerGroup handles this automatically;
94
+ * direct client usage requires manual cleanup.
95
+ *
96
+ * @example
50
97
  * ```typescript
51
- * const transport = new StreamTransport();
52
- * try {
53
- * for await (const message of client.receiveMessages(options, transport)) {
54
- * // Process the stream...
55
- * const reader = message.payload.getReader();
56
- * // ... handle stream data
57
- * }
58
- * } catch (error) {
59
- * // Cleanup is handled automatically by ConsumerGroup
60
- * // or manually: await transport.finalize(message.payload);
61
- * }
98
+ * const queue = new QueueClient({ region: "iad1", transport: new StreamTransport() });
99
+ *
100
+ * // Sending a stream
101
+ * await queue.send("large-file", myReadableStream);
102
+ *
103
+ * // Receiving - cleanup handled automatically
104
+ * await queue.receive("large-file", "processor", async (stream, meta) => {
105
+ * const reader = stream.getReader();
106
+ * // Process chunks...
107
+ * });
108
+ * ```
62
109
  */
63
110
  declare class StreamTransport implements Transport<ReadableStream<Uint8Array>> {
64
111
  readonly contentType = "application/octet-stream";
65
112
  serialize(value: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
66
113
  deserialize(stream: ReadableStream<Uint8Array>): Promise<ReadableStream<Uint8Array>>;
114
+ /**
115
+ * Consume any remaining stream data to prevent resource leaks.
116
+ * Called automatically by ConsumerGroup; manual call required for direct client usage.
117
+ */
67
118
  finalize(payload: ReadableStream<Uint8Array>): Promise<void>;
68
119
  }
69
120
 
@@ -72,120 +123,292 @@ declare class StreamTransport implements Transport<ReadableStream<Uint8Array>> {
72
123
  */
73
124
 
74
125
  /**
75
- * Shared options for publishing messages
126
+ * Vercel region code. The 20 known codes match
127
+ * {@link https://dcs.vercel-infra.com/ | dcs.vercel-infra.com}.
128
+ * Arbitrary strings are also accepted for forward compatibility
129
+ * with regions added after this SDK version.
130
+ */
131
+ type VercelRegion = "arn1" | "bom1" | "cdg1" | "cle1" | "cpt1" | "dub1" | "dxb1" | "fra1" | "gru1" | "hkg1" | "hnd1" | "iad1" | "icn1" | "kix1" | "lhr1" | "pdx1" | "sfo1" | "sin1" | "syd1" | "yul1" | (string & {});
132
+ /**
133
+ * Resolves a region code to a base {@link URL} for the Vercel Queue Service API.
134
+ *
135
+ * The SDK appends its own API path (`/api/v3/…`) to the returned URL.
136
+ * To add a prefix (e.g. when routing through a reverse proxy), include it
137
+ * in the pathname of the returned URL:
138
+ *
139
+ * ```ts
140
+ * // Default — domain only, no prefix:
141
+ * (region) => new URL(`https://${region}.vercel-queue.com`)
142
+ *
143
+ * // Custom domain with a base path:
144
+ * (region) => new URL(`https://my-proxy.example/custom-prefix`)
145
+ * // → requests go to https://my-proxy.example/custom-prefix/api/v3/…
146
+ * ```
147
+ *
148
+ * Default: `` (region) => new URL(`https://${region}.vercel-queue.com`) ``
76
149
  */
77
- interface PublishOptions {
150
+ type BaseUrlResolver = (region: string) => URL;
151
+ interface QueueClientOptions {
152
+ /**
153
+ * Vercel region code for API routing.
154
+ *
155
+ * Requests are sent to the regional endpoint resolved by
156
+ * {@link BaseUrlResolver} (default: `https://${region}.vercel-queue.com`).
157
+ *
158
+ * Use a `QUEUE_REGION` env var set to `${VERCEL_REGION}` in production
159
+ * and a fixed region (e.g. `"iad1"`) in development.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * new QueueClient({ region: process.env.QUEUE_REGION! })
164
+ * ```
165
+ */
166
+ region: VercelRegion;
78
167
  /**
79
- * Unique key to prevent duplicate message submissions
80
- * @default random UUID
168
+ * Custom resolver that maps a region code to a base {@link URL}.
169
+ *
170
+ * The SDK always appends its own API path (`/api/v3/…`) to the returned URL.
171
+ * Include a pathname to add a prefix (e.g. for reverse-proxy routing):
172
+ *
173
+ * ```ts
174
+ * resolveBaseUrl: (region) => new URL(`https://my-proxy.example/prefix`)
175
+ * ```
176
+ *
177
+ * @default (region) => new URL(`https://${region}.vercel-queue.com`)
178
+ */
179
+ resolveBaseUrl?: BaseUrlResolver;
180
+ /**
181
+ * Authentication token for the Vercel Queue Service API.
182
+ * If not provided, the client will attempt to get a token via OIDC
183
+ * when running in a Vercel Function environment.
184
+ */
185
+ token?: string;
186
+ /**
187
+ * Custom headers to include in all requests.
188
+ */
189
+ headers?: Record<string, string>;
190
+ /**
191
+ * Deployment ID for message routing and lease validation.
192
+ *
193
+ * - `undefined` (default): auto-detect from `VERCEL_DEPLOYMENT_ID` env var;
194
+ * sent messages are pinned to this deployment.
195
+ * - `null`: explicitly unpinned — no deployment ID is sent on any request.
196
+ * - `"dpl_xxx"`: explicit value used for both send pinning and consume identification.
197
+ *
198
+ * Ignored in development mode (deployment ID is never sent locally).
199
+ */
200
+ deploymentId?: string | null;
201
+ /**
202
+ * Serializer/deserializer for message payloads.
203
+ * Used for all send and receive operations.
204
+ * @default JsonTransport
205
+ */
206
+ transport?: Transport;
207
+ }
208
+ /**
209
+ * Options for sending messages.
210
+ */
211
+ interface SendOptions {
212
+ /**
213
+ * Unique key to prevent duplicate message submissions.
214
+ * Deduplication window: `min(message retention, 24 hours)`.
81
215
  */
82
216
  idempotencyKey?: string;
83
217
  /**
84
- * Message retention time in seconds
218
+ * Message retention time in seconds. After this period, the message expires.
85
219
  * @default 86400 (24 hours)
86
- * @min 60
87
- * @max 86400
220
+ * @minimum 60 (1 minute)
221
+ * @maximum 86400 (24 hours)
88
222
  */
89
223
  retentionSeconds?: number;
90
- }
91
- interface SendMessageOptions<T = unknown> extends PublishOptions {
92
224
  /**
93
- * The queue name to send the message to
225
+ * Delay delivery of the message by this many seconds.
226
+ * The message will not be visible to consumers until the delay has passed.
227
+ * @default 0
228
+ * @minimum 0
229
+ * @maximum Value of retentionSeconds (delay cannot exceed retention)
94
230
  */
95
- queueName: string;
231
+ delaySeconds?: number;
96
232
  /**
97
- * The message payload
233
+ * Custom headers to include with this message.
234
+ * These headers are passed through to the VQS server.
98
235
  */
99
- payload: T;
236
+ headers?: Record<string, string>;
100
237
  }
101
- interface SendMessageResponse {
102
- /**
103
- * The generated message ID
104
- */
105
- messageId: string;
238
+ /**
239
+ * Result returned by `send()`.
240
+ *
241
+ * `messageId` is `null` when the server accepted the message for deferred
242
+ * processing (e.g. during a server-side outage) and no ID is available yet.
243
+ */
244
+ interface SendResult {
245
+ messageId: string | null;
106
246
  }
107
247
  interface Message<T = unknown> {
108
248
  /**
109
- * The message ID
249
+ * Unique message identifier. Used for receive-by-ID operations.
110
250
  */
111
251
  messageId: string;
112
252
  /**
113
- * The deserialized message payload
114
- * Note: If using streaming transports, ensure proper cleanup by calling transport.finalize(payload)
115
- * when done processing, especially in error cases
253
+ * The deserialized message payload.
254
+ * Note: If using StreamTransport, ensure proper cleanup by calling
255
+ * `transport.finalize(payload)` when done processing, especially in error cases.
116
256
  */
117
257
  payload: T;
118
258
  /**
119
- * Number of times this message has been delivered
259
+ * Number of times this message has been delivered.
260
+ * Starts at 1 for the first delivery, increments on each retry.
120
261
  */
121
262
  deliveryCount: number;
122
263
  /**
123
- * Timestamp when the message was created
264
+ * Timestamp when the message was created/published.
124
265
  */
125
- timestamp: string;
266
+ createdAt: Date;
126
267
  /**
127
- * MIME type of the message content
268
+ * Timestamp when the message expires.
269
+ * Only present for messages delivered via the v2beta binary callback path.
128
270
  */
129
- contentType: string;
271
+ expiresAt?: Date;
130
272
  /**
131
- * Unique ticket for this message delivery (required for delete/patch operations)
273
+ * MIME type of the message content.
274
+ * Set by the transport's `contentType` property during publish.
132
275
  */
133
- ticket: string;
134
- }
135
- /**
136
- * Result indicating the message should be timed out for retry later
137
- */
138
- interface MessageTimeoutResult {
276
+ contentType: string;
139
277
  /**
140
- * Time in seconds before the message becomes visible again
278
+ * Receipt handle (lease token) for this message delivery.
279
+ * Required for acknowledge and visibility extension operations.
280
+ * Valid only until the visibility timeout expires.
141
281
  */
142
- timeoutSeconds: number;
282
+ receiptHandle: string;
143
283
  }
144
- /**
145
- * Result returned by message handlers
146
- */
147
- type MessageHandlerResult = void | MessageTimeoutResult;
148
284
  /**
149
285
  * Message metadata provided to handlers
150
286
  */
151
287
  interface MessageMetadata {
152
288
  messageId: string;
153
289
  deliveryCount: number;
154
- timestamp: string;
290
+ createdAt: Date;
291
+ expiresAt?: Date;
292
+ topicName: string;
293
+ consumerGroup: string;
294
+ /** Vercel region the client is targeting. */
295
+ region: string;
155
296
  }
156
297
  /**
157
- * Message handler function type
298
+ * Instruction returned by a {@link RetryHandler} to control what happens
299
+ * to a message when the handler throws.
300
+ *
301
+ * - `{ afterSeconds: N }` — reschedule the message for redelivery after N seconds
302
+ * - `{ acknowledge: true }` — acknowledge the message so it is never retried
303
+ */
304
+ type RetryDirective = {
305
+ afterSeconds: number;
306
+ } | {
307
+ acknowledge: true;
308
+ };
309
+ /**
310
+ * Called when the message handler throws an error.
311
+ * Return a {@link RetryDirective} to reschedule or acknowledge the message,
312
+ * or return `undefined` to let the error propagate normally.
158
313
  */
159
- type MessageHandler<T = unknown> = (message: T, metadata: MessageMetadata) => Promise<MessageHandlerResult> | MessageHandlerResult;
314
+ type RetryHandler = (error: unknown, metadata: MessageMetadata) => RetryDirective | void | undefined;
160
315
  /**
161
- * Options for creating a ConsumerGroup instance
316
+ * Message handler function type.
317
+ *
318
+ * Called once per message with the deserialized payload and metadata.
319
+ * Not called when the queue is empty — check the return value of `receive()` instead.
320
+ */
321
+ type MessageHandler<T = unknown> = (message: T, metadata: MessageMetadata) => Promise<void> | void;
322
+ /**
323
+ * Result returned by `receive()`. Use `ok` to check if messages were processed.
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * const result = await receive("topic", "group", handler);
328
+ * if (result.ok) {
329
+ * // Messages were processed successfully
330
+ * } else if (result.reason === "empty") {
331
+ * // Queue had no messages available
332
+ * }
333
+ * ```
162
334
  */
163
- interface ConsumerGroupOptions<T = unknown> {
335
+ type ReceiveResult = {
336
+ ok: true;
337
+ } | {
338
+ ok: false;
339
+ reason: "empty";
340
+ } | {
341
+ ok: false;
342
+ reason: "not_found";
343
+ messageId: string;
344
+ } | {
345
+ ok: false;
346
+ reason: "not_available";
347
+ messageId: string;
348
+ } | {
349
+ ok: false;
350
+ reason: "already_processed";
351
+ messageId: string;
352
+ };
353
+ /**
354
+ * Options for receiving a specific message by ID.
355
+ */
356
+ interface ReceiveByIdOptions {
357
+ /** The specific message ID to consume */
358
+ messageId: string;
164
359
  /**
165
- * Serializer/deserializer for the payload
166
- * @default JsonTransport instance
360
+ * Message lock duration in seconds.
361
+ * @default 300 (5 minutes)
362
+ * @minimum 30
363
+ * @maximum 3600 (1 hour)
167
364
  */
168
- transport?: Transport<T>;
365
+ visibilityTimeoutSeconds?: number;
169
366
  /**
170
- * Time in seconds that messages will be invisible to other consumers
171
- * @default 30
367
+ * Called when the handler throws. Return `{ afterSeconds: N }` to reschedule
368
+ * the message for redelivery after N seconds, or return `undefined`
369
+ * to let the error propagate normally.
370
+ */
371
+ retry?: RetryHandler;
372
+ }
373
+ /**
374
+ * Options for receiving messages from the queue with an optional batch limit.
375
+ */
376
+ interface ReceiveBatchOptions {
377
+ /**
378
+ * Maximum number of messages to retrieve in a single request.
379
+ * @default 1
380
+ * @minimum 1
381
+ * @maximum 10
382
+ */
383
+ limit?: number;
384
+ /**
385
+ * Message lock duration in seconds.
386
+ * @default 300 (5 minutes)
387
+ * @minimum 30
388
+ * @maximum 3600 (1 hour)
172
389
  */
173
390
  visibilityTimeoutSeconds?: number;
174
391
  /**
175
- * How often to refresh the visibility timeout during processing (in seconds)
176
- * @default 10
392
+ * Called when the handler throws. Return `{ afterSeconds: N }` to reschedule
393
+ * the message for redelivery after N seconds, or return `undefined`
394
+ * to let the error propagate normally.
177
395
  */
178
- refreshInterval?: number;
396
+ retry?: RetryHandler;
179
397
  }
180
398
  /**
181
- * Error thrown when a message is not found (404)
399
+ * Options for the receive method. Either receive by message ID or receive
400
+ * from the queue with an optional limit. These options are mutually exclusive.
401
+ */
402
+ type ReceiveOptions = ReceiveByIdOptions | ReceiveBatchOptions;
403
+ /**
404
+ * Error thrown when a message does not exist or has expired.
182
405
  */
183
406
  declare class MessageNotFoundError extends Error {
184
407
  constructor(messageId: string);
185
408
  }
186
409
  /**
187
- * Error thrown when a message is not available for processing (409)
188
- * This can happen when the message is in the wrong state, already claimed, etc.
410
+ * Error thrown when a message exists but cannot be processed.
411
+ * This can happen when the message is in the wrong state or already claimed by another consumer.
189
412
  */
190
413
  declare class MessageNotAvailableError extends Error {
191
414
  constructor(messageId: string, reason?: string);
@@ -197,166 +420,252 @@ declare class MessageCorruptedError extends Error {
197
420
  constructor(messageId: string, reason: string);
198
421
  }
199
422
  /**
200
- * Error thrown when there are no messages available in the queue (204)
423
+ * Error thrown when there are no messages available in the queue.
201
424
  */
202
425
  declare class QueueEmptyError extends Error {
203
426
  constructor(queueName: string, consumerGroup: string);
204
427
  }
205
428
  /**
206
- * Error thrown when a message is temporarily locked (423)
429
+ * Error thrown when a message is temporarily locked by another consumer.
430
+ * The message is currently being processed and cannot be claimed.
207
431
  */
208
432
  declare class MessageLockedError extends Error {
433
+ /** Suggested retry delay in seconds, if provided by the server. */
209
434
  readonly retryAfter?: number;
210
435
  constructor(messageId: string, retryAfter?: number);
211
436
  }
212
437
  /**
213
- * Error thrown when authentication fails (401)
438
+ * Error thrown when authentication fails.
439
+ * This typically means the token is missing, invalid, or expired.
214
440
  */
215
441
  declare class UnauthorizedError extends Error {
216
442
  constructor(message?: string);
217
443
  }
218
444
  /**
219
- * Error thrown when access is forbidden (403)
445
+ * Error thrown when access is forbidden.
446
+ * This typically means the queue belongs to a different environment or project.
220
447
  */
221
448
  declare class ForbiddenError extends Error {
222
449
  constructor(message?: string);
223
450
  }
224
451
  /**
225
- * Error thrown for bad requests (400)
452
+ * Error thrown when request parameters are invalid.
226
453
  */
227
454
  declare class BadRequestError extends Error {
228
455
  constructor(message: string);
229
456
  }
230
457
  /**
231
- * Error thrown for internal server errors (500)
458
+ * Error thrown when the server encounters an unexpected error.
232
459
  */
233
460
  declare class InternalServerError extends Error {
234
461
  constructor(message?: string);
235
462
  }
236
463
  /**
237
- * Error thrown when batch limit parameter is invalid
464
+ * Error thrown when the batch limit parameter is outside the valid range.
465
+ * The limit must be between 1 and 10 (inclusive).
238
466
  */
239
467
  declare class InvalidLimitError extends Error {
240
468
  constructor(limit: number, min?: number, max?: number);
241
469
  }
242
-
243
470
  /**
244
- * Options for the consume method
471
+ * Error thrown when attempting to process a message that has already been successfully processed.
245
472
  */
246
- interface ConsumeOptions {
247
- /** The specific message ID to consume (if not provided, consumes next available message) */
248
- messageId?: string;
249
- /** Whether to skip downloading the payload (only allowed when messageId is provided) */
250
- skipPayload?: boolean;
473
+ declare class MessageAlreadyProcessedError extends Error {
474
+ constructor(messageId: string);
251
475
  }
252
-
253
476
  /**
254
- * Options for the send function
477
+ * Error thrown when a duplicate idempotency key is detected.
478
+ * The message was already sent within the deduplication window.
255
479
  */
256
- interface SendOptions<T = unknown> extends PublishOptions {
257
- /**
258
- * Serializer/deserializer for the payload
259
- * @default JsonTransport instance
260
- */
261
- transport?: Transport<T>;
480
+ declare class DuplicateMessageError extends Error {
481
+ readonly idempotencyKey?: string;
482
+ constructor(message: string, idempotencyKey?: string);
262
483
  }
263
484
  /**
264
- * Send a message to a topic (shorthand for topic creation and publishing)
265
- * Uses the default QueueClient with automatic OIDC token detection
266
- * @param topicName Name of the topic to send to
267
- * @param payload The data to send
268
- * @param options Optional send options including transport and publish settings
269
- * @returns Promise with the message ID
270
- * @throws {BadRequestError} When request parameters are invalid
271
- * @throws {UnauthorizedError} When authentication fails
272
- * @throws {ForbiddenError} When access is denied (environment mismatch)
273
- * @throws {InternalServerError} When server encounters an error
274
- */
275
- declare function send<T = unknown>(topicName: string, payload: T, options?: SendOptions<T>): Promise<{
276
- messageId: string;
277
- }>;
485
+ * Error thrown when consumer discovery fails.
486
+ * This typically means the deployment could not be reached or is not configured correctly.
487
+ */
488
+ declare class ConsumerDiscoveryError extends Error {
489
+ readonly deploymentId?: string;
490
+ constructor(message: string, deploymentId?: string);
491
+ }
278
492
  /**
279
- * Options for the receive function
493
+ * Error thrown when the consumer registry is not configured for the project.
280
494
  */
281
- interface ReceiveOptions<T = unknown> extends ConsumerGroupOptions<T>, ConsumeOptions {
495
+ declare class ConsumerRegistryNotConfiguredError extends Error {
496
+ constructor(message?: string);
497
+ }
498
+
499
+ declare class QueueClient {
500
+ constructor(options: QueueClientOptions);
501
+ /**
502
+ * Send a message to a topic.
503
+ *
504
+ * This is an arrow function property so it can be destructured:
505
+ * ```typescript
506
+ * const { send } = new QueueClient({ region: process.env.QUEUE_REGION! });
507
+ * await send("my-topic", payload);
508
+ * ```
509
+ *
510
+ * @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
511
+ * @param payload - The data to send (serialized via the configured transport)
512
+ * @param options - Optional send options (idempotencyKey, retentionSeconds, delaySeconds, headers)
513
+ * @returns `{ messageId }` — `messageId` is `null` when the server accepted
514
+ * the message for deferred processing (no ID available yet)
515
+ */
516
+ send: <T = unknown>(topicName: string, payload: T, options?: SendOptions) => Promise<SendResult>;
517
+ /**
518
+ * Receive and process messages from a topic.
519
+ *
520
+ * Each message is automatically locked, kept alive via periodic visibility
521
+ * extensions during processing, and acknowledged upon successful handler completion.
522
+ * The handler is not called when the queue is empty — check `result.ok` instead.
523
+ *
524
+ * This is an arrow function property so it can be destructured:
525
+ * ```typescript
526
+ * const { receive } = new QueueClient({ region: process.env.QUEUE_REGION! });
527
+ * const result = await receive("my-topic", "my-group", handler);
528
+ * if (!result.ok) console.log(result.reason);
529
+ * ```
530
+ *
531
+ * @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
532
+ * @param consumerGroup - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
533
+ * @param handler - Function to process each message payload and metadata.
534
+ * Not called when the queue is empty.
535
+ * @param options - Optional receive options (visibilityTimeoutSeconds, limit, or messageId)
536
+ * @returns Discriminated result: `{ ok: true }` on success, `{ ok: false, reason }` otherwise
537
+ */
538
+ receive: <T = unknown>(topicName: string, consumerGroup: string, handler: MessageHandler<T>, options?: ReceiveOptions) => Promise<ReceiveResult>;
539
+ /**
540
+ * Create a Web API route handler for processing queue callback messages.
541
+ *
542
+ * Parses incoming `Request` as a CloudEvent and invokes the handler.
543
+ * For use on Vercel — Vercel invokes this route when messages are available.
544
+ *
545
+ * This is an arrow function property so it can be destructured:
546
+ * ```typescript
547
+ * const { handleCallback } = new QueueClient({ region: process.env.QUEUE_REGION! });
548
+ * export const POST = handleCallback(handler);
549
+ * ```
550
+ *
551
+ * @param handler - Function to process the message payload and metadata
552
+ * @param options - Optional configuration
553
+ * @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
554
+ * @param options.retry - Called when the handler throws. Return `{ afterSeconds: N }` to
555
+ * reschedule the message for redelivery after N seconds.
556
+ * @returns A `(request: Request) => Promise<Response>` route handler
557
+ */
558
+ handleCallback: <T = unknown>(handler: MessageHandler<T>, options?: {
559
+ visibilityTimeoutSeconds?: number;
560
+ retry?: RetryHandler;
561
+ }) => ((request: Request) => Promise<Response>);
562
+ /**
563
+ * Create a Connect-style route handler for processing queue callback messages.
564
+ * For use on Vercel — Vercel invokes this route when messages are available.
565
+ *
566
+ * For frameworks using the `(req, res)` middleware pattern where `req.body`
567
+ * is pre-parsed (Next.js Pages Router, etc.).
568
+ *
569
+ * This is an arrow function property so it can be destructured:
570
+ * ```typescript
571
+ * const { handleNodeCallback } = new QueueClient({ region: process.env.QUEUE_REGION! });
572
+ * app.post("/api/queue", handleNodeCallback(handler));
573
+ * ```
574
+ *
575
+ * @param handler - Function to process the message payload and metadata
576
+ * @param options - Optional configuration
577
+ * @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
578
+ * @param options.retry - Called when the handler throws. Return `{ afterSeconds: N }` to
579
+ * reschedule the message for redelivery after N seconds.
580
+ * @returns A `(req, res) => Promise<void>` route handler
581
+ */
582
+ handleNodeCallback: <T = unknown>(handler: MessageHandler<T>, options?: {
583
+ visibilityTimeoutSeconds?: number;
584
+ retry?: RetryHandler;
585
+ }) => ((req: {
586
+ method?: string;
587
+ headers: Record<string, string | string[] | undefined>;
588
+ body?: unknown;
589
+ }, res: {
590
+ status(code: number): {
591
+ json(data: unknown): void;
592
+ end(): void;
593
+ };
594
+ end(): void;
595
+ }) => Promise<void>);
282
596
  }
597
+
283
598
  /**
284
- * Receive a message from a topic (shorthand for topic and consumer group creation)
285
- * Uses the default QueueClient with automatic OIDC token detection
286
- * @param topicName Name of the topic to receive from
287
- * @param consumerGroup Name of the consumer group
288
- * @param handler Function to process the message
289
- * @returns Promise that resolves when the message is processed
290
- * @throws All the same errors as the underlying client methods
291
- */
292
- declare function receive<T = unknown>(topicName: string, consumerGroup: string, handler: MessageHandler<T>, options?: ReceiveOptions<T>): Promise<void>;
293
- /**
294
- * Receive a specific message by its ID with full payload
295
- * @param topicName Name of the topic to receive from
296
- * @param consumerGroup Name of the consumer group
297
- * @param handler Function to process the message
298
- * @param options Receive options with messageId specified
299
- * @returns Promise that resolves when the message is processed
300
- * @throws All the same errors as the underlying client methods
301
- */
302
- declare function receive<T = unknown>(topicName: string, consumerGroup: string, handler: MessageHandler<T>, options: ReceiveOptions<T> & {
303
- messageId: string;
304
- skipPayload?: false | undefined;
305
- }): Promise<void>;
306
- /**
307
- * Receive a specific message by its ID without downloading the payload (metadata only)
308
- * @param topicName Name of the topic to receive from
309
- * @param consumerGroup Name of the consumer group
310
- * @param handler Function to process the message metadata (payload will be void)
311
- * @param options Receive options with messageId and skipPayload specified
312
- * @returns Promise that resolves when the message is processed
313
- * @throws All the same errors as the underlying client methods
314
- */
315
- declare function receive<T = unknown>(topicName: string, consumerGroup: string, handler: MessageHandler<void>, options: ReceiveOptions<T> & {
316
- messageId: string;
317
- skipPayload: true;
318
- }): Promise<void>;
599
+ * Core queue callback utilities for handling incoming webhook payloads
600
+ * from Vercel triggers using the CloudEvent specification.
601
+ *
602
+ * This module provides the framework-agnostic core. For framework-specific
603
+ * wrappers, see `@vercel/queue/web` and `@vercel/queue/nextjs/pages`.
604
+ */
319
605
 
606
+ declare const CLOUD_EVENT_TYPE_V1BETA = "com.vercel.queue.v1beta";
607
+ declare const CLOUD_EVENT_TYPE_V2BETA = "com.vercel.queue.v2beta";
608
+ /**
609
+ * Routing-only callback: the SDK must fetch the message by ID.
610
+ * Produced by v1beta structured mode and v2beta large-body mode.
611
+ */
612
+ type ParsedCallbackV1 = {
613
+ queueName: string;
614
+ consumerGroup: string;
615
+ messageId: string;
616
+ region?: string;
617
+ };
320
618
  /**
321
- * Configuration object with handlers for different topics and consumer groups
619
+ * Full-message callback: payload and receipt handle are inlined,
620
+ * so the SDK can process directly without an extra fetch.
621
+ * Produced by v2beta small-body mode.
322
622
  */
323
- type CallbackHandlers = {
324
- [topicName: string]: {
325
- [consumerGroup: string]: MessageHandler;
326
- };
623
+ type ParsedCallbackV2 = {
624
+ queueName: string;
625
+ consumerGroup: string;
626
+ messageId: string;
627
+ region?: string;
628
+ receiptHandle: string;
629
+ deliveryCount?: number;
630
+ createdAt?: string;
631
+ expiresAt?: string;
632
+ contentType?: string;
633
+ visibilityDeadline?: string;
634
+ rawBody?: ReadableStream<Uint8Array>;
635
+ parsedPayload?: unknown;
327
636
  };
637
+ type ParsedCallbackRequest = ParsedCallbackV1 | ParsedCallbackV2;
328
638
  /**
329
- * Simplified queue callback handler for Next.js route handlers
639
+ * Parse a callback from a pre-parsed body and headers.
330
640
  *
331
- * Automatically extracts queue information from CloudEvent format
332
- * and routes to the appropriate handler based on topic and consumer group.
641
+ * For frameworks like Next.js Pages Router where the body has already been
642
+ * parsed, use this instead of {@link parseCallback}.
333
643
  *
334
- * @param handlers Object with topic-specific handlers organized by consumer groups
335
- * @returns A Next.js route handler function
644
+ * Detects the CloudEvent version from the `ce-type` header:
645
+ * - `com.vercel.queue.v2beta`: binary content mode (metadata in headers,
646
+ * payload in body). For small messages, the body is attached as `parsedPayload`.
647
+ * - Otherwise: structured content mode (v1beta, entire CloudEvent in JSON body)
336
648
  *
337
- * @example
338
- * ```typescript
339
- * // Single topic with multiple consumer groups
340
- * export const POST = handleCallback({
341
- * "image-processing": {
342
- * "compress": (message, metadata) => console.log("Compressing image", message),
343
- * "resize": (message, metadata) => console.log("Resizing image", message),
344
- * }
345
- * });
649
+ * @param body - The framework-parsed request body. Type depends on Content-Type
650
+ * and framework configuration:
651
+ * - v1beta: parsed CloudEvent JSON object
652
+ * - v2beta: the message payload as parsed by the framework (object, Buffer, or string)
653
+ * @param headers - HTTP headers
654
+ * @returns Parsed callback request with routing metadata and optional payload
655
+ */
656
+ declare function parseRawCallback(body: unknown, headers: Record<string, string | string[] | undefined>): ParsedCallbackRequest;
657
+ /**
658
+ * Parse and validate a CloudEvent callback from a Web API `Request` object.
346
659
  *
347
- * // Multiple topics with consumer groups
348
- * export const POST = handleCallback({
349
- * "user-events": {
350
- * "welcome": (user, metadata) => console.log("Welcoming user", user),
351
- * "analytics": (user, metadata) => console.log("Tracking user", user),
352
- * },
353
- * "order-events": {
354
- * "fulfillment": (order, metadata) => console.log("Fulfilling order", order),
355
- * "notifications": (order, metadata) => console.log("Notifying order", order),
356
- * }
357
- * });
358
- * ```
660
+ * Detects the CloudEvent version from the `ce-type` header:
661
+ * - `com.vercel.queue.v2beta`: binary content mode (metadata in headers,
662
+ * payload in body). For v2beta, the body is attached as `rawBody` (a
663
+ * ReadableStream) rather than being parsed.
664
+ * - Otherwise: structured content mode (v1beta, entire CloudEvent in JSON body)
665
+ *
666
+ * For frameworks that pre-parse the body (e.g. Next.js Pages Router),
667
+ * use {@link parseRawCallback} instead.
359
668
  */
360
- declare function handleCallback(handlers: CallbackHandlers): (request: Request) => Promise<Response>;
669
+ declare function parseCallback(request: Request): Promise<ParsedCallbackRequest>;
361
670
 
362
- export { BadRequestError, BufferTransport, ForbiddenError, InternalServerError, InvalidLimitError, JsonTransport, type Message, MessageCorruptedError, type MessageHandler, type MessageHandlerResult, MessageLockedError, type MessageMetadata, MessageNotAvailableError, MessageNotFoundError, type MessageTimeoutResult, type PublishOptions, QueueEmptyError, type ReceiveOptions, type SendMessageOptions, type SendMessageResponse, type SendOptions, StreamTransport, type Transport, UnauthorizedError, handleCallback, receive, send };
671
+ export { BadRequestError, type BaseUrlResolver, BufferTransport, CLOUD_EVENT_TYPE_V1BETA, CLOUD_EVENT_TYPE_V2BETA, ConsumerDiscoveryError, ConsumerRegistryNotConfiguredError, DuplicateMessageError, ForbiddenError, InternalServerError, InvalidLimitError, JsonTransport, type Message, MessageAlreadyProcessedError, MessageCorruptedError, type MessageHandler, MessageLockedError, type MessageMetadata, MessageNotAvailableError, MessageNotFoundError, type ParsedCallbackRequest, type ParsedCallbackV1, type ParsedCallbackV2, QueueClient, type QueueClientOptions, QueueEmptyError, type ReceiveBatchOptions, type ReceiveByIdOptions, type ReceiveOptions, type ReceiveResult, type RetryDirective, type RetryHandler, type SendOptions, type SendResult, StreamTransport, type Transport, UnauthorizedError, type VercelRegion, parseCallback, parseRawCallback };