@vercel/queue 0.0.0-alpha.34 → 0.0.0-alpha.36
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/README.md +284 -34
- package/dist/index.d.mts +208 -54
- package/dist/index.d.ts +208 -54
- package/dist/index.js +173 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -38
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs-pages.d.mts +1 -1
- package/dist/nextjs-pages.d.ts +1 -1
- package/dist/nextjs-pages.js +145 -32
- package/dist/nextjs-pages.js.map +1 -1
- package/dist/nextjs-pages.mjs +145 -32
- package/dist/nextjs-pages.mjs.map +1 -1
- package/dist/types-C7IKe67P.d.mts +509 -0
- package/dist/types-C7IKe67P.d.ts +509 -0
- package/package.json +1 -1
- package/dist/types-BHtRP_i_.d.mts +0 -411
- package/dist/types-BHtRP_i_.d.ts +0 -411
package/README.md
CHANGED
|
@@ -81,6 +81,7 @@ await send(
|
|
|
81
81
|
{
|
|
82
82
|
idempotencyKey: "unique-key", // Optional: prevent duplicate messages
|
|
83
83
|
retentionSeconds: 3600, // Optional: override retention time (defaults to 24 hours)
|
|
84
|
+
delaySeconds: 60, // Optional: delay message delivery by N seconds
|
|
84
85
|
},
|
|
85
86
|
);
|
|
86
87
|
```
|
|
@@ -120,7 +121,7 @@ export const POST = handleCallback({
|
|
|
120
121
|
// Single topic with one consumer
|
|
121
122
|
"my-topic": {
|
|
122
123
|
"my-consumer": async (message, metadata) => {
|
|
123
|
-
// metadata includes: { messageId, deliveryCount, createdAt }
|
|
124
|
+
// metadata includes: { messageId, deliveryCount, createdAt, topicName, consumerGroup }
|
|
124
125
|
console.log("Processing message:", message);
|
|
125
126
|
|
|
126
127
|
// If this throws an error, the message will be automatically retried
|
|
@@ -248,6 +249,47 @@ Configure which topics and consumers your API route handles.
|
|
|
248
249
|
|
|
249
250
|
## Advanced Features
|
|
250
251
|
|
|
252
|
+
### Client Class
|
|
253
|
+
|
|
254
|
+
For custom configuration (tokens, headers, etc.), use the `Client` class:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { Client } from "@vercel/queue";
|
|
258
|
+
|
|
259
|
+
const client = new Client({
|
|
260
|
+
token: "my-token", // Optional: custom auth token
|
|
261
|
+
headers: { "X-Custom": "header" }, // Optional: custom headers
|
|
262
|
+
pinToDeployment: false, // Optional: disable deployment pinning (default: true)
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Send a message
|
|
266
|
+
await client.send("my-topic", { hello: "world" });
|
|
267
|
+
|
|
268
|
+
// Handle callbacks using the same client
|
|
269
|
+
export const POST = client.handleCallback({
|
|
270
|
+
"my-topic": {
|
|
271
|
+
"my-group": async (msg, meta) => console.log(msg),
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Parsing Callback Requests
|
|
277
|
+
|
|
278
|
+
For custom webhook handling, use `parseCallback` to extract queue information from CloudEvent requests:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { parseCallback } from "@vercel/queue";
|
|
282
|
+
|
|
283
|
+
export async function POST(request: Request) {
|
|
284
|
+
const { queueName, consumerGroup, messageId } = await parseCallback(request);
|
|
285
|
+
|
|
286
|
+
// Use the parsed information for custom processing...
|
|
287
|
+
await myWorkflow.handleWebhook(queueName, consumerGroup, messageId);
|
|
288
|
+
|
|
289
|
+
return Response.json({ status: "success" });
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
251
293
|
### Serialization (Transport) System
|
|
252
294
|
|
|
253
295
|
The queue client supports customizable serialization through the `Transport` interface:
|
|
@@ -272,36 +314,51 @@ await send(
|
|
|
272
314
|
{ data: "example" },
|
|
273
315
|
{ transport: new JsonTransport() },
|
|
274
316
|
);
|
|
317
|
+
|
|
318
|
+
// JsonTransport with custom serialization
|
|
319
|
+
const transport = new JsonTransport({
|
|
320
|
+
replacer: (key, value) => (key === "password" ? undefined : value),
|
|
321
|
+
reviver: (key, value) => (key === "date" ? new Date(value) : value),
|
|
322
|
+
});
|
|
323
|
+
await send("json-topic", { data: "example" }, { transport });
|
|
275
324
|
```
|
|
276
325
|
|
|
277
326
|
### Transport Selection Guide
|
|
278
327
|
|
|
279
|
-
| Use Case
|
|
280
|
-
|
|
|
281
|
-
| Small JSON objects
|
|
282
|
-
| Binary
|
|
283
|
-
| Large
|
|
284
|
-
| Real-time streams
|
|
328
|
+
| Use Case | Recommended Transport | Memory Usage | Performance |
|
|
329
|
+
| ------------------ | --------------------- | ------------ | ----------- |
|
|
330
|
+
| Small JSON objects | JsonTransport | Low | High |
|
|
331
|
+
| Binary data | BufferTransport | Medium | High |
|
|
332
|
+
| Large payloads | StreamTransport | Very Low | Medium |
|
|
333
|
+
| Real-time streams | StreamTransport | Very Low | High |
|
|
285
334
|
|
|
286
335
|
## Error Handling
|
|
287
336
|
|
|
288
337
|
The queue client provides specific error types:
|
|
289
338
|
|
|
290
|
-
- **`QueueEmptyError`**: No messages available
|
|
291
|
-
- **`MessageLockedError`**: Message
|
|
292
|
-
- **`MessageNotFoundError`**: Message doesn't exist
|
|
293
|
-
- **`MessageNotAvailableError`**: Message exists but
|
|
294
|
-
- **`
|
|
295
|
-
- **`
|
|
296
|
-
- **`
|
|
297
|
-
- **`
|
|
298
|
-
- **`
|
|
339
|
+
- **`QueueEmptyError`**: No messages available in the queue
|
|
340
|
+
- **`MessageLockedError`**: Message is being processed by another consumer
|
|
341
|
+
- **`MessageNotFoundError`**: Message doesn't exist or has expired
|
|
342
|
+
- **`MessageNotAvailableError`**: Message exists but cannot be claimed
|
|
343
|
+
- **`MessageAlreadyProcessedError`**: Message was already successfully processed
|
|
344
|
+
- **`MessageCorruptedError`**: Message data could not be parsed
|
|
345
|
+
- **`BadRequestError`**: Invalid request parameters
|
|
346
|
+
- **`UnauthorizedError`**: Authentication failed (invalid or missing token)
|
|
347
|
+
- **`ForbiddenError`**: Access denied (wrong environment or project)
|
|
348
|
+
- **`DuplicateMessageError`**: Idempotency key was already used
|
|
349
|
+
- **`ConcurrencyLimitError`**: Too many in-flight messages
|
|
350
|
+
- **`ConsumerDiscoveryError`**: Could not reach the consumer deployment
|
|
351
|
+
- **`ConsumerRegistryNotConfiguredError`**: Project not configured for queues
|
|
352
|
+
- **`InternalServerError`**: Unexpected server error
|
|
353
|
+
- **`InvalidLimitError`**: Batch limit outside valid range (1-10)
|
|
299
354
|
|
|
300
355
|
Example error handling:
|
|
301
356
|
|
|
302
357
|
```typescript
|
|
303
358
|
import {
|
|
304
359
|
BadRequestError,
|
|
360
|
+
ConcurrencyLimitError,
|
|
361
|
+
DuplicateMessageError,
|
|
305
362
|
ForbiddenError,
|
|
306
363
|
InternalServerError,
|
|
307
364
|
UnauthorizedError,
|
|
@@ -316,52 +373,131 @@ try {
|
|
|
316
373
|
console.log("Environment mismatch - check configuration");
|
|
317
374
|
} else if (error instanceof BadRequestError) {
|
|
318
375
|
console.log("Invalid parameters:", error.message);
|
|
376
|
+
} else if (error instanceof DuplicateMessageError) {
|
|
377
|
+
console.log("Duplicate message:", error.idempotencyKey);
|
|
378
|
+
} else if (error instanceof ConcurrencyLimitError) {
|
|
379
|
+
console.log(
|
|
380
|
+
"Rate limited:",
|
|
381
|
+
error.currentInflight,
|
|
382
|
+
"/",
|
|
383
|
+
error.maxConcurrency,
|
|
384
|
+
);
|
|
319
385
|
} else if (error instanceof InternalServerError) {
|
|
320
386
|
console.log("Server error - retry with backoff");
|
|
321
387
|
}
|
|
322
388
|
}
|
|
323
389
|
```
|
|
324
390
|
|
|
391
|
+
## Environment Variables
|
|
392
|
+
|
|
393
|
+
The following environment variables can be used to configure the queue client:
|
|
394
|
+
|
|
395
|
+
| Variable | Description | Default |
|
|
396
|
+
| ------------------------ | ------------------------------------ | -------------------------- |
|
|
397
|
+
| `VERCEL_QUEUE_BASE_URL` | Override the queue service URL | `https://vercel-queue.com` |
|
|
398
|
+
| `VERCEL_QUEUE_BASE_PATH` | Override the API base path | `/api/v3/topic` |
|
|
399
|
+
| `VERCEL_QUEUE_DEBUG` | Enable debug logging (`1` or `true`) | - |
|
|
400
|
+
| `VERCEL_DEPLOYMENT_ID` | Deployment ID (auto-set by Vercel) | - |
|
|
401
|
+
|
|
325
402
|
## Advanced Usage
|
|
326
403
|
|
|
327
404
|
### Direct Message Processing
|
|
328
405
|
|
|
329
|
-
> **Note**: The `receive` function is
|
|
406
|
+
> **Note**: The `receive` function is for advanced use cases where you need direct message processing control outside of Vercel's automatic triggering.
|
|
330
407
|
|
|
331
408
|
```typescript
|
|
409
|
+
import { receive } from "@vercel/queue";
|
|
410
|
+
|
|
332
411
|
// Process next available message
|
|
333
412
|
await receive<T>(topicName, consumerGroup, handler);
|
|
334
413
|
|
|
335
414
|
// Process specific message by ID
|
|
336
415
|
await receive<T>(topicName, consumerGroup, handler, {
|
|
337
|
-
messageId: "message-id"
|
|
416
|
+
messageId: "message-id",
|
|
338
417
|
});
|
|
339
418
|
|
|
340
419
|
// Process message with options
|
|
341
420
|
await receive<T>(topicName, consumerGroup, handler, {
|
|
342
|
-
messageId
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
refreshInterval?: number; // Refresh interval for long-running operations
|
|
421
|
+
messageId: "message-id", // Optional: process specific message by ID
|
|
422
|
+
transport: new JsonTransport(), // Optional: custom transport (defaults to JsonTransport)
|
|
423
|
+
visibilityTimeoutSeconds: 30, // Optional: message visibility timeout
|
|
424
|
+
visibilityRefreshInterval: 10, // Optional: how often to refresh the lock
|
|
347
425
|
});
|
|
348
426
|
|
|
349
427
|
// Handler function signature
|
|
350
428
|
type MessageHandler<T = unknown> = (
|
|
351
429
|
message: T,
|
|
352
|
-
metadata: MessageMetadata
|
|
430
|
+
metadata: MessageMetadata,
|
|
353
431
|
) => Promise<void> | void;
|
|
432
|
+
|
|
433
|
+
// MessageMetadata type
|
|
434
|
+
interface MessageMetadata {
|
|
435
|
+
messageId: string;
|
|
436
|
+
deliveryCount: number;
|
|
437
|
+
createdAt: Date;
|
|
438
|
+
topicName: string;
|
|
439
|
+
consumerGroup: string;
|
|
440
|
+
}
|
|
354
441
|
```
|
|
355
442
|
|
|
356
|
-
## Limits
|
|
443
|
+
## Service Limits & Constraints
|
|
444
|
+
|
|
445
|
+
### Throughput & Storage
|
|
446
|
+
|
|
447
|
+
| Limit | Value | Notes |
|
|
448
|
+
| --------------------------- | --------------------- | ----------------------------------- |
|
|
449
|
+
| Message throughput | 10,000s msg/sec/topic | Scales horizontally |
|
|
450
|
+
| Payload size | 1 GB | Smaller messages have lower latency |
|
|
451
|
+
| Number of topics | Unlimited | No hard limit |
|
|
452
|
+
| Consumer groups per message | ~4,000 | Per-message limit |
|
|
453
|
+
| Messages per queue | Unlimited | No hard limit |
|
|
454
|
+
|
|
455
|
+
### Parameter Constraints
|
|
357
456
|
|
|
358
|
-
|
|
359
|
-
- **Payload Size**: Maximum payload size is 4.5MB (this limit will be increased soon)
|
|
360
|
-
- **Number of Topics**: No limit on the number of topics you can create
|
|
457
|
+
#### Publishing Messages
|
|
361
458
|
|
|
362
|
-
|
|
459
|
+
| Parameter | Default | Min | Max | Notes |
|
|
460
|
+
| ------------------ | ------------ | --- | ----------- | ----------------------------------- |
|
|
461
|
+
| `retentionSeconds` | 86,400 (24h) | 60 | 86,400 | Message TTL |
|
|
462
|
+
| `delaySeconds` | 0 | 0 | ≤ retention | Cannot exceed retention |
|
|
463
|
+
| `idempotencyKey` | — | — | — | Dedup window: `min(retention, 24h)` |
|
|
363
464
|
|
|
364
|
-
|
|
465
|
+
#### Receiving Messages
|
|
466
|
+
|
|
467
|
+
| Parameter | Default | Min | Max | Notes |
|
|
468
|
+
| -------------------------- | --------- | --- | ------ | --------------------------- |
|
|
469
|
+
| `visibilityTimeoutSeconds` | 30 | 0 | 3,600 | 0 = immediate re-visibility |
|
|
470
|
+
| `limit` | 1 | 1 | 10 | Messages per request |
|
|
471
|
+
| `maxConcurrency` | unlimited | 1 | 10,000 | In-flight message limit |
|
|
472
|
+
|
|
473
|
+
#### Visibility Extension
|
|
474
|
+
|
|
475
|
+
| Constraint | Value |
|
|
476
|
+
| -------------------------- | ---------------------------------- |
|
|
477
|
+
| `visibilityTimeoutSeconds` | 0 - 3,600 seconds |
|
|
478
|
+
| Cannot extend beyond | Message's original expiration time |
|
|
479
|
+
| Receipt handle | Must match the receive operation |
|
|
480
|
+
|
|
481
|
+
### Identifier Formats
|
|
482
|
+
|
|
483
|
+
| Identifier | Pattern | Example |
|
|
484
|
+
| ---------------- | ---------------- | -------------------------------- |
|
|
485
|
+
| Topic/Queue name | `[A-Za-z0-9_-]+` | `my-queue`, `task_queue_v2` |
|
|
486
|
+
| Consumer group | `[A-Za-z0-9_-]+` | `worker-1`, `analytics_consumer` |
|
|
487
|
+
| Message ID | Opaque string | `0-1`, `3-7K9mNpQrS` |
|
|
488
|
+
| Receipt handle | Opaque string | Used for delete/visibility ops |
|
|
489
|
+
|
|
490
|
+
### Content-Type Handling
|
|
491
|
+
|
|
492
|
+
| Scenario | Result |
|
|
493
|
+
| ------------------------------- | -------------------------- |
|
|
494
|
+
| Client provides `Content-Type` | Used as-is |
|
|
495
|
+
| No header, magic bytes detected | Auto-detected MIME type |
|
|
496
|
+
| No header, detection fails | `application/octet-stream` |
|
|
497
|
+
|
|
498
|
+
### Wildcard Topics
|
|
499
|
+
|
|
500
|
+
Topic patterns support wildcards for flexible routing:
|
|
365
501
|
|
|
366
502
|
```json
|
|
367
503
|
{
|
|
@@ -379,11 +515,125 @@ If you need more than 1,000 messages per second, you can create multiple topics
|
|
|
379
515
|
}
|
|
380
516
|
```
|
|
381
517
|
|
|
382
|
-
|
|
518
|
+
**Wildcard Rules:**
|
|
519
|
+
|
|
520
|
+
- `*` may only appear **once** in the pattern
|
|
521
|
+
- `*` must be at the **end** of the topic name
|
|
522
|
+
- Valid: `user-*`, `orders-*`
|
|
523
|
+
- Invalid: `*-events`, `user-*-data`
|
|
383
524
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
525
|
+
## API Reference
|
|
526
|
+
|
|
527
|
+
### Client Configuration
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import { Client } from "@vercel/queue";
|
|
531
|
+
|
|
532
|
+
const client = new Client({
|
|
533
|
+
// Base URL for the queue service
|
|
534
|
+
// Default: "https://vercel-queue.com"
|
|
535
|
+
// Env: VERCEL_QUEUE_BASE_URL
|
|
536
|
+
baseUrl: "https://vercel-queue.com",
|
|
537
|
+
|
|
538
|
+
// API path prefix
|
|
539
|
+
// Default: "/api/v3/topic"
|
|
540
|
+
// Env: VERCEL_QUEUE_BASE_PATH
|
|
541
|
+
basePath: "/api/v3/topic",
|
|
542
|
+
|
|
543
|
+
// Auth token (auto-fetched via OIDC if not provided)
|
|
544
|
+
token: "my-token",
|
|
545
|
+
|
|
546
|
+
// Custom headers for all requests
|
|
547
|
+
headers: { "X-Custom": "value" },
|
|
548
|
+
|
|
549
|
+
// Deployment ID for message routing
|
|
550
|
+
// Default: process.env.VERCEL_DEPLOYMENT_ID
|
|
551
|
+
deploymentId: "dpl_xxx",
|
|
552
|
+
|
|
553
|
+
// Pin messages to current deployment when publishing
|
|
554
|
+
// Default: true
|
|
555
|
+
pinToDeployment: true,
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Send Options
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
await send("my-topic", payload, {
|
|
563
|
+
// Deduplication key
|
|
564
|
+
// Dedup window: min(retentionSeconds, 24 hours)
|
|
565
|
+
idempotencyKey: "unique-key",
|
|
566
|
+
|
|
567
|
+
// Message TTL in seconds
|
|
568
|
+
// Default: 86400, Min: 60, Max: 86400
|
|
569
|
+
retentionSeconds: 3600,
|
|
570
|
+
|
|
571
|
+
// Delay before message becomes visible
|
|
572
|
+
// Default: 0, Min: 0, Max: retentionSeconds
|
|
573
|
+
delaySeconds: 60,
|
|
574
|
+
|
|
575
|
+
// Custom serializer (default: JsonTransport)
|
|
576
|
+
transport: new JsonTransport(),
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Receive Options
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
await receive("my-topic", "my-consumer", handler, {
|
|
584
|
+
// Specific message ID to consume (optional)
|
|
585
|
+
messageId: "0-1",
|
|
586
|
+
|
|
587
|
+
// Message lock duration in seconds
|
|
588
|
+
// Default: 30, Min: 0, Max: 3600
|
|
589
|
+
visibilityTimeoutSeconds: 60,
|
|
590
|
+
|
|
591
|
+
// How often to refresh the lock during processing
|
|
592
|
+
// Default: visibilityTimeoutSeconds / 3
|
|
593
|
+
visibilityRefreshInterval: 15,
|
|
594
|
+
|
|
595
|
+
// Custom deserializer (default: JsonTransport)
|
|
596
|
+
transport: new JsonTransport(),
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Receive Options (Advanced)
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
await receive("my-topic", "my-consumer", handler, {
|
|
604
|
+
// Payload deserializer
|
|
605
|
+
// Default: JsonTransport
|
|
606
|
+
transport: new JsonTransport(),
|
|
607
|
+
|
|
608
|
+
// Message lock duration
|
|
609
|
+
// Default: 30, Min: 0, Max: 3600
|
|
610
|
+
visibilityTimeoutSeconds: 60,
|
|
611
|
+
|
|
612
|
+
// How often to refresh the lock during processing
|
|
613
|
+
// Default: visibilityTimeoutSeconds / 3
|
|
614
|
+
visibilityRefreshInterval: 20,
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### handleCallback Options
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
export const POST = handleCallback(
|
|
622
|
+
{
|
|
623
|
+
"my-topic": {
|
|
624
|
+
"my-consumer": async (message, metadata) => {
|
|
625
|
+
await processMessage(message);
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
// Message lock duration for long-running handlers
|
|
631
|
+
// Default: 30, Min: 0, Max: 3600
|
|
632
|
+
// visibilityRefreshInterval defaults to visibilityTimeoutSeconds / 3
|
|
633
|
+
visibilityTimeoutSeconds: 300, // 5 minutes
|
|
634
|
+
},
|
|
635
|
+
);
|
|
636
|
+
```
|
|
387
637
|
|
|
388
638
|
## License
|
|
389
639
|
|