@vercel/queue 0.0.0-alpha.15 → 0.0.0-alpha.17

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 CHANGED
@@ -18,6 +18,11 @@ A TypeScript client library for interacting with the Vercel Queue Service API, d
18
18
  npm install @vercel/queue
19
19
  ```
20
20
 
21
+ The package includes:
22
+
23
+ - **Main Library**: Queue client and utilities for production and development
24
+ - **CLI Tool**: `npx @vercel/queue/local-discover` for local development handler discovery
25
+
21
26
  ## Quick Start
22
27
 
23
28
  For local development, you'll need to pull your Vercel environment variables:
@@ -30,15 +35,53 @@ npm i -g vercel
30
35
  vc env pull
31
36
  ```
32
37
 
38
+ ## Local Development
39
+
40
+ **Queues just work locally.** When you `send()` messages in development mode, they automatically trigger your handlers locally - no external queue infrastructure needed.
41
+
42
+ ### Next.js Lazy Loading
43
+
44
+ For Next.js API routes (or others that are lazy-loaded), run this simple command to ensure handlers are discovered:
45
+
46
+ ```bash
47
+ npx @vercel/queue/local-discover
48
+ ```
49
+
50
+ That's it! The script reads your `vercel.json`, finds your queue handlers, and triggers Next.js to load them.
51
+
52
+ ### Example Workflow
53
+
54
+ ```bash
55
+ # Start your dev server
56
+ npm run dev
57
+
58
+ # Discover handlers (only needed for Next.js lazy loading)
59
+ npx @vercel/queue/local-discover
60
+
61
+ # Send messages - they process locally automatically!
62
+ ```
63
+
64
+ ### CLI Options
65
+
66
+ ```bash
67
+ # Custom port
68
+ npx @vercel/queue/local-discover --port 3001
69
+
70
+ # Different config file
71
+ npx @vercel/queue/local-discover --config ./my-vercel.json
72
+
73
+ # Skip vercel.json, use defaults
74
+ npx @vercel/queue/local-discover --no-vercel-config
75
+ ```
76
+
33
77
  ### TypeScript Configuration
34
78
 
35
79
  Update your `tsconfig.json` to use `"bundler"` module resolution for proper package export resolution:
36
80
 
37
- ```json5
81
+ ```json
38
82
  {
39
83
  "compilerOptions": {
40
84
  "moduleResolution": "bundler"
41
- // ... other options
42
85
  }
43
86
  }
44
87
  ```
@@ -140,7 +183,7 @@ While you can split handlers into separate routes if needed (e.g., for code orga
140
183
 
141
184
  Configure which topics and consumers your API route handles:
142
185
 
143
- ```json5
186
+ ```json
144
187
  {
145
188
  "functions": {
146
189
  "app/api/queue/route.ts": {
@@ -149,9 +192,9 @@ Configure which topics and consumers your API route handles:
149
192
  "type": "queue/v1beta",
150
193
  "topic": "my-topic",
151
194
  "consumer": "my-consumer",
152
- "maxAttempts": 3, // Optional: Maximum number of delivery attempts (default: 3)
153
- "retryAfterSeconds": 60, // Optional: Delay between retries (default: 60)
154
- "initialDelaySeconds": 0 // Optional: Initial delay before first delivery (default: 0)
195
+ "maxAttempts": 3,
196
+ "retryAfterSeconds": 60,
197
+ "initialDelaySeconds": 0
155
198
  },
156
199
  {
157
200
  "type": "queue/v1beta",
@@ -162,8 +205,8 @@ Configure which topics and consumers your API route handles:
162
205
  "type": "queue/v1beta",
163
206
  "topic": "order-events",
164
207
  "consumer": "analytics",
165
- "maxAttempts": 5, // Retry up to 5 times
166
- "retryAfterSeconds": 300 // Wait 5 minutes between retries
208
+ "maxAttempts": 5,
209
+ "retryAfterSeconds": 300
167
210
  }
168
211
  ]
169
212
  }
@@ -305,7 +348,7 @@ interface MessageTimeoutResult {
305
348
 
306
349
  If you need more than 1,000 messages per second, you can create multiple topics (e.g., user-specific or shard-based topics) and handle them with a single consumer using wildcards in your `vercel.json`:
307
350
 
308
- ```json5
351
+ ```json
309
352
  {
310
353
  "functions": {
311
354
  "app/api/queue/route.ts": {
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Standalone CLI script to discover queue handlers in development mode
5
+ * This script is self-contained and doesn't import from the main package
6
+ */
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+
11
+ function showHelp() {
12
+ console.log(`
13
+ @vercel/queue local-discover - Discover queue handlers in development mode
14
+
15
+ USAGE:
16
+ npx @vercel/queue/local-discover [options]
17
+
18
+ OPTIONS:
19
+ --port <number> Port number for Next.js dev server (default: 3000)
20
+ --config <path> Path to vercel.json file (default: ./vercel.json)
21
+ --no-vercel-config Skip reading vercel.json, use default routes
22
+ --help, -h Show this help message
23
+
24
+ EXAMPLES:
25
+ npx @vercel/queue/local-discover
26
+ npx @vercel/queue/local-discover --port 3001
27
+ npx @vercel/queue/local-discover --config ./my-vercel.json
28
+ npx @vercel/queue/local-discover --no-vercel-config
29
+ `);
30
+ }
31
+
32
+ /**
33
+ * Read vercel.json and extract function endpoints with queue triggers
34
+ */
35
+ function readVercelConfig(configPath = "./vercel.json") {
36
+ try {
37
+ const fullPath = path.resolve(configPath);
38
+ const configContent = fs.readFileSync(fullPath, "utf-8");
39
+ const config = JSON.parse(configContent);
40
+
41
+ const routes = [];
42
+
43
+ // Extract routes from functions with queue triggers
44
+ if (config.functions) {
45
+ for (const [functionPath, functionConfig] of Object.entries(
46
+ config.functions,
47
+ )) {
48
+ if (
49
+ functionConfig &&
50
+ typeof functionConfig === "object" &&
51
+ "experimentalTriggers" in functionConfig
52
+ ) {
53
+ const triggers = functionConfig.experimentalTriggers;
54
+ if (Array.isArray(triggers)) {
55
+ // Check if any trigger is a queue trigger
56
+ const hasQueueTrigger = triggers.some(
57
+ (trigger) =>
58
+ trigger &&
59
+ typeof trigger === "object" &&
60
+ trigger.type === "queue/v1beta",
61
+ );
62
+
63
+ if (hasQueueTrigger) {
64
+ // Convert file path to API route
65
+ // app/api/vm/queue/route.ts -> /api/vm/queue
66
+ // pages/api/queue.ts -> /api/queue
67
+ let apiPath = functionPath;
68
+
69
+ if (apiPath.startsWith("app/api/")) {
70
+ apiPath = apiPath.replace("app/api/", "/api/");
71
+ } else if (apiPath.startsWith("pages/api/")) {
72
+ apiPath = apiPath.replace("pages/api/", "/api/");
73
+ }
74
+
75
+ // Remove file extensions and /route suffix
76
+ apiPath = apiPath.replace(
77
+ /\/(route|index)\.(ts|js|tsx|jsx)$/,
78
+ "",
79
+ );
80
+ apiPath = apiPath.replace(/\.(ts|js|tsx|jsx)$/, "");
81
+
82
+ routes.push(apiPath);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ return routes;
90
+ } catch (error) {
91
+ // vercel.json might not exist or be malformed
92
+ return [];
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Make OPTIONS requests to trigger Next.js lazy loading
98
+ */
99
+ async function discoverQueueHandlers(options = {}) {
100
+ if (process.env.NODE_ENV !== "development") {
101
+ console.log("[Dev Mode] Not in development mode, skipping discovery");
102
+ return;
103
+ }
104
+
105
+ const baseUrl = options.baseUrl || "http://localhost:3000";
106
+ const fromVercelConfig = options.fromVercelConfig ?? true;
107
+ const configPath = options.configPath || "./vercel.json";
108
+
109
+ let routes = options.routes || [];
110
+
111
+ // Auto-discover from vercel.json if enabled
112
+ if (fromVercelConfig && routes.length === 0) {
113
+ const configRoutes = readVercelConfig(configPath);
114
+ if (configRoutes.length > 0) {
115
+ routes = configRoutes;
116
+ console.log(
117
+ `[Dev Mode] Found ${configRoutes.length} queue endpoints in vercel.json`,
118
+ );
119
+ } else {
120
+ // Fallback to common routes
121
+ routes = ["/api/queue/callback", "/api/queue", "/api/queues/callback"];
122
+ console.log("[Dev Mode] No vercel.json found, using default routes");
123
+ }
124
+ }
125
+
126
+ console.log(
127
+ "[Dev Mode] Making OPTIONS requests to trigger module loading...",
128
+ );
129
+
130
+ for (const route of routes) {
131
+ try {
132
+ // Make an OPTIONS request to trigger module loading without processing
133
+ const response = await fetch(`${baseUrl}${route}`, {
134
+ method: "OPTIONS",
135
+ // Add a header to identify this as a discovery request
136
+ headers: { "x-queue-discovery": "true" },
137
+ });
138
+
139
+ // Any response means the route exists and module was loaded
140
+ console.log(`[Dev Mode] ✓ ${route} (${response.status})`);
141
+ } catch (error) {
142
+ // Ignore errors - route might not exist or server not running
143
+ console.log(`[Dev Mode] ✗ ${route} (server may not be running)`);
144
+ }
145
+ }
146
+
147
+ console.log("[Dev Mode] Module loading complete.");
148
+ }
149
+
150
+ async function main() {
151
+ const args = process.argv.slice(2);
152
+ const options = {};
153
+
154
+ // Parse simple command line arguments
155
+ for (let i = 0; i < args.length; i++) {
156
+ const arg = args[i];
157
+ if (arg === "--help" || arg === "-h") {
158
+ showHelp();
159
+ return;
160
+ } else if (arg === "--port" && args[i + 1]) {
161
+ options.baseUrl = `http://localhost:${args[i + 1]}`;
162
+ i++;
163
+ } else if (arg === "--config" && args[i + 1]) {
164
+ options.configPath = args[i + 1];
165
+ i++;
166
+ } else if (arg === "--no-vercel-config") {
167
+ options.fromVercelConfig = false;
168
+ }
169
+ }
170
+
171
+ try {
172
+ await discoverQueueHandlers(options);
173
+ } catch (error) {
174
+ console.error("Failed to discover queue handlers:", error.message);
175
+ process.exit(1);
176
+ }
177
+ }
178
+
179
+ main();