@vulppi/intrest
Version:
Backend kit make by Vulppi
5 lines • 66.9 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/workers/v1-single.ts", "../../src/controllers/constants.ts", "../../src/controllers/path.ts", "../../src/controllers/request-handler.ts", "../../src/controllers/parser.ts", "../../src/controllers/tunnel.ts", "../../src/controllers/response.ts", "../../src/controllers/compare.ts"],
"sourcesContent": ["import ck from 'chalk'\r\nimport { existsSync, mkdirSync, rmSync } from 'fs'\r\nimport { createServer } from 'http'\r\nimport { join } from 'path'\r\nimport { defaultPaths, globPatterns } from '../controllers/constants'\r\nimport { getModule, globFind, normalizePath } from '../controllers/path'\r\nimport { buildRequestHandler } from '../controllers/request-handler'\r\nimport { tunnel } from '../controllers/tunnel'\r\n\r\nconst basePath = process.cwd()\r\nconst configPath = await globFind(basePath, globPatterns.configFile)\r\nconst config = ((await getModule(configPath)).default || {}) as IntREST.Config\r\n\r\nconst bootstrapPath = await globFind(\r\n basePath,\r\n defaultPaths.compiledFolder,\r\n defaultPaths.compiledRoutes,\r\n globPatterns.bootstrapCompiled,\r\n)\r\nconst bootstrap = await getModule(bootstrapPath)\r\nconst bootstrapHandler = bootstrap.bootstrap || bootstrap.default\r\nif (bootstrapHandler && typeof bootstrapHandler === 'function') {\r\n console.log('\\n Bootstrapping...\\n')\r\n await bootstrapHandler(config)\r\n}\r\n\r\nconst appPort = config.port || +(process.env.PORT || 4000)\r\nconst appTempPath = normalizePath(\r\n join(basePath, config.paths?.uploadTemp || '.tmp'),\r\n)\r\n\r\nif (existsSync(appTempPath)) {\r\n rmSync(appTempPath, { recursive: true })\r\n}\r\nmkdirSync(appTempPath, { recursive: true })\r\nconst server = createServer(\r\n {\r\n noDelay: true,\r\n keepAlive: true,\r\n keepAliveTimeout: 30000,\r\n keepAliveInitialDelay: 5000,\r\n connectionsCheckingInterval: 5000,\r\n },\r\n buildRequestHandler(tunnel),\r\n)\r\nserver.listen(appPort, () => {\r\n console.log(`\\n Server running on port %s`, ck.yellow(appPort))\r\n console.log(\r\n ` You can access the server at %s or %s\\n`,\r\n ck.yellow(`http://localhost:${appPort}`),\r\n ck.yellow(`http://127.0.0.1:${appPort}`),\r\n )\r\n})\r\n", "export const isDev = () => process.env.NODE_ENV === 'development'\r\n\r\nexport const globPatterns = {\r\n env: ['.env', '.env{.local,.development,.production}'],\r\n entryFolder: ['routes', 'src/routes'],\r\n assetsFolder: ['assets', 'src/assets'],\r\n staticFolder: ['static', 'src/static', 'public', 'src/public'],\r\n configFile: 'intrest.config.{mjs,cjs,js}',\r\n envFile: '.env{.local,.development,.production}',\r\n bootstrapEntry: 'bootstrap.ts',\r\n bootstrapCompiled: 'bootstrap.mjs',\r\n entryPoints: '**/{route,middleware,validation}.ts',\r\n identityPoints: '**/__identity.mjs',\r\n middlewarePoints: '**/middleware.mjs',\r\n routeFile: 'route.{mjs,cjs,js,ts}',\r\n middlewareFile: 'middleware.{mjs,cjs,js,ts}',\r\n} as const\r\n\r\nexport const regexpPatterns = {\r\n env: /^\\.env(?:\\.[a-z-_]*)?$/,\r\n config: /^intrest\\.config\\.[mc]?js$/,\r\n bootstrap: /^bootstrap\\.ts$/,\r\n entry: /(?:^|\\/)(?:route|middleware|validation)\\.ts$/,\r\n observable: /(?:route)\\.ts$/,\r\n reservedChars: /(?:[.*+?^=!:${}()|\\[\\]\\/\\\\])/g,\r\n startSlashesOrNot: /^[\\\\\\/]*/,\r\n endSlashesOrNot: /[\\\\\\/]*$/,\r\n multiSlashes: /[\\\\\\/]+/g,\r\n isBusboyContentType: /^(?:x-www-form-urlencoded|multipart\\/form-data.*)$/i,\r\n isJSONContentType: /^application\\/json$/i,\r\n isXMLContentType: /^application\\/xml$/i,\r\n isAcceptableContentType:\r\n /^(?:x-www-form-urlencoded|multipart\\/form-data.*|application\\/(?:json|xml))$/i,\r\n} as const\r\n\r\nexport const defaultPaths = {\r\n compiledFolder: '.intrest',\r\n compiledRoutes: 'routes',\r\n workerMultiWorker: 'multi.mjs',\r\n workerSingleWorker: 'single.mjs',\r\n workerRouter: 'router.mjs',\r\n routeIdentity: '__identity.mjs',\r\n} as const\r\n\r\nexport const defaultOutputPaths = {\r\n vercel: '.vercel/output',\r\n}\r\n", "import { glob } from 'glob'\r\nimport { join, normalize } from 'path'\r\nimport { pathToFileURL } from 'url'\r\nimport { globPatterns, isDev } from './constants'\r\n\r\n/**\r\n * Normalize the path and replace all backslash with slash\r\n *\r\n * @param pathname\r\n * @returns\r\n */\r\nexport function normalizePath(pathname: string) {\r\n return normalize(pathname).replace(/[\\/\\\\]+/g, '/')\r\n}\r\n\r\n/**\r\n * Get the module from the given path\r\n * If the path is not given or the module is not found, return empty object\r\n */\r\nexport async function getModule(configPath?: string) {\r\n if (!configPath) return {} as Record<string, any>\r\n const configURL = pathToFileURL(configPath)\r\n configURL.searchParams.set('update', Date.now().toString())\r\n try {\r\n return (\r\n (await import(configURL.toString()).then(\r\n (m) => m as Record<string, any>,\r\n )) || {}\r\n )\r\n } catch (err) {\r\n return {} as Record<string, any>\r\n }\r\n}\r\n\r\n/**\r\n * Find the first file that match the glob pattern\r\n */\r\nexport async function globFind(\r\n ...pattern: string[]\r\n): Promise<string | undefined> {\r\n const res = await glob(normalizePath(join(...pattern)), {\r\n ignore: ['**/node_modules/**'],\r\n windowsPathsNoEscape: true,\r\n })\r\n return res[0] && normalizePath(res[0])\r\n}\r\n\r\n/**\r\n * Find the first file that match the glob pattern list\r\n */\r\nexport async function globFindList(\r\n ...pattern: string[][]\r\n): Promise<string | undefined> {\r\n const list = pattern.map((p) => normalizePath(join(...p)))\r\n const res = await glob(list, {\r\n ignore: ['**/node_modules/**'],\r\n windowsPathsNoEscape: true,\r\n })\r\n return res[0] && normalizePath(res[0])\r\n}\r\n\r\n/**\r\n * Find all files that match the glob pattern\r\n */\r\nexport async function globFindAll(...pattern: string[]): Promise<string[]> {\r\n const res = await glob(normalizePath(join(...pattern)), {\r\n ignore: ['**/node_modules/**'],\r\n windowsPathsNoEscape: true,\r\n })\r\n return res.map(normalizePath)\r\n}\r\n\r\n/**\r\n * Find all files that match the glob pattern list\r\n */\r\nexport async function globFindAllList(\r\n ...pattern: string[][]\r\n): Promise<string[]> {\r\n const list = pattern.map((p) => normalizePath(join(...p)))\r\n const res = await glob(list.reverse(), {\r\n ignore: ['**/node_modules/**'],\r\n windowsPathsNoEscape: true,\r\n })\r\n return res.map(normalizePath)\r\n}\r\n\r\n/**\r\n * Escape the path with the given escape string\r\n */\r\nexport function escapePath(pathname: string, ...escape: string[]) {\r\n return normalizePath(pathname)\r\n .replace(normalizePath(join(...escape)), '')\r\n .replace(/^\\//, '')\r\n}\r\n\r\n/**\r\n * Clear the extension of the path\r\n */\r\nexport function clearExtension(path: string) {\r\n return normalizePath(path).replace(/\\.[a-z0-9]+$/i, '')\r\n}\r\n\r\n/**\r\n * Find the list of env files that match the glob pattern\r\n * inside the given base path\r\n */\r\nexport async function findEnvPaths(basePath: string) {\r\n return globFindAllList(...globPatterns.env.map((p) => [basePath, p]))\r\n}\r\n\r\n/**\r\n * Get the first env file that match the glob pattern\r\n * inside the given base path\r\n */\r\nexport async function getEnvPath(basePath: string) {\r\n return (await findEnvPaths(basePath))[0] as string | undefined\r\n}\r\n\r\n/**\r\n * Find the app folder that match the glob pattern\r\n * inside the given base path\r\n */\r\nexport async function getFolderPath(\r\n basePath: string,\r\n possibleFolders: readonly string[],\r\n) {\r\n return (\r\n (await globFindAllList(...possibleFolders.map((p) => [basePath, p])))[0] ||\r\n possibleFolders[1]\r\n )\r\n}\r\n\r\n/**\r\n * Add the update query to the module path for development\r\n * to prevent the module from being cached\r\n */\r\nexport function encapsulateModule(v: string) {\r\n if (!isDev()) return v\r\n return `${v}?update=${Date.now()}`\r\n}\r\n", "import busboy from 'busboy'\r\nimport ck from 'chalk'\r\nimport concat from 'concat-stream'\r\nimport cookie from 'cookie'\r\nimport { randomUUID } from 'crypto'\r\nimport { XMLParser, XMLValidator } from 'fast-xml-parser'\r\nimport { createWriteStream, rmSync } from 'fs'\r\nimport type { IncomingMessage, ServerResponse } from 'http'\r\nimport { StatusCodes, getReasonPhrase } from 'http-status-codes'\r\nimport _ from 'lodash'\r\nimport { join } from 'path'\r\nimport { performance } from 'perf_hooks'\r\nimport { globPatterns, isDev, regexpPatterns } from './constants'\r\nimport {\r\n parseDecompressBuffer,\r\n parseStringBytesToNumber,\r\n parseStringToAutoDetectValue,\r\n} from './parser'\r\nimport { getModule, globFind, normalizePath } from './path'\r\nimport ms from 'ms'\r\n\r\ninterface TunnelFunction {\r\n (\r\n params: Omit<WorkerProps, 'requestId'>,\r\n endCallback: (state: ResponseState, data: ResponseData) => void,\r\n ): Promise<void>\r\n}\r\n\r\nfunction debugRequest(\r\n startRequestTime: number,\r\n method: string,\r\n path: string,\r\n status: number = 200,\r\n) {\r\n console.debug(\r\n '%s(%s - %s) - %s %s',\r\n ck.yellow(method),\r\n ck.green(status),\r\n getReasonPhrase(status),\r\n ck.bold(path),\r\n ck.cyan(`${(performance.now() - startRequestTime).toPrecision(5)}ms`),\r\n )\r\n}\r\n\r\nexport function buildRequestHandler(tunnel: TunnelFunction) {\r\n return async function requestHandler(\r\n req: IncomingMessage,\r\n res: ServerResponse,\r\n ) {\r\n // Get the root path of the project\r\n const basePath = process.cwd()\r\n // Try find the config module\r\n const configPath = await globFind(basePath, globPatterns.configFile)\r\n const config = ((await getModule(configPath)).default ||\r\n {}) as IntREST.Config\r\n // Get the temp path for upload files\r\n const appTempPath = normalizePath(\r\n join(basePath, config.paths?.uploadTemp || '.tmp'),\r\n )\r\n\r\n // Get the request method, path, query and content type\r\n const method = (req.method?.toUpperCase() ||\r\n 'GET') as IntREST.RequestMethods\r\n const [prePath, preQuery] = (req.url || '/').split('?')\r\n const path = decodeURIComponent(prePath)\r\n const query = preQuery || ''\r\n const contentType = req.headers['content-type'] || 'application/json'\r\n\r\n // Prepare origin for CORS\r\n const pureOrigin = req.headers.origin || req.headers.host || ''\r\n const ipOrigin = req.socket.remoteAddress\r\n const origin = pureOrigin.replace(/^[a-z]+:\\/\\//, '')\r\n const originWithProtocol = /^[a-z]+:\\/\\//.test(pureOrigin)\r\n ? pureOrigin\r\n : pureOrigin.includes('localhost') || isDev()\r\n ? `http://${pureOrigin || 'localhost'}`\r\n : pureOrigin\r\n ? `https://${pureOrigin}`\r\n : '*'\r\n\r\n // Validate allowOrigin\r\n if (config.limits?.allowOrigin && !isDev()) {\r\n const allowOrigin = (\r\n Array.isArray(config.limits.allowOrigin)\r\n ? config.limits.allowOrigin\r\n : [config.limits.allowOrigin]\r\n ).map((d) => d.replace(/^[a-z]+:\\/\\//, ''))\r\n\r\n const allowOriginItem = allowOrigin.find((o) => origin.endsWith(o))\r\n if (allowOriginItem) {\r\n res.setHeader('Access-Control-Allow-Origin', allowOriginItem)\r\n } else {\r\n res.setHeader('Access-Control-Allow-Origin', '*')\r\n }\r\n } else {\r\n res.setHeader('Access-Control-Allow-Origin', originWithProtocol)\r\n }\r\n\r\n // Set default headers\r\n res.setHeader('Server', 'IntREST')\r\n\r\n res.setHeader('Accept', [\r\n 'application/json',\r\n 'application/xml',\r\n 'x-www-form-urlencoded',\r\n 'multipart/form-data',\r\n ])\r\n res.setHeader('Accept-Encoding', ['gzip', 'x-gzip', 'deflate', 'identity'])\r\n res.setHeader('Access-Control-Allow-Methods', [\r\n 'GET',\r\n 'POST',\r\n 'PUT',\r\n 'PATCH',\r\n 'DELETE',\r\n 'OPTIONS',\r\n ])\r\n res.setHeader('Access-Control-Allow-Headers', [\r\n 'Content-Length',\r\n 'Content-Type',\r\n 'Authorization',\r\n 'Range',\r\n '*',\r\n ...(config.limits?.allowHeaders || []),\r\n ])\r\n res.setHeader('Access-Control-Allow-Credentials', 'true')\r\n res.setHeader('Access-Control-Max-Age', '86400')\r\n res.setHeader('Accept-Ranges', 'bytes')\r\n res.setHeader('Connection', 'keep-alive')\r\n res.setHeader('Keep-Alive', ['timeout=5', 'max=30'])\r\n\r\n // Start the request time\r\n const startRequestTime = performance.now()\r\n\r\n if (/^options$/i.test(method)) {\r\n res.statusCode = StatusCodes.NO_CONTENT\r\n res.end()\r\n return\r\n }\r\n\r\n // Check if the content type is acceptable\r\n if (!regexpPatterns.isAcceptableContentType.test(contentType)) {\r\n res.writeHead(StatusCodes.UNSUPPORTED_MEDIA_TYPE, {\r\n 'Content-Type': 'application/json',\r\n })\r\n debugRequest(\r\n startRequestTime,\r\n method,\r\n path,\r\n StatusCodes.UNSUPPORTED_MEDIA_TYPE,\r\n )\r\n return res.end(\r\n JSON.stringify({\r\n message:\r\n config.messages?.UNSUPPORTED_MEDIA_TYPE || 'Unsupported media type',\r\n }),\r\n )\r\n }\r\n\r\n let body = {} as Record<string, any>\r\n // Check if the body size is acceptable\r\n const bodySize =\r\n (req.headers['content-length'] &&\r\n parseInt(req.headers['content-length'])) ||\r\n 0\r\n const maxBodySize = parseStringBytesToNumber(\r\n config.limits?.bodyMaxSize || '10mb',\r\n )\r\n\r\n if (bodySize > maxBodySize) {\r\n res.writeHead(StatusCodes.REQUEST_TOO_LONG, {\r\n 'Content-Type': 'application/json',\r\n })\r\n debugRequest(startRequestTime, method, path, StatusCodes.REQUEST_TOO_LONG)\r\n return res.end(\r\n JSON.stringify({\r\n message:\r\n config.messages?.REQUEST_TOO_LONG || 'Request entity too large',\r\n }),\r\n )\r\n }\r\n const filesToBeRemoved: string[] = []\r\n // Parse the body if the method is not GET\r\n if (!/^get$/i.test(method)) {\r\n // x-www-form-urlencoded and multipart/form-data\r\n try {\r\n if (regexpPatterns.isBusboyContentType.test(contentType)) {\r\n await new Promise<void>((resolve, reject) => {\r\n const bb = busboy({ headers: req.headers })\r\n bb.on('file', (name, file, info) => {\r\n const { filename, encoding, mimeType: mimetype } = info\r\n const fileHash = randomUUID()\r\n const filePath = normalizePath(join(appTempPath, fileHash))\r\n const fileMetadata = {\r\n absolutePath: filePath,\r\n filename,\r\n encoding,\r\n mimetype,\r\n } as IntREST.FileMetadata\r\n _.set(body, name, fileMetadata)\r\n filesToBeRemoved.push(filePath)\r\n const fileWriter = createWriteStream(filePath, {\r\n flags: 'w',\r\n })\r\n file.pipe(fileWriter, {\r\n end: true,\r\n })\r\n })\r\n bb.on('field', (name, val) => {\r\n _.set(body, name, parseStringToAutoDetectValue(val))\r\n })\r\n bb.on('close', resolve)\r\n bb.on('error', reject)\r\n req.pipe(bb, {\r\n end: true,\r\n })\r\n })\r\n } else {\r\n // application/json and application/xml\r\n const buffer = await new Promise<Buffer>((resolve) => {\r\n const writer = concat(resolve)\r\n req.pipe(writer)\r\n })\r\n const encoding = req.headers['content-encoding'] || 'identity'\r\n\r\n const bodyString =\r\n (\r\n await parseDecompressBuffer(\r\n buffer,\r\n encoding.split(/, */) as IntREST.RequestEncoding[],\r\n )\r\n ).toString() || '{}' // default to empty object\r\n\r\n if (regexpPatterns.isJSONContentType.test(contentType)) {\r\n body = JSON.parse(bodyString)\r\n } else {\r\n // is XML Content-Type\r\n const parser = new XMLParser({\r\n ignoreAttributes: false,\r\n allowBooleanAttributes: true,\r\n attributeNamePrefix: '',\r\n attributesGroupName: '$attributes',\r\n commentPropName: '$comment',\r\n cdataPropName: '$cdata',\r\n textNodeName: '$text',\r\n alwaysCreateTextNode: true,\r\n parseTagValue: true,\r\n unpairedTags: ['meta', 'link', 'img', 'br', 'hr', 'input'],\r\n })\r\n if (!XMLValidator.validate(bodyString)) {\r\n throw new Error('Invalid XML')\r\n }\r\n\r\n body = parser.parse(bodyString)\r\n }\r\n }\r\n } catch (err: any) {\r\n res.writeHead(StatusCodes.BAD_REQUEST, {\r\n 'Content-Type': 'application/json',\r\n })\r\n debugRequest(startRequestTime, method, path, StatusCodes.BAD_REQUEST)\r\n return res.end(\r\n JSON.stringify({\r\n message: 'Invalid body',\r\n error: err.message || err.toString(),\r\n }),\r\n )\r\n }\r\n }\r\n\r\n const cookies = cookie.parse(req.headers.cookie || '')\r\n\r\n try {\r\n await tunnel(\r\n {\r\n basePath,\r\n config,\r\n data: {\r\n method,\r\n path,\r\n custom: {},\r\n headers: req.headers,\r\n cookies,\r\n body,\r\n query,\r\n origin: {\r\n url:\r\n originWithProtocol === '*'\r\n ? undefined\r\n : new URL(originWithProtocol),\r\n ip: ipOrigin,\r\n },\r\n },\r\n },\r\n (state, data) => {\r\n if (state === 'cookie') {\r\n const { name, value, options } = data as ResponseDataMap['cookie']\r\n if (options?.maxAge && typeof options?.maxAge === 'string') {\r\n options.maxAge = ms(options.maxAge)\r\n }\r\n\r\n res.appendHeader(\r\n 'Set-Cookie',\r\n cookie.serialize(name, value, options as any),\r\n )\r\n } else if (state === 'clear-cookie') {\r\n const { name, options } = data as ResponseDataMap['clear-cookie']\r\n if (options?.maxAge && typeof options?.maxAge === 'string') {\r\n options.maxAge = ms(options.maxAge)\r\n }\r\n\r\n res.appendHeader(\r\n 'Set-Cookie',\r\n cookie.serialize(name, '', options as any),\r\n )\r\n } else if (state === 'set') {\r\n const [name, value] = data as ResponseDataMap['set']\r\n res.setHeader(name, value || '')\r\n } else if (state === 'write') {\r\n const buffer = data as ResponseDataMap['write']\r\n res.write(buffer)\r\n } else if (state === 'status') {\r\n res.statusCode = data as ResponseDataMap['status']\r\n } else if (state === 'end') {\r\n if (\r\n filesToBeRemoved.length &&\r\n config.removeUploadFilesAfterResponse\r\n ) {\r\n for (const file of filesToBeRemoved) {\r\n try {\r\n rmSync(file)\r\n } catch (err: any) {\r\n console.error(`Error removing file ${file}`, err.message)\r\n }\r\n }\r\n }\r\n debugRequest(startRequestTime, method, path, res.statusCode)\r\n res.end()\r\n }\r\n },\r\n )\r\n } catch (err) {\r\n console.error(err)\r\n res.writeHead(StatusCodes.INTERNAL_SERVER_ERROR, {\r\n 'Content-Type': 'application/json',\r\n })\r\n debugRequest(\r\n startRequestTime,\r\n method,\r\n path,\r\n StatusCodes.INTERNAL_SERVER_ERROR,\r\n )\r\n return res.end(\r\n JSON.stringify({\r\n message:\r\n config.messages?.INTERNAL_SERVER_ERROR || 'Internal server error',\r\n }),\r\n )\r\n }\r\n }\r\n}\r\n", "import type { Readable } from 'stream'\nimport {\n createDeflate,\n createGunzip,\n createGzip,\n createInflate,\n deflate,\n gunzip,\n gzip,\n inflate,\n} from 'zlib'\n\n/**\n * Parse string bytes to number\n * 1kb = 1024 bytes\n * 1mb = 1048576 bytes\n * 1gb = 1073741824 bytes\n * 1tb = 1099511627776 bytes\n *\n * @param bytes\n * @returns\n */\nexport function parseStringBytesToNumber(bytes: string | number): number {\n if (typeof bytes !== 'string') return bytes\n\n const [, size, unit] = bytes.match(/^(\\d+)([kmgt]b?)$/i) || []\n if (!size || !unit) return 0\n const sizeNumber = parseInt(size)\n if (isNaN(sizeNumber)) return 0\n switch (unit.toLowerCase()) {\n case 'tb':\n return sizeNumber * 1024 * 1024 * 1024 * 1024\n case 'gb':\n return sizeNumber * 1024 * 1024 * 1024\n case 'mb':\n return sizeNumber * 1024 * 1024\n case 'kb':\n return sizeNumber * 1024\n default:\n return sizeNumber\n }\n}\n\n/**\n * If detect string is number or boolean, parse it to number or boolean\n */\nexport function parseStringToAutoDetectValue(val?: string | null) {\n switch (true) {\n case val == null:\n return undefined\n case /^(no|n|false|f|off)$/i.test(val!):\n return false\n case /^(yes|y|true|t|on)$/i.test(val!):\n return true\n case !isNaN(parseFloat(val!)):\n return parseFloat(val!)\n default:\n return val\n }\n}\n\n/**\n * Decompress buffer with gzip and/or deflate encoding\n *\n * @param data\n * @param encoding\n * @returns\n */\nexport async function parseDecompressBuffer(\n data: Buffer,\n encoding: IntREST.RequestEncoding[] = ['identity'],\n) {\n let buffer = data\n for (const enc of encoding) {\n if (/^gzip$/i.test(enc)) {\n buffer = await new Promise<Buffer>((resolve, reject) => {\n gunzip(buffer, (err, res) => {\n if (err) return reject(err)\n resolve(res)\n })\n })\n } else if (/^deflate$/i.test(enc)) {\n buffer = await new Promise<Buffer>((resolve, reject) => {\n inflate(buffer, (err, res) => {\n if (err) return reject(err)\n resolve(res)\n })\n })\n }\n }\n return buffer\n}\n\n/**\n * Compress buffer with gzip and/or deflate encoding\n *\n * @param data\n * @param encoding\n * @returns\n */\nexport async function parseCompressBuffer(\n data: Buffer,\n encoding: IntREST.RequestEncoding[] = ['identity'],\n) {\n let buffer = data\n for (const enc of encoding) {\n if (/^gzip$/i.test(enc)) {\n buffer = await new Promise<Buffer>((resolve, reject) => {\n gzip(buffer, (err, res) => {\n if (err) return reject(err)\n resolve(res)\n })\n })\n } else if (/^deflate$/i.test(enc)) {\n buffer = await new Promise<Buffer>((resolve, reject) => {\n deflate(buffer, (err, res) => {\n if (err) return reject(err)\n resolve(res)\n })\n })\n }\n }\n return buffer\n}\n\n/**\n * Decompress stream piping with gzip and/or deflate encoding\n *\n * @param data\n * @param encoding\n * @returns\n */\nexport async function parseDecompressStream(\n data: Readable,\n encoding: IntREST.RequestEncoding[] = ['identity'],\n) {\n let stream = data\n for (const enc of encoding) {\n if (/^gzip$/i.test(enc)) {\n stream = stream.pipe(createGunzip())\n } else if (/^deflate$/i.test(enc)) {\n stream = stream.pipe(createInflate())\n }\n }\n return stream\n}\n\n/**\n * Compress stream piping with gzip and/or deflate encoding\n *\n * @param data\n * @param encoding\n * @returns\n */\nexport async function parseCompressStream(\n data: Readable,\n encoding: IntREST.RequestEncoding[] = ['identity'],\n) {\n let stream = data\n for (const enc of encoding) {\n if (/^gzip$/i.test(enc)) {\n stream = stream.pipe(createGzip())\n } else if (/^deflate$/i.test(enc)) {\n stream = stream.pipe(createDeflate())\n }\n }\n return stream\n}\n", "import { createReadStream } from 'fs'\r\nimport { StatusCodes } from 'http-status-codes'\r\nimport _ from 'lodash'\r\nimport { lookup } from 'mime-types'\r\nimport { join } from 'path'\r\nimport { unescape } from 'querystring'\r\nimport { pathToFileURL } from 'url'\r\nimport { defaultPaths, globPatterns } from './constants'\r\nimport {\r\n encapsulateModule,\r\n getFolderPath,\r\n globFind,\r\n globFindAll,\r\n normalizePath,\r\n} from './path'\r\nimport { sendResponseParser } from './response'\r\n\r\nexport async function tunnel(\r\n { data, config, basePath }: Omit<WorkerProps, 'requestId'>,\r\n endCallback: (state: ResponseState, data: ResponseData) => void,\r\n) {\r\n const context = {\r\n ...data,\r\n params: {},\r\n query: new URLSearchParams(data.query || ''),\r\n } as IntREST.IntRequest\r\n\r\n // Send response if static file found\r\n const staticFolder = await getFolderPath(basePath, globPatterns.staticFolder)\r\n if (staticFolder) {\r\n const staticFile = await globFind(staticFolder, context.path)\r\n if (staticFile) {\r\n const mimeType = lookup(staticFile)\r\n if (mimeType) {\r\n return await sendResponse(\r\n {\r\n status: StatusCodes.OK,\r\n body: createReadStream(staticFile),\r\n headers: {\r\n 'Content-Type': mimeType,\r\n },\r\n },\r\n context.headers,\r\n endCallback,\r\n )\r\n }\r\n }\r\n }\r\n\r\n try {\r\n const identities = await getIdentities(context.path)\r\n\r\n // Send response not found if no route found\r\n if (!identities.length) {\r\n return await sendResponse(\r\n {\r\n status: StatusCodes.NOT_FOUND,\r\n body: {\r\n message: config.messages?.NOT_FOUND || 'Not found',\r\n },\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n },\r\n context.headers,\r\n endCallback,\r\n )\r\n }\r\n\r\n const method = context.method\r\n const basePathCompiled = normalizePath(\r\n join(defaultPaths.compiledFolder, defaultPaths.compiledRoutes),\r\n )\r\n const routes = await Promise.all(\r\n identities.map(async (i) => {\r\n const routeModule = await import(\r\n encapsulateModule(\r\n pathToFileURL(\r\n normalizePath(join(basePathCompiled, i.pathname, 'route.mjs')),\r\n ).toString(),\r\n )\r\n )\r\n return {\r\n handler: (routeModule[method] ||\r\n routeModule['ALL'] ||\r\n routeModule.default) as IntREST.RequestHandler | undefined,\r\n identity: i,\r\n }\r\n }),\r\n )\r\n const route = routes.find((r) => typeof r.handler === 'function')\r\n\r\n // Send response not allowed if method not found in route\r\n if (!route?.handler) {\r\n return await sendResponse(\r\n {\r\n status: StatusCodes.METHOD_NOT_ALLOWED,\r\n body: {\r\n message:\r\n config.messages?.METHOD_NOT_ALLOWED || 'Method not allowed',\r\n },\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n },\r\n context.headers,\r\n endCallback,\r\n )\r\n }\r\n\r\n const paramExtract = route.identity.paramExtract\r\n const pathname = route.identity.pathname\r\n const paramKeys = route.identity.paramKeys\r\n const paramValues = Array.from(data.path.match(paramExtract) || []).slice(1)\r\n context.params = _.zipObject(paramKeys, paramValues.map(unescape))\r\n\r\n const middlewares = await getMiddlewares(pathname)\r\n\r\n const response = await callRecursiveMiddlewares({\r\n middlewares,\r\n context,\r\n config,\r\n requestHandler: route.handler,\r\n })\r\n\r\n if (!response) throw new Error('Response not found')\r\n\r\n return await sendResponse(response, context.headers, endCallback)\r\n } catch (error) {\r\n console.error(error)\r\n if (error instanceof Error) {\r\n return await sendResponse(\r\n {\r\n status: StatusCodes.INTERNAL_SERVER_ERROR,\r\n body: {\r\n message: error.message,\r\n },\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n },\r\n context.headers,\r\n endCallback,\r\n )\r\n } else if (typeof error === 'object' && error != null) {\r\n return await sendResponse(error, context.headers, endCallback)\r\n }\r\n\r\n throw error\r\n }\r\n}\r\n\r\nasync function sendResponse(\r\n resData: IntREST.IntResponse,\r\n reqHeaders: IntREST.IntRequest['headers'],\r\n endCallback: (state: ResponseState, data: ResponseData) => void,\r\n) {\r\n return await sendResponseParser(resData, reqHeaders, 'void', (ev) => {\r\n const { state, data } = ev\r\n endCallback(state, data)\r\n })\r\n}\r\n\r\nasync function getIdentities(route: string) {\r\n const basePath = normalizePath(\r\n join(\r\n process.cwd(),\r\n defaultPaths.compiledFolder,\r\n defaultPaths.compiledRoutes,\r\n ),\r\n )\r\n const identitiesPaths = await globFindAll(\r\n basePath,\r\n globPatterns.identityPoints,\r\n )\r\n const identitiesModules = await Promise.all(\r\n identitiesPaths.map((p) =>\r\n import(encapsulateModule(pathToFileURL(p).toString())).then(\r\n (m) => m as AutoGeneratedVars,\r\n ),\r\n ),\r\n )\r\n return identitiesModules\r\n .filter((m) => m.paramExtract.test(route))\r\n .sort((a, b) => (a.pathname > b.pathname ? 1 : -1))\r\n .sort(sortCompiledRoutes)\r\n}\r\n\r\nasync function getMiddlewares(pathname: string) {\r\n const basePath = normalizePath(\r\n join(\r\n process.cwd(),\r\n defaultPaths.compiledFolder,\r\n defaultPaths.compiledRoutes,\r\n ),\r\n )\r\n const pathnames = pathname\r\n .split('/')\r\n .map((_, i, l) => (i > 0 ? l.slice(0, i + 1).join('/') : '/'))\r\n .map((p) => normalizePath(join(basePath, p, 'middleware.mjs')))\r\n const middlewarePaths = await globFindAll(\r\n basePath,\r\n globPatterns.middlewarePoints,\r\n )\r\n const validMiddlewarePaths = middlewarePaths.filter((p) =>\r\n pathnames.some((pn) => p === pn),\r\n )\r\n\r\n const middlewareModules = await Promise.all(\r\n validMiddlewarePaths.map(async (p) => ({\r\n handler: await import(\r\n encapsulateModule(pathToFileURL(p).toString())\r\n ).then((m) => (m.middleware || m.default) as IntREST.MiddlewareHandler),\r\n pathname: p.replace(basePath, ''),\r\n })),\r\n )\r\n return middlewareModules.filter((m) => typeof m.handler === 'function')\r\n}\r\n\r\nfunction sortCompiledRoutes(a: AutoGeneratedVars, b: AutoGeneratedVars) {\r\n const aSlipt = a.pathname.split('/')\r\n const bSlipt = b.pathname.split('/')\r\n for (let i = 0; i < aSlipt.length; i++) {\r\n if (aSlipt[i][0] === '[' && bSlipt[i]?.[0] === '[') continue\r\n if (aSlipt[i][0] === '[') return 1\r\n if (bSlipt[i]?.[0] === '[') return -1\r\n }\r\n if (b.route.toLowerCase() > a.route.toLowerCase()) return -1\r\n if (b.route.toLowerCase() < a.route.toLowerCase()) return 1\r\n return 0\r\n}\r\n\r\ninterface MiddlewareModule {\r\n handler: IntREST.MiddlewareHandler\r\n pathname: string\r\n}\r\n\r\ninterface CallRecursiveMiddlewaresArgs {\r\n middlewares: MiddlewareModule[]\r\n context: IntREST.IntRequest\r\n config: IntREST.Config\r\n requestHandler: IntREST.RequestHandler\r\n}\r\n\r\nasync function callRecursiveMiddlewares({\r\n middlewares,\r\n context,\r\n config,\r\n requestHandler,\r\n}: CallRecursiveMiddlewaresArgs): Promise<IntREST.IntResponse> {\r\n if (!middlewares.length)\r\n return (\r\n (await requestHandler(context)) || {\r\n status: StatusCodes.NOT_FOUND,\r\n }\r\n )\r\n\r\n const middleware = middlewares[0]\r\n\r\n const timeoutId = setTimeout(() => {\r\n throw new Error(`Middleware handler timeout: ${middleware.pathname}`)\r\n }, config.limits?.middleware?.timeout || 5000)\r\n\r\n const response = await middleware.handler(\r\n context,\r\n async (c?: CustomRequestData) => {\r\n clearTimeout(timeoutId)\r\n context.custom = _.merge(context.custom, c)\r\n return callRecursiveMiddlewares({\r\n middlewares: middlewares.slice(1),\r\n context,\r\n config,\r\n requestHandler,\r\n })\r\n },\r\n )\r\n clearTimeout(timeoutId)\r\n return response\r\n}\r\n", "import { existsSync } from 'fs'\r\nimport { StatusCodes } from 'http-status-codes'\r\nimport _ from 'lodash'\r\nimport { dirname } from 'path'\r\nimport { join } from 'path'\r\nimport rangeParser from 'range-parser'\r\nimport { Readable } from 'stream'\r\nimport { isBuffer } from './compare'\r\nimport { defaultPaths } from './constants'\r\nimport { escapePath, globFindAll, normalizePath } from './path'\r\n\r\nfunction isRange(\r\n range?: rangeParser.Result | rangeParser.Ranges,\r\n): range is rangeParser.Ranges {\r\n return !!(\r\n range &&\r\n Array.isArray(range) &&\r\n range.length &&\r\n range.type === 'bytes'\r\n )\r\n}\r\n\r\n/**\r\n * Send the response data to the client through the worker port\r\n * auto detect the type of response and send the data\r\n */\r\nexport async function sendResponseParser(\r\n res: IntREST.IntResponse,\r\n reqHeaders: IntREST.IntRequest['headers'],\r\n requestId: string,\r\n sendMessage: (msg: TransferResponse) => void,\r\n) {\r\n // Get content length and type from the response headers\r\n const lengthHeaderKey = Object.keys(res.headers || {}).find((k) =>\r\n /^content-length$/i.test(k),\r\n )\r\n const typeHeaderKey = Object.keys(res.headers || {}).find((k) =>\r\n /^content-type$/i.test(k),\r\n )\r\n const contentLength = _.get(\r\n res.headers || {},\r\n lengthHeaderKey || 'Content-Length',\r\n Infinity,\r\n )\r\n const contentType = _.get(res.headers || {}, typeHeaderKey || 'Content-Type')\r\n\r\n // Get the range from the request headers\r\n const range = reqHeaders.range\r\n ? rangeParser(+contentLength, reqHeaders.range, { combine: true })\r\n : undefined\r\n\r\n // If has range in request header, add content range and status code and remove content-length\r\n if (isRange(range)) {\r\n res.status =\r\n !res.status || res.status === StatusCodes.OK\r\n ? StatusCodes.PARTIAL_CONTENT\r\n : res.status\r\n res.headers = {\r\n ...res.headers,\r\n 'Content-Range':\r\n `bytes ${range[0].start}-${range[0].end}` +\r\n (contentLength && isFinite(+contentLength) ? `/${contentLength}` : ''),\r\n }\r\n delete res.headers[lengthHeaderKey || 'Content-Length']\r\n }\r\n\r\n // Check if the response has headers to send to the client\r\n for (const entry of Object.entries(res.headers || {})) {\r\n sendMessage({\r\n requestId,\r\n state: 'set',\r\n data: entry,\r\n })\r\n }\r\n // Check if the response has cookies to send to the client\r\n for (const [key, cookieData] of Object.entries(res.cookies || {})) {\r\n if (Array.isArray(cookieData)) {\r\n for (const cookie of cookieData) {\r\n sendMessage({\r\n requestId,\r\n state: 'cookie',\r\n data: {\r\n name: key,\r\n value: cookie.value,\r\n options: cookie.options,\r\n },\r\n })\r\n }\r\n } else {\r\n sendMessage({\r\n requestId,\r\n state: 'cookie',\r\n data: {\r\n name: key,\r\n value: cookieData.value,\r\n options: cookieData.options,\r\n },\r\n })\r\n }\r\n }\r\n // Check if the response has cookies to clear in the client\r\n for (const [key, cookieData] of Object.entries(res.clearCookies || {})) {\r\n if (Array.isArray(cookieData)) {\r\n for (const cookie of cookieData) {\r\n sendMessage({\r\n requestId,\r\n state: 'clear-cookie',\r\n data: {\r\n name: key,\r\n options: cookie,\r\n },\r\n })\r\n }\r\n } else {\r\n sendMessage({\r\n requestId,\r\n state: 'clear-cookie',\r\n data: {\r\n name: key,\r\n options: cookieData,\r\n },\r\n })\r\n }\r\n }\r\n\r\n // Send the status code to the client\r\n sendMessage({\r\n requestId,\r\n state: 'status',\r\n data: res.status || StatusCodes.OK,\r\n })\r\n\r\n // Check if the response has a body to send to the client\r\n if (res.body) {\r\n // If the response body is a buffer or string\r\n // send the data to the client like a buffer\r\n if (typeof res.body === 'string' || isBuffer(res.body)) {\r\n // If the response has no content type\r\n // set the content type to text/plain\r\n if (!contentType) {\r\n sendMessage({\r\n requestId,\r\n state: 'set',\r\n data: ['Content-Type', 'text/plain'],\r\n })\r\n }\r\n const data = Buffer.from(res.body)\r\n // If the response has a range header\r\n // send only the range of the buffer\r\n if (isRange(range)) {\r\n sendMessage({\r\n requestId,\r\n state: 'write',\r\n data: data.subarray(range[0].start, range[0].end + 1),\r\n })\r\n } else {\r\n sendMessage({\r\n requestId,\r\n state: 'write',\r\n data,\r\n })\r\n }\r\n // If the response body is a readable stream\r\n } else if (res.body instanceof Readable) {\r\n // If the response not has a content type\r\n // set the content type to application/octet-stream\r\n if (!contentType) {\r\n sendMessage({\r\n requestId,\r\n state: 'set',\r\n data: ['Content-Type', 'application/octet-stream'],\r\n })\r\n }\r\n const reader = res.body\r\n if (isRange(range)) {\r\n const start = range[0].start\r\n const end = range[0].end\r\n let readed = 0\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n reader.on('data', (c) => {\r\n const chunk = Buffer.from(c)\r\n if (chunk.length + readed < start) {\r\n readed += chunk.length\r\n return\r\n }\r\n if (readed > end) {\r\n reader.destroy()\r\n return resolve()\r\n }\r\n const offset = Math.max(start - readed, 0)\r\n const length = Math.min(end + 1 - readed, chunk.length)\r\n readed += length\r\n const data = chunk.subarray(offset, offset + length)\r\n sendMessage({\r\n requestId,\r\n state: 'write',\r\n data,\r\n })\r\n })\r\n reader.on('end', () => {\r\n resolve()\r\n })\r\n reader.on('error', (err) => {\r\n reject(err)\r\n })\r\n })\r\n } else {\r\n await new Promise<void>((resolve, reject) => {\r\n reader.on('data', (chunk) => {\r\n sendMessage({\r\n requestId,\r\n state: 'write',\r\n data: chunk,\r\n })\r\n })\r\n reader.on('end', () => {\r\n resolve()\r\n })\r\n reader.on('error', (err) => {\r\n reject(err)\r\n })\r\n })\r\n }\r\n } else {\r\n // If the response body is a object\r\n if (!contentType) {\r\n sendMessage({\r\n requestId,\r\n state: 'set',\r\n data: ['Content-Type', 'application/json'],\r\n })\r\n }\r\n const body = JSON.stringify(res.body)\r\n const data = Buffer.from(body)\r\n if (isRange(range)) {\r\n sendMessage({\r\n requestId,\r\n state: 'write',\r\n data: data.subarray(range[0].start, range[0].end + 1),\r\n })\r\n } else {\r\n sendMessage({\r\n requestId,\r\n state: 'write',\r\n data,\r\n })\r\n }\r\n }\r\n }\r\n\r\n // Send the end of the response to the client\r\n sendMessage({\r\n requestId,\r\n state: 'end',\r\n data: undefined,\r\n })\r\n}\r\n\r\n/**\r\n * Find the middleware pathnames in the compiled directory\r\n */\r\nexport async function findMiddlewarePathnames(\r\n basePath: string,\r\n routeFilePath: string,\r\n) {\r\n // Get the directory of the route file path\r\n // and escape it from root project path\r\n const dir = dirname(\r\n escapePath(\r\n routeFilePath,\r\n normalizePath(\r\n join(\r\n basePath,\r\n defaultPaths.compiledFolder,\r\n defaultPaths.compiledRoutes,\r\n ),\r\n ),\r\n ),\r\n )\r\n // Get all directories from the route file path recursively\r\n const directories = recursiveDirectoryList(dir)\r\n // Create a list of possible middleware paths\r\n const searchList = directories.map((r) =>\r\n normalizePath(\r\n join(\r\n ...[\r\n basePath,\r\n defaultPaths.compiledFolder,\r\n defaultPaths.compiledRoutes,\r\n r,\r\n 'middleware.mjs',\r\n ].filter(Boolean),\r\n ),\r\n ),\r\n )\r\n // Filter the list to get only the existing paths\r\n return searchList.filter((r) => existsSync(r))\r\n}\r\n\r\n/**\r\n * Find all route pathnames in the compiled directory\r\n */\r\nexport async function getAllRoutePathnames(basePath: string) {\r\n return await globFindAll(\r\n basePath,\r\n defaultPaths.compiledFolder,\r\n defaultPaths.compiledRoutes,\r\n '**',\r\n 'route.mjs',\r\n )\r\n}\r\n\r\n/**\r\n * Parse route pathname to route path\r\n */\r\nexport function parseRoutePathname(pathname: string) {\r\n return (\r\n pathname\r\n // Remove groups\r\n .replace(/[\\/\\\\]?\\([A-z\u00C0-\u00FA0-9-_$]+\\)/gi, '')\r\n // Remove filename and extension\r\n .replace(/route\\.(mj|cj|j|t)s$/, '')\r\n // Remove the '\\' of end of the path\r\n .replace(/\\/*$/, '')\r\n // Ensure that starts with `/`\r\n .replace(/^\\/*/, '/')\r\n )\r\n}\r\n\r\nexport function parseRoutePathnameToRegexp(pathname: string, basePath: string) {\r\n const escapedRoute = escapePath(pathname, basePath)\r\n const cleanedRoute = parseRoutePathname(escapedRoute)\r\n\r\n if (/\\[\\.\\.\\.[A-z\u00C0-\u00FA0-9-_$]+\\].+$/.test(cleanedRoute)) {\r\n throw new Error(`Invalid route path: ${escapedRoute}`)\r\n }\r\n\r\n // Compile the route path to a regexp\r\n return {\r\n pathname,\r\n route: cleanedRoute,\r\n vars: Array.from(\r\n cleanedRoute.matchAll(/\\[(?:\\.{3,3})?([A-z\u00C0-\u00FA0-9-_$]+)\\]/g),\r\n ).map((m) => m[1]),\r\n paramRegexp: new RegExp(\r\n cleanedRoute\r\n .replace(/\\[([A-z\u00C0-\u00FA0-9-_$]+)\\]/g, '([A-z\u00C0-\u00FA0-9-_:$.@%]+)')\r\n .replace(/\\[\\.{3,3}([A-z\u00C0-\u00FA0-9-_$]+)\\]/g, '?([A-z\u00C0-\u00FA0-9-_:$%.@/]*)')\r\n .replace(/[\\/\\\\]/, '\\\\/')\r\n .replace(/^\\^*/, '^')\r\n .replace(/\\$*$/, '\\\\/?$'),\r\n ),\r\n }\r\n}\r\n\r\n/**\r\n * Find the route pathnames in the compiled directory\r\n */\r\nexport async function findRoutePathnames(basePath: string, route?: string) {\r\n const routesPathnames = await getAllRoutePathnames(basePath)\r\n const maps = routesPathnames.map((r) =>\r\n parseRoutePathnameToRegexp(\r\n r,\r\n normalizePath(\r\n join(\r\n basePath,\r\n defaultPaths.compiledFolder,\r\n defaultPaths.compiledRoutes,\r\n ),\r\n ),\r\n ),\r\n )\r\n\r\n // Filter the list to get only the match routes if route is defined\r\n if (route) {\r\n return maps\r\n .filter((m) => m.paramRegexp.test(route))\r\n .sort(sortCompiledRoutes)\r\n }\r\n return maps.sort(sortCompiledRoutes)\r\n}\r\n\r\ninterface CompiledRoute {\r\n pathname: string\r\n route: string\r\n vars: string[]\r\n paramRegexp: RegExp\r\n}\r\n\r\n/**\r\n * Sort the compiled routes by priority\r\n */\r\nexport function sortCompiledRoutes(a: CompiledRoute, b: CompiledRoute) {\r\n const aSlipt = a.pathname.split('/')\r\n const bSlipt = b.pathname.split('/')\r\n for (let i = 0; i < aSlipt.length; i++) {\r\n if (aSlipt[i][0] === '[' && bSlipt[i][0] === '[') continue\r\n if (aSlipt[i][0] === '[') return 1\r\n if (bSlipt[i]?.[0] === '[') return -1\r\n }\r\n if (b.route.toLowerCase() > a.route.toLowerCase()) return -1\r\n if (b.route.toLowerCase() < a.route.toLowerCase()) return 1\r\n return b.pathname.length - a.pathname.length\r\n}\r\n\r\n/**\r\n * Get all directories from a path recursively\r\n */\r\nexport function recursiveDirectoryList(path: string) {\r\n const dirs = normalizePath(path)\r\n .split('/')\r\n .map((_, i, arr) => arr.slice(0, i + 1).join('/'))\r\n if (!dirs.includes('')) dirs.unshift('')\r\n return dirs\r\n}\r\n", "import {\n isAnyArrayBuffer,\n isUint16Array,\n isUint32Array,\n isUint8Array,\n} from 'util/types'\n\nexport function isBuffer(data: any): data is Buffer {\n if (isAnyArrayBuffer(data)) return true\n if (isUint8Array(data)) return true\n if (isUint16Array(data)) return true\n if (isUint32Array(data)) return true\n\n return false\n}\n"],
"mappings": "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