@vercel/node 5.6.24 → 5.7.0

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.
@@ -0,0 +1,259 @@
1
+ //
2
+ // Unified Lambda handler for bundled vanilla API routes.
3
+ // All bundleable lambdas share this exact file (same handler digest).
4
+ // It reads x-matched-path to determine which entrypoint to invoke.
5
+ //
6
+ // IMPORTANT: This file must remain entrypoint-agnostic. Do not embed
7
+ // any path-specific constants. The x-matched-path header is set by
8
+ // route rules injected by the builder.
9
+ //
10
+ // This runs at Lambda runtime where the handler is invoked with (req, res).
11
+ // User modules are in the Lambda's file tree alongside this file.
12
+ //
13
+ // Supports all handler shapes:
14
+ // 1. Function export: module.exports = (req, res) => { ... }
15
+ // 2. Web handlers: export function GET(request) { ... }
16
+ // 3. Fetch handler: export function fetch(request) { ... }
17
+ // 4. Server handler: http.createServer(...).listen()
18
+ //
19
+
20
+ const http = require('http');
21
+ const { existsSync } = require('fs');
22
+ const { resolve } = require('path');
23
+ const { pathToFileURL } = require('url');
24
+ const { Readable } = require('stream');
25
+
26
+ const HTTP_METHODS = [
27
+ 'GET',
28
+ 'HEAD',
29
+ 'OPTIONS',
30
+ 'POST',
31
+ 'PUT',
32
+ 'DELETE',
33
+ 'PATCH',
34
+ ];
35
+
36
+ const handlerCache = Object.create(null);
37
+
38
+ /**
39
+ * Resolve an extensionless entrypoint to an actual file path.
40
+ * Tries common JS extensions in order, returning the first match.
41
+ */
42
+ function resolveEntrypoint(name) {
43
+ const base = resolve('./' + name);
44
+ for (const ext of ['.js', '.cjs', '.mjs']) {
45
+ const p = base + ext;
46
+ if (existsSync(p)) return p;
47
+ }
48
+ // If base is a directory, look for index files inside it
49
+ if (existsSync(base)) {
50
+ for (const ext of ['.js', '.cjs', '.mjs']) {
51
+ const p = resolve(base, 'index' + ext);
52
+ if (existsSync(p)) return p;
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * Load a module via dynamic import(). Works for both CJS and ESM regardless
60
+ * of the file extension or the package.json "type" field.
61
+ */
62
+ async function loadModule(filePath) {
63
+ return import(pathToFileURL(filePath).href);
64
+ }
65
+
66
+ /**
67
+ * Unwrap nested default exports (common with TS/ESM compiled to CJS).
68
+ */
69
+ function unwrapDefaults(mod) {
70
+ for (let i = 0; i < 5; i++) {
71
+ if (mod && mod.default) mod = mod.default;
72
+ else break;
73
+ }
74
+ return mod;
75
+ }
76
+
77
+ /**
78
+ * Create a Node.js (req, res) handler from web handler exports (GET, POST, fetch, etc.).
79
+ * Uses Node.js 18+ built-in Web API globals (Request, Response).
80
+ */
81
+ function createWebHandler(listener) {
82
+ const methods = Object.create(null);
83
+
84
+ // If fetch is exported, it handles all methods
85
+ if (typeof listener.fetch === 'function') {
86
+ for (const m of HTTP_METHODS) {
87
+ methods[m] = listener.fetch;
88
+ }
89
+ }
90
+
91
+ // Named method exports override fetch
92
+ for (const m of HTTP_METHODS) {
93
+ if (typeof listener[m] === 'function') {
94
+ methods[m] = listener[m];
95
+ }
96
+ }
97
+
98
+ return async (req, res) => {
99
+ const method = req.method || 'GET';
100
+ const fn = methods[method];
101
+ if (!fn) {
102
+ res.statusCode = 405;
103
+ res.end('Method Not Allowed');
104
+ return;
105
+ }
106
+
107
+ // Build a Web API Request from the Node.js IncomingMessage
108
+ const proto = req.headers['x-forwarded-proto'] || 'https';
109
+ const host =
110
+ req.headers['x-forwarded-host'] || req.headers.host || 'localhost';
111
+ const url = new URL(req.url || '/', `${proto}://${host}`);
112
+
113
+ const init = { method, headers: req.headers, duplex: 'half' };
114
+ if (method !== 'GET' && method !== 'HEAD') {
115
+ init.body = Readable.toWeb(req);
116
+ }
117
+
118
+ const request = new Request(url, init);
119
+ let response;
120
+ try {
121
+ response = await fn(request);
122
+ } catch {
123
+ res.statusCode = 500;
124
+ res.end('Internal Server Error');
125
+ return;
126
+ }
127
+
128
+ // Write the Web API Response back to the Node.js ServerResponse
129
+ res.statusCode = response.status;
130
+ for (const [key, value] of response.headers) {
131
+ res.appendHeader(key, value);
132
+ }
133
+
134
+ if (response.body) {
135
+ for await (const chunk of response.body) {
136
+ res.write(chunk);
137
+ }
138
+ }
139
+ res.end();
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Compile a user module and return a (req, res) handler regardless of
145
+ * export shape. Mirrors the detection logic in serverless-handler.mts.
146
+ */
147
+ async function compileUserCode(filePath) {
148
+ let server = null;
149
+ let serverFound;
150
+
151
+ // Monkey-patch http.Server.prototype.listen to capture server instances
152
+ // created during module import (e.g. Express apps calling app.listen()).
153
+ const originalListen = http.Server.prototype.listen;
154
+ http.Server.prototype.listen = function () {
155
+ server = this;
156
+ http.Server.prototype.listen = originalListen;
157
+ if (serverFound) serverFound();
158
+ return this;
159
+ };
160
+
161
+ try {
162
+ let listener = await loadModule(filePath);
163
+ listener = unwrapDefaults(listener);
164
+
165
+ // 1. Web handlers (GET, POST, fetch, etc.)
166
+ const isWebHandler =
167
+ HTTP_METHODS.some(m => typeof listener[m] === 'function') ||
168
+ typeof listener.fetch === 'function';
169
+
170
+ if (isWebHandler) {
171
+ return createWebHandler(listener);
172
+ }
173
+
174
+ // 2. Function handler: (req, res) => { ... }
175
+ if (typeof listener === 'function') {
176
+ return listener;
177
+ }
178
+
179
+ // 3. Server handler: http.createServer(...).listen()
180
+ // Wait briefly for async server creation if not captured yet.
181
+ if (!server) {
182
+ await new Promise(r => {
183
+ serverFound = r;
184
+ setTimeout(r, 1000);
185
+ });
186
+ }
187
+
188
+ if (server) {
189
+ // Start the captured server on a random port and proxy requests to it.
190
+ await new Promise(r => server.listen(0, '127.0.0.1', r));
191
+ const { port } = server.address();
192
+
193
+ return (req, res) => {
194
+ const proxyReq = http.request(
195
+ {
196
+ hostname: '127.0.0.1',
197
+ port,
198
+ path: req.url,
199
+ method: req.method,
200
+ headers: {
201
+ ...req.headers,
202
+ host: req.headers['x-forwarded-host'] || req.headers.host,
203
+ },
204
+ },
205
+ proxyRes => {
206
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
207
+ proxyRes.pipe(res);
208
+ }
209
+ );
210
+ proxyReq.on('error', err => {
211
+ res.statusCode = 502;
212
+ res.end('Proxy error: ' + err.message);
213
+ });
214
+ req.pipe(proxyReq);
215
+ };
216
+ }
217
+
218
+ throw new Error(
219
+ "Can't detect handler export shape for " +
220
+ filePath +
221
+ '. Expected a function export, HTTP method exports (GET, POST, ...), or an http.Server.'
222
+ );
223
+ } finally {
224
+ http.Server.prototype.listen = originalListen;
225
+ }
226
+ }
227
+
228
+ module.exports = async (req, res) => {
229
+ const matchedPath = req.headers['x-matched-path'];
230
+ if (typeof matchedPath !== 'string' || !matchedPath) {
231
+ res.statusCode = 500;
232
+ res.end(
233
+ 'Missing x-matched-path header. The bundled handler requires route-level header injection.'
234
+ );
235
+ return;
236
+ }
237
+
238
+ // Convert matched path to entrypoint name.
239
+ // x-matched-path "/" maps to "index", "/api/hello" maps to "api/hello".
240
+ const entrypoint = matchedPath.replace(/^\//, '') || 'index';
241
+
242
+ if (!handlerCache[entrypoint]) {
243
+ const filePath = resolveEntrypoint(entrypoint);
244
+ if (!filePath) {
245
+ res.statusCode = 404;
246
+ res.end('No handler found for ' + entrypoint);
247
+ return;
248
+ }
249
+ // Store the Promise immediately so concurrent requests to the same
250
+ // entrypoint share one compileUserCode() call instead of racing.
251
+ handlerCache[entrypoint] = compileUserCode(filePath).catch(err => {
252
+ delete handlerCache[entrypoint]; // Allow retry on next request
253
+ throw err;
254
+ });
255
+ }
256
+
257
+ const handler = await handlerCache[entrypoint];
258
+ return handler(req, res);
259
+ };
package/dist/index.js CHANGED
@@ -69961,6 +69961,7 @@ var require_dist = __commonJS({
69961
69961
  // src/index.ts
69962
69962
  var src_exports = {};
69963
69963
  __export(src_exports, {
69964
+ _resetBundlingRoutesEmitted: () => _resetBundlingRoutesEmitted,
69964
69965
  build: () => build,
69965
69966
  prepareCache: () => prepareCache,
69966
69967
  shouldServe: () => import_build_utils8.shouldServe,
@@ -70689,6 +70690,10 @@ function getAWSLambdaHandler(entrypoint, config) {
70689
70690
  }
70690
70691
  return "";
70691
70692
  }
70693
+ var bundlingRoutesEmitted = false;
70694
+ function _resetBundlingRoutesEmitted() {
70695
+ bundlingRoutesEmitted = false;
70696
+ }
70692
70697
  var build = async ({
70693
70698
  files,
70694
70699
  entrypoint,
@@ -70836,9 +70841,50 @@ var build = async ({
70836
70841
  } else {
70837
70842
  const shouldAddHelpers = !(config.helpers === false || process.env.NODEJS_HELPERS === "0");
70838
70843
  const supportsResponseStreaming = (staticConfig?.supportsResponseStreaming ?? staticConfig?.experimentalResponseStreaming) === true ? true : void 0;
70844
+ const enableBundling = process.env.VERCEL_API_FUNCTION_BUNDLING === "1" && config.zeroConfig === true && !isMiddleware && !isEdgeFunction;
70845
+ if (enableBundling) {
70846
+ const bundledHandlerName = "___vc_bundled_api_handler.js";
70847
+ preparedFiles[bundledHandlerName] = new import_build_utils3.FileFsRef({
70848
+ fsPath: (0, import_path3.join)((0, import_path3.dirname)(__filename), "bundling-handler.js")
70849
+ });
70850
+ handler = bundledHandlerName;
70851
+ if (!bundlingRoutesEmitted) {
70852
+ bundlingRoutesEmitted = true;
70853
+ routes = [
70854
+ { handle: "hit" },
70855
+ {
70856
+ src: "/index(?:/)?",
70857
+ transforms: [
70858
+ {
70859
+ type: "request.headers",
70860
+ op: "set",
70861
+ target: { key: "x-matched-path" },
70862
+ args: "/"
70863
+ }
70864
+ ],
70865
+ continue: true,
70866
+ important: true
70867
+ },
70868
+ {
70869
+ src: "/((?!index$).*?)(?:/)?",
70870
+ transforms: [
70871
+ {
70872
+ type: "request.headers",
70873
+ op: "set",
70874
+ target: { key: "x-matched-path" },
70875
+ args: "/$1"
70876
+ }
70877
+ ],
70878
+ continue: true,
70879
+ important: true
70880
+ }
70881
+ ];
70882
+ }
70883
+ }
70839
70884
  output = new import_build_utils3.NodejsLambda({
70840
70885
  files: preparedFiles,
70841
70886
  handler,
70887
+ experimentalAllowBundling: enableBundling || void 0,
70842
70888
  architecture: staticConfig?.architecture,
70843
70889
  runtime: nodeVersion.runtime,
70844
70890
  useWebApi: isMiddleware ? true : useWebApi,
@@ -71270,6 +71316,7 @@ var import_build_utils8 = require("@vercel/build-utils");
71270
71316
  var version = 3;
71271
71317
  // Annotate the CommonJS export names for ESM import in node:
71272
71318
  0 && (module.exports = {
71319
+ _resetBundlingRoutesEmitted,
71273
71320
  build,
71274
71321
  prepareCache,
71275
71322
  shouldServe,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/node",
3
- "version": "5.6.24",
3
+ "version": "5.7.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -32,9 +32,9 @@
32
32
  "tsx": "4.21.0",
33
33
  "typescript": "npm:typescript@5.9.3",
34
34
  "undici": "5.28.4",
35
- "@vercel/build-utils": "13.12.2",
35
+ "@vercel/error-utils": "2.0.3",
36
36
  "@vercel/static-config": "3.2.0",
37
- "@vercel/error-utils": "2.0.3"
37
+ "@vercel/build-utils": "13.12.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@babel/core": "7.24.4",