@vercel/node
Advanced tools
| // | ||
| // Unified Lambda handler for bundled vanilla API routes. | ||
| // All bundleable lambdas share this exact file (same handler digest). | ||
| // It reads x-matched-path to determine which entrypoint to invoke. | ||
| // | ||
| // IMPORTANT: This file must remain entrypoint-agnostic. Do not embed | ||
| // any path-specific constants. The x-matched-path header is set by | ||
| // route rules injected by the builder. | ||
| // | ||
| // This runs at Lambda runtime where the handler is invoked with (req, res). | ||
| // User modules are in the Lambda's file tree alongside this file. | ||
| // | ||
| // Supports all handler shapes: | ||
| // 1. Function export: module.exports = (req, res) => { ... } | ||
| // 2. Web handlers: export function GET(request) { ... } | ||
| // 3. Fetch handler: export function fetch(request) { ... } | ||
| // 4. Server handler: http.createServer(...).listen() | ||
| // | ||
| const http = require('http'); | ||
| const { existsSync } = require('fs'); | ||
| const { resolve } = require('path'); | ||
| const { pathToFileURL } = require('url'); | ||
| const { Readable } = require('stream'); | ||
| const HTTP_METHODS = [ | ||
| 'GET', | ||
| 'HEAD', | ||
| 'OPTIONS', | ||
| 'POST', | ||
| 'PUT', | ||
| 'DELETE', | ||
| 'PATCH', | ||
| ]; | ||
| const handlerCache = Object.create(null); | ||
| /** | ||
| * Resolve an extensionless entrypoint to an actual file path. | ||
| * Tries common JS extensions in order, returning the first match. | ||
| */ | ||
| function resolveEntrypoint(name) { | ||
| const base = resolve('./' + name); | ||
| for (const ext of ['.js', '.cjs', '.mjs']) { | ||
| const p = base + ext; | ||
| if (existsSync(p)) return p; | ||
| } | ||
| // If base is a directory, look for index files inside it | ||
| if (existsSync(base)) { | ||
| for (const ext of ['.js', '.cjs', '.mjs']) { | ||
| const p = resolve(base, 'index' + ext); | ||
| if (existsSync(p)) return p; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Load a module via dynamic import(). Works for both CJS and ESM regardless | ||
| * of the file extension or the package.json "type" field. | ||
| */ | ||
| async function loadModule(filePath) { | ||
| return import(pathToFileURL(filePath).href); | ||
| } | ||
| /** | ||
| * Unwrap nested default exports (common with TS/ESM compiled to CJS). | ||
| */ | ||
| function unwrapDefaults(mod) { | ||
| for (let i = 0; i < 5; i++) { | ||
| if (mod && mod.default) mod = mod.default; | ||
| else break; | ||
| } | ||
| return mod; | ||
| } | ||
| /** | ||
| * Create a Node.js (req, res) handler from web handler exports (GET, POST, fetch, etc.). | ||
| * Uses Node.js 18+ built-in Web API globals (Request, Response). | ||
| */ | ||
| function createWebHandler(listener) { | ||
| const methods = Object.create(null); | ||
| // If fetch is exported, it handles all methods | ||
| if (typeof listener.fetch === 'function') { | ||
| for (const m of HTTP_METHODS) { | ||
| methods[m] = listener.fetch; | ||
| } | ||
| } | ||
| // Named method exports override fetch | ||
| for (const m of HTTP_METHODS) { | ||
| if (typeof listener[m] === 'function') { | ||
| methods[m] = listener[m]; | ||
| } | ||
| } | ||
| return async (req, res) => { | ||
| const method = req.method || 'GET'; | ||
| const fn = methods[method]; | ||
| if (!fn) { | ||
| res.statusCode = 405; | ||
| res.end('Method Not Allowed'); | ||
| return; | ||
| } | ||
| // Build a Web API Request from the Node.js IncomingMessage | ||
| const proto = req.headers['x-forwarded-proto'] || 'https'; | ||
| const host = | ||
| req.headers['x-forwarded-host'] || req.headers.host || 'localhost'; | ||
| const url = new URL(req.url || '/', `${proto}://${host}`); | ||
| const init = { method, headers: req.headers, duplex: 'half' }; | ||
| if (method !== 'GET' && method !== 'HEAD') { | ||
| init.body = Readable.toWeb(req); | ||
| } | ||
| const request = new Request(url, init); | ||
| let response; | ||
| try { | ||
| response = await fn(request); | ||
| } catch { | ||
| res.statusCode = 500; | ||
| res.end('Internal Server Error'); | ||
| return; | ||
| } | ||
| // Write the Web API Response back to the Node.js ServerResponse | ||
| res.statusCode = response.status; | ||
| for (const [key, value] of response.headers) { | ||
| res.appendHeader(key, value); | ||
| } | ||
| if (response.body) { | ||
| for await (const chunk of response.body) { | ||
| res.write(chunk); | ||
| } | ||
| } | ||
| res.end(); | ||
| }; | ||
| } | ||
| /** | ||
| * Compile a user module and return a (req, res) handler regardless of | ||
| * export shape. Mirrors the detection logic in serverless-handler.mts. | ||
| */ | ||
| async function compileUserCode(filePath) { | ||
| let server = null; | ||
| let serverFound; | ||
| // Monkey-patch http.Server.prototype.listen to capture server instances | ||
| // created during module import (e.g. Express apps calling app.listen()). | ||
| const originalListen = http.Server.prototype.listen; | ||
| http.Server.prototype.listen = function () { | ||
| server = this; | ||
| http.Server.prototype.listen = originalListen; | ||
| if (serverFound) serverFound(); | ||
| return this; | ||
| }; | ||
| try { | ||
| let listener = await loadModule(filePath); | ||
| listener = unwrapDefaults(listener); | ||
| // 1. Web handlers (GET, POST, fetch, etc.) | ||
| const isWebHandler = | ||
| HTTP_METHODS.some(m => typeof listener[m] === 'function') || | ||
| typeof listener.fetch === 'function'; | ||
| if (isWebHandler) { | ||
| return createWebHandler(listener); | ||
| } | ||
| // 2. Function handler: (req, res) => { ... } | ||
| if (typeof listener === 'function') { | ||
| return listener; | ||
| } | ||
| // 3. Server handler: http.createServer(...).listen() | ||
| // Wait briefly for async server creation if not captured yet. | ||
| if (!server) { | ||
| await new Promise(r => { | ||
| serverFound = r; | ||
| setTimeout(r, 1000); | ||
| }); | ||
| } | ||
| if (server) { | ||
| // Start the captured server on a random port and proxy requests to it. | ||
| await new Promise(r => server.listen(0, '127.0.0.1', r)); | ||
| const { port } = server.address(); | ||
| return (req, res) => { | ||
| const proxyReq = http.request( | ||
| { | ||
| hostname: '127.0.0.1', | ||
| port, | ||
| path: req.url, | ||
| method: req.method, | ||
| headers: { | ||
| ...req.headers, | ||
| host: req.headers['x-forwarded-host'] || req.headers.host, | ||
| }, | ||
| }, | ||
| proxyRes => { | ||
| res.writeHead(proxyRes.statusCode, proxyRes.headers); | ||
| proxyRes.pipe(res); | ||
| } | ||
| ); | ||
| proxyReq.on('error', err => { | ||
| res.statusCode = 502; | ||
| res.end('Proxy error: ' + err.message); | ||
| }); | ||
| req.pipe(proxyReq); | ||
| }; | ||
| } | ||
| throw new Error( | ||
| "Can't detect handler export shape for " + | ||
| filePath + | ||
| '. Expected a function export, HTTP method exports (GET, POST, ...), or an http.Server.' | ||
| ); | ||
| } finally { | ||
| http.Server.prototype.listen = originalListen; | ||
| } | ||
| } | ||
| module.exports = async (req, res) => { | ||
| const matchedPath = req.headers['x-matched-path']; | ||
| if (typeof matchedPath !== 'string' || !matchedPath) { | ||
| res.statusCode = 500; | ||
| res.end( | ||
| 'Missing x-matched-path header. The bundled handler requires route-level header injection.' | ||
| ); | ||
| return; | ||
| } | ||
| // Convert matched path to entrypoint name. | ||
| // x-matched-path "/" maps to "index", "/api/hello" maps to "api/hello". | ||
| const entrypoint = matchedPath.replace(/^\//, '') || 'index'; | ||
| if (!handlerCache[entrypoint]) { | ||
| const filePath = resolveEntrypoint(entrypoint); | ||
| if (!filePath) { | ||
| res.statusCode = 404; | ||
| res.end('No handler found for ' + entrypoint); | ||
| return; | ||
| } | ||
| // Store the Promise immediately so concurrent requests to the same | ||
| // entrypoint share one compileUserCode() call instead of racing. | ||
| handlerCache[entrypoint] = compileUserCode(filePath).catch(err => { | ||
| delete handlerCache[entrypoint]; // Allow retry on next request | ||
| throw err; | ||
| }); | ||
| } | ||
| const handler = await handlerCache[entrypoint]; | ||
| return handler(req, res); | ||
| }; |
+11
-11
@@ -665,3 +665,4 @@ var __create = Object.create; | ||
| this.waitUntil = (promise) => { | ||
| this.promises.add(promise); | ||
| const handledPromise = Promise.resolve(promise).catch(this.onError); | ||
| this.promises.add(handledPromise); | ||
| }; | ||
@@ -671,7 +672,3 @@ this.waitForBatch = async () => { | ||
| this.promises.clear(); | ||
| await Promise.all( | ||
| promises.map( | ||
| (promise) => Promise.resolve(promise).then(() => void 0, this.onError) | ||
| ) | ||
| ); | ||
| await Promise.all(promises); | ||
| }; | ||
@@ -912,3 +909,3 @@ this.onError = onError ?? console.error; | ||
| return str ? JSON.parse(str) : {}; | ||
| } catch (error) { | ||
| } catch (_error) { | ||
| throw new ApiError(400, "Invalid JSON"); | ||
@@ -1257,6 +1254,9 @@ } | ||
| validateConfiguredRuntime(runtime, entrypoint2); | ||
| const { maxDuration } = await getLambdaOptionsFromFunction({ | ||
| sourceFile: entrypoint2, | ||
| config | ||
| }); | ||
| const { maxDuration: configMaxDuration } = await getLambdaOptionsFromFunction( | ||
| { | ||
| sourceFile: entrypoint2, | ||
| config | ||
| } | ||
| ); | ||
| const maxDuration = typeof configMaxDuration === "number" ? configMaxDuration : void 0; | ||
| const isMiddleware = config.middleware === true; | ||
@@ -1263,0 +1263,0 @@ const useEdgeRuntime = isMiddleware && !runtime || isEdgeRuntime(runtime); |
@@ -64,3 +64,2 @@ // provided by the edge runtime: | ||
| // This will be invoked by logic using this template | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| function registerFetchListener(module, options, dependencies) { | ||
@@ -67,0 +66,0 @@ let handler; |
+6
-6
| { | ||
| "name": "@vercel/node", | ||
| "version": "5.7.0-canary.20260211174907.cdd2da6", | ||
| "version": "5.7.0", | ||
| "license": "Apache-2.0", | ||
@@ -20,3 +20,3 @@ "main": "./dist/index", | ||
| "@types/node": "20.11.0", | ||
| "@vercel/nft": "1.1.1", | ||
| "@vercel/nft": "1.5.0", | ||
| "async-listen": "3.0.0", | ||
@@ -36,5 +36,5 @@ "cjs-module-lexer": "1.2.3", | ||
| "undici": "5.28.4", | ||
| "@vercel/error-utils": "2.1.0-canary.20260211174907.cdd2da6", | ||
| "@vercel/static-config": "3.2.0-canary.20260211174907.cdd2da6", | ||
| "@vercel/build-utils": "13.4.0-canary.20260211174907.cdd2da6" | ||
| "@vercel/error-utils": "2.0.3", | ||
| "@vercel/static-config": "3.2.0", | ||
| "@vercel/build-utils": "13.12.2" | ||
| }, | ||
@@ -64,3 +64,3 @@ "devDependencies": { | ||
| "vitest": "^2.0.1", | ||
| "@vercel/functions": "3.5.0-canary.20260211174907.cdd2da6" | ||
| "@vercel/functions": "3.4.3" | ||
| }, | ||
@@ -67,0 +67,0 @@ "scripts": { |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
2822209
0.38%7
16.67%73383
0.46%0
-100%17
13.33%17
112.5%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated