cpeak 2.8.0 → 2.9.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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../lib/index.ts","../lib/internal/compression.ts","../lib/internal/mimeTypes.ts","../lib/internal/errors.ts","../lib/internal/router.ts","../lib/utils/parseJSON.ts","../lib/utils/serveStatic.ts","../lib/utils/render.ts","../lib/utils/swagger.ts","../lib/utils/auth.ts","../lib/utils/cookieParser.ts","../lib/utils/cors.ts"],"sourcesContent":["import http from \"node:http\";\nimport fs from \"node:fs/promises\";\nimport { createReadStream } from \"node:fs\";\nimport { pipeline } from \"node:stream/promises\";\n\nimport type net from \"node:net\";\nimport type { Readable } from \"node:stream\";\nimport type { Buffer } from \"node:buffer\";\n\nimport {\n resolveCompressionOptions,\n compressAndSend\n} from \"./internal/compression\";\nimport { MIME_TYPES } from \"./internal/mimeTypes\";\nimport { Router } from \"./internal/router\";\nimport { frameworkError, ErrorCode } from \"./internal/errors\";\n\nexport { frameworkError, ErrorCode };\n\nimport type {\n StringMap,\n CpeakHttpServer,\n CpeakOptions,\n CpeakRequest,\n CpeakResponse,\n Middleware,\n RouteMiddleware,\n Handler\n} from \"./types\";\n\nimport type { ResolvedCompressionConfig } from \"./internal/types\";\n\nexport class CpeakIncomingMessage extends http.IncomingMessage {\n // We define body and params here for better V8 optimization (not changing the shape of the object at runtime)\n public body: any = undefined;\n public params: StringMap = {};\n\n #query?: StringMap;\n\n // Parse the URL parameters (like /users?key1=value1&key2=value2)\n // We will call this query to be more familiar with other node.js frameworks.\n // This is a getter method (accessed like a property)\n get query(): StringMap {\n // This way if a developer writes req.query multiple times, we don't parse it multiple times\n if (this.#query) return this.#query;\n\n const url = this.url || \"\";\n const qIndex = url.indexOf(\"?\");\n\n if (qIndex === -1) {\n this.#query = {};\n } else {\n const searchParams = new URLSearchParams(url.substring(qIndex + 1));\n this.#query = Object.fromEntries(searchParams.entries());\n }\n\n return this.#query;\n }\n}\n\nexport class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {\n // Set per-request from the Cpeak instance. Undefined when compression isn't enabled.\n _compression?: ResolvedCompressionConfig;\n\n // Send a file back to the client\n async sendFile(path: string, mime?: string) {\n if (!mime) {\n const dotIndex = path.lastIndexOf(\".\");\n const fileExtension = dotIndex >= 0 ? path.slice(dotIndex + 1) : \"\";\n mime = MIME_TYPES[fileExtension];\n if (!mime) {\n throw frameworkError(\n `MIME type is missing for \"${path}\". Pass it as the second argument or register the extension via cpeak({ mimeTypes: { ${fileExtension || \"ext\"}: \"...\" } }).`,\n this.sendFile,\n ErrorCode.MISSING_MIME\n );\n }\n }\n\n try {\n const stat = await fs.stat(path);\n if (!stat.isFile()) {\n throw frameworkError(\n `Not a file: ${path}`,\n this.sendFile,\n ErrorCode.NOT_A_FILE\n );\n }\n\n if (this._compression) {\n await compressAndSend(\n this,\n mime,\n createReadStream(path),\n this._compression,\n stat.size\n );\n return;\n }\n\n this.setHeader(\"Content-Type\", mime);\n this.setHeader(\"Content-Length\", String(stat.size));\n\n // Properly propagate stream errors and respect backpressure\n await pipeline(createReadStream(path), this);\n } catch (err: any) {\n if (err?.code === \"ENOENT\") {\n throw frameworkError(\n `File not found: ${path}`,\n this.sendFile,\n ErrorCode.FILE_NOT_FOUND\n );\n }\n\n throw frameworkError(\n `Failed to send file: ${path}`,\n this.sendFile,\n ErrorCode.SEND_FILE_FAIL\n );\n }\n }\n\n // Set the status code of the response\n status(code: number) {\n this.statusCode = code;\n return this;\n }\n\n // Set the Content-Disposition header to prompt the user to download a file\n attachment(filename?: string) {\n const contentDisposition = filename\n ? `attachment; filename=\"${filename}\"`\n : \"attachment\";\n this.setHeader(\"Content-Disposition\", contentDisposition);\n return this;\n }\n\n // Redirects to a new URL\n redirect(location: string) {\n this.writeHead(302, { Location: location });\n this.end();\n }\n\n // Send a json data back to the client.\n // This is only good for bodies that their size is less than the highWaterMark value.\n json(data: any): Promise<void> {\n const body = JSON.stringify(data);\n if (this._compression) {\n return compressAndSend(this, \"application/json\", body, this._compression);\n }\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(body);\n return Promise.resolve();\n }\n\n // Explicit compression entry point. A developer can use this in any custom handler to compress arbitrary responses\n compress(\n mime: string,\n body: Buffer | string | Readable,\n size?: number\n ): Promise<void> {\n if (!this._compression) {\n throw frameworkError(\n \"compression is not enabled. Pass `compression` to cpeak({ compression: true | { ... } }) to use res.compress.\",\n this.compress,\n ErrorCode.COMPRESSION_NOT_ENABLED\n );\n }\n return compressAndSend(this, mime, body, this._compression, size);\n }\n}\n\nexport class Cpeak {\n #server: CpeakHttpServer;\n #router: Router;\n #middleware: Middleware[];\n #handleErr?: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void;\n #compression?: ResolvedCompressionConfig;\n\n constructor(options: CpeakOptions = {}) {\n this.#server = http.createServer({\n IncomingMessage: CpeakIncomingMessage,\n ServerResponse: CpeakServerResponse\n });\n this.#router = new Router();\n this.#middleware = [];\n\n // Resolve compression options once at app startup.\n if (options.compression) {\n this.#compression = resolveCompressionOptions(options.compression);\n }\n\n // Merge developer-supplied mime types with the defaults once at startup\n if (options.mimeTypes) Object.assign(MIME_TYPES, options.mimeTypes);\n\n this.#server.on(\n \"request\",\n async (req: CpeakRequest, res: CpeakResponse) => {\n res._compression = this.#compression;\n\n // Get the url without the URL parameters (query strings)\n const qIndex = req.url?.indexOf(\"?\");\n const urlWithoutQueries =\n qIndex === -1 ? req.url || \"\" : req.url?.substring(0, qIndex);\n\n // Routes every error path through the registered handleErr. Awaits\n // handleErr so its own async work (or a rejecting res.json under\n // compression) is caught. If handleErr itself fails, we log and send a\n // bare 500 so the client never gets a hung socket. Returns a Promise\n // that never rejects to avoid unhandled promise rejections in case of errors in handleErr.\n const dispatchError = async (error: unknown) => {\n if (res.headersSent) {\n req.socket?.destroy();\n return;\n }\n res.setHeader(\"Connection\", \"close\");\n try {\n await this.#handleErr?.(error, req, res);\n } catch (handlerFailure) {\n console.error(\n \"[cpeak] handleErr failed while processing:\",\n error,\n \"\\nReason:\",\n handlerFailure\n );\n if (!res.headersSent) {\n try {\n res.statusCode = 500;\n res.end();\n } catch {}\n }\n }\n };\n\n // Run all the specific middleware functions for that router only and then run the handler\n const runHandler = async (\n req: CpeakRequest,\n res: CpeakResponse,\n middleware: RouteMiddleware[],\n cb: Handler,\n index: number\n ) => {\n // Our exit point...\n if (index === middleware.length) {\n // Call the route handler with the modified req and res objects.\n // Also handle the promise errors by passing them to handleErr to save developers from having to manually wrap every handler in try/catch.\n try {\n await cb(req, res);\n } catch (error) {\n dispatchError(error);\n }\n } else {\n // Handle the promise errors by passing them to handleErr to save developers from having to manually wrap every route middleware in try/catch.\n try {\n await middleware[index](req, res, async (error?: unknown) => {\n // this function only accepts an error argument to be more compatible with NPM modules that are built for express\n if (error) {\n return dispatchError(error);\n }\n await runHandler(req, res, middleware, cb, index + 1);\n });\n } catch (error) {\n dispatchError(error);\n }\n }\n };\n\n // Run all the middleware functions (beforeEach functions) before we run the corresponding route\n const runMiddleware = async (\n req: CpeakRequest,\n res: CpeakResponse,\n middleware: Middleware[],\n index: number\n ) => {\n // Our exit point...\n if (index === middleware.length) {\n const method = req.method?.toLowerCase() || \"\";\n const found = this.#router.find(method, urlWithoutQueries || \"\");\n\n if (found) {\n req.params = found.params;\n return await runHandler(\n req,\n res,\n found.middleware,\n found.handler,\n 0\n );\n }\n\n // If the requested route dose not exist, return 404\n return res\n .status(404)\n .json({ error: `Cannot ${req.method} ${urlWithoutQueries}` });\n } else {\n try {\n await middleware[index](req, res, async (err?: unknown) => {\n if (err) {\n return dispatchError(err);\n }\n await runMiddleware(req, res, middleware, index + 1);\n });\n } catch (error) {\n dispatchError(error);\n }\n }\n };\n\n await runMiddleware(req, res, this.#middleware, 0);\n }\n );\n }\n\n route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]) {\n // The last argument should always be our handler\n const cb = args.pop() as Handler;\n\n if (!cb || typeof cb !== \"function\") {\n throw new Error(\"Route definition must include a handler\");\n }\n\n // Rest will be our middleware functions\n const middleware = args.flat() as RouteMiddleware[];\n\n this.#router.add(method, path, middleware, cb);\n }\n\n beforeEach(cb: Middleware) {\n this.#middleware.push(cb);\n }\n\n handleErr(cb: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void) {\n this.#handleErr = cb;\n }\n\n // The first 3 listens are just TS overloads for better type inference and editor autocompletion. The last one is the actual implementation.\n listen(port: number, cb?: () => void): CpeakHttpServer;\n listen(port: number, host: string, cb?: () => void): CpeakHttpServer;\n listen(options: net.ListenOptions, cb?: () => void): CpeakHttpServer;\n listen(...args: any[]) {\n return this.#server.listen(...args);\n }\n\n address() {\n return this.#server.address();\n }\n\n close(cb?: (err?: Error) => void) {\n return this.#server.close(cb);\n }\n\n // A getter for developers who want to access the underlying http server instance for advanced use cases that aren't covered by Cpeak\n get server() {\n return this.#server;\n }\n}\n\n// Util functions\nexport {\n serveStatic,\n parseJSON,\n render,\n swagger,\n auth,\n hashPassword,\n verifyPassword,\n cookieParser,\n cors\n} from \"./utils\";\n\nexport type {\n AuthOptions,\n PbkdfOptions,\n CookieOptions,\n CorsOptions\n} from \"./utils/types\";\n\nexport type { CompressionOptions } from \"./internal/types\";\n\nexport type {\n CpeakHttpServer,\n CpeakOptions,\n CpeakRequest,\n CpeakResponse,\n Next,\n Middleware,\n RouteMiddleware,\n Handler\n} from \"./types\";\n\nexport default function cpeak(options?: CpeakOptions): Cpeak {\n return new Cpeak(options);\n}\n","import zlib from \"node:zlib\";\nimport { Readable } from \"node:stream\";\nimport { Buffer } from \"node:buffer\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { Transform } from \"node:stream\";\nimport type { ServerResponse } from \"node:http\";\nimport type { CompressionOptions, ResolvedCompressionConfig } from \"./types\";\n\ntype Encoding = \"br\" | \"gzip\" | \"deflate\";\n\nconst COMPRESSIBLE_TYPE = /text|json|javascript|css|xml|svg/i;\nconst NO_TRANSFORM = /(?:^|,)\\s*no-transform\\s*(?:,|$)/i;\n\n// Parse Accept-Encoding and pick a compression algorithm the server supports.\n// Handles q=0 to disable an algorithm. Cpeak preference is fixed: br > gzip > deflate.\nfunction pickEncoding(header: string): Encoding | null {\n if (!header) return null;\n\n const accepted: Record<string, number> = {};\n let wildcard: number | undefined;\n\n for (const part of header.split(\",\")) {\n const [rawName, ...params] = part.trim().split(\";\");\n const name = rawName.trim().toLowerCase();\n if (!name) continue;\n\n let q = 1;\n for (const p of params) {\n const m = p.trim().match(/^q=([\\d.]+)$/i);\n if (m) q = Number(m[1]);\n }\n if (Number.isNaN(q)) q = 0;\n\n if (name === \"*\") wildcard = q;\n else accepted[name] = q;\n }\n\n const tryPick = (enc: Encoding): boolean => {\n const q = enc in accepted ? accepted[enc] : wildcard;\n return q !== undefined && q > 0;\n };\n\n if (tryPick(\"br\")) return \"br\";\n if (tryPick(\"gzip\")) return \"gzip\";\n if (tryPick(\"deflate\")) return \"deflate\";\n return null;\n}\n\n// Handling the Vary HTTP header\nfunction appendVary(res: ServerResponse, value: string) {\n const existing = res.getHeader(\"Vary\");\n if (!existing) return res.setHeader(\"Vary\", value);\n const current = String(existing)\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n if (\n current.includes(\"*\") ||\n current.some((v) => v.toLowerCase() === value.toLowerCase())\n )\n return;\n res.setHeader(\"Vary\", [...current, value].join(\", \"));\n}\n\n// Brotli options. Zlib uses 11 (max), which is really slow for live\n// responses. We go with 4 unless the developer specifies otherwise.\nfunction brotliOptsFor(config: ResolvedCompressionConfig): zlib.BrotliOptions {\n const userBrotli = config.brotli || {};\n return {\n ...userBrotli,\n params: {\n [zlib.constants.BROTLI_PARAM_QUALITY]: 4,\n ...(userBrotli.params || {})\n }\n };\n}\n\nfunction createCompressorStream(\n encoding: Encoding,\n config: ResolvedCompressionConfig\n): Transform {\n if (encoding === \"br\")\n return zlib.createBrotliCompress(brotliOptsFor(config));\n if (encoding === \"gzip\") return zlib.createGzip(config.gzip);\n return zlib.createDeflate(config.deflate);\n}\n\n// Decides what to do with this response\nfunction negotiate(\n res: ServerResponse,\n mime: string,\n size: number,\n config: ResolvedCompressionConfig\n): { encoding: Encoding | null; eligible: boolean } {\n // Whether this content type is worth trying to compress at all.\n // Some types are already compressed and don't compress well.\n if (!COMPRESSIBLE_TYPE.test(mime)) return { encoding: null, eligible: false };\n\n if (res.req?.method === \"HEAD\") return { encoding: null, eligible: false };\n\n // RFC specification: don't transform responses that ask not to be transformed.\n const cc = res.getHeader(\"Cache-Control\");\n if (cc && NO_TRANSFORM.test(String(cc)))\n return { encoding: null, eligible: false };\n\n const existing = res.getHeader(\"Content-Encoding\");\n if (existing && existing !== \"identity\")\n return { encoding: null, eligible: false };\n\n if (size < config.threshold) return { encoding: null, eligible: true };\n\n const encoding = pickEncoding(\n String(res.req?.headers[\"accept-encoding\"] || \"\")\n );\n return { encoding, eligible: true };\n}\n\n// Converts into a Readable stream\nfunction bodyAsReadable(body: Buffer | string | Readable): Readable {\n if (Buffer.isBuffer(body)) return Readable.from([body]);\n if (typeof body === \"string\") return Readable.from([Buffer.from(body)]);\n return body;\n}\n\n// Resolves compression options (or 'true' for defaults) into a\n// complete config. Called once at Cpeak construction.\nexport function resolveCompressionOptions(\n input: true | CompressionOptions\n): ResolvedCompressionConfig {\n const options: CompressionOptions = input === true ? {} : input;\n return {\n threshold: options.threshold ?? 1024,\n brotli: options.brotli ?? {},\n gzip: options.gzip ?? {},\n deflate: options.deflate ?? {}\n };\n}\n\n// The final point used by res.compress, res.json, res.sendFile and res.render\n// when compression is enabled by the developer.\n//\n// Compression always goes through createGzip/createBrotliCompress/createDeflate\n// streams which are async and run on libuv's thread pool.\nexport async function compressAndSend(\n res: ServerResponse,\n mime: string,\n body: Buffer | string | Readable,\n config: ResolvedCompressionConfig,\n size?: number\n): Promise<void> {\n res.setHeader(\"Content-Type\", mime);\n\n const knownSize: number = Buffer.isBuffer(body)\n ? body.length\n : typeof body === \"string\"\n ? Buffer.byteLength(body)\n : (size ?? Infinity);\n\n const { encoding, eligible } = negotiate(res, mime, knownSize, config);\n\n if (!encoding) {\n if (eligible) appendVary(res, \"Accept-Encoding\");\n if (Buffer.isBuffer(body) || typeof body === \"string\") {\n res.setHeader(\"Content-Length\", String(knownSize));\n res.end(body);\n return;\n }\n if (size !== undefined) res.setHeader(\"Content-Length\", String(size));\n await pipeline(body, res);\n return;\n }\n\n res.setHeader(\"Content-Encoding\", encoding);\n appendVary(res, \"Accept-Encoding\");\n await pipeline(\n bodyAsReadable(body),\n createCompressorStream(encoding, config),\n res\n );\n}\n","import type { StringMap } from \"../types\";\n\n// Developers can expand this if needed in the cpeak() constructor\nexport const MIME_TYPES: StringMap = {\n html: \"text/html\",\n css: \"text/css\",\n js: \"application/javascript\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n svg: \"image/svg+xml\",\n txt: \"text/plain\",\n eot: \"application/vnd.ms-fontobject\",\n otf: \"font/otf\",\n ttf: \"font/ttf\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n json: \"application/json\",\n webmanifest: \"application/manifest+json\"\n};","// A utility function to create an error with a custom stack trace\nexport function frameworkError(\n message: string,\n skipFn: Function,\n code?: string,\n status?: number\n) {\n const err = new Error(message) as Error & {\n code?: string;\n cpeak_err?: boolean;\n };\n Error.captureStackTrace(err, skipFn);\n\n err.cpeak_err = true;\n\n if (code) err.code = code;\n if (status) (err as any).status = status;\n\n return err;\n}\n\nexport enum ErrorCode {\n MISSING_MIME = \"CPEAK_ERR_MISSING_MIME\",\n FILE_NOT_FOUND = \"CPEAK_ERR_FILE_NOT_FOUND\",\n NOT_A_FILE = \"CPEAK_ERR_NOT_A_FILE\",\n SEND_FILE_FAIL = \"CPEAK_ERR_SEND_FILE_FAIL\",\n INVALID_JSON = \"CPEAK_ERR_INVALID_JSON\",\n PAYLOAD_TOO_LARGE = \"CPEAK_ERR_PAYLOAD_TOO_LARGE\",\n WEAK_SECRET = \"CPEAK_ERR_WEAK_SECRET\",\n COMPRESSION_NOT_ENABLED = \"CPEAK_ERR_COMPRESSION_NOT_ENABLED\",\n // For router:\n DUPLICATE_ROUTE = \"CPEAK_ERR_DUPLICATE_ROUTE\",\n INVALID_ROUTE = \"CPEAK_ERR_INVALID_ROUTE\"\n}\n","import type { Handler, RouteMiddleware, StringMap } from \"../types\";\nimport { frameworkError, ErrorCode } from \"./errors\";\n\n// A node in our radix tree. Each one can hold up to three kinds of children:\n// an exact static segment, a single \":param\" placeholder, or a tail \"*\"\n// wildcard. The handler and middleware here belong to the route whose path\n// ends at this node, if any.\n//\n// Param names are not stored on the tree edges. We capture values positionally\n// as we walk, and zip them with the param names attached to whichever leaf we\n// land on. That lets two routes share the same param slot in the tree even\n// when they use different names, like \"/:id/profile\" and \"/:username/settings\".\ninterface RadixNode {\n staticChildren: Map<string, RadixNode>;\n paramChild?: RadixNode;\n wildcardChild?: WildcardLeaf;\n handler?: Handler;\n middleware?: RouteMiddleware[];\n // Names of params captured along the path to this leaf, in order. Only set\n // on nodes that own a handler.\n paramNames?: string[];\n}\n\ninterface WildcardLeaf {\n handler: Handler;\n middleware: RouteMiddleware[];\n // Names of params captured before reaching this wildcard, in order.\n paramNames: string[];\n}\n\nexport interface RouteMatch {\n middleware: RouteMiddleware[];\n handler: Handler;\n params: StringMap;\n}\n\nfunction createNode(): RadixNode {\n return { staticChildren: new Map() };\n}\n\n// We keep one radix tree per HTTP method so different methods can safely\n// share a path shape. POST /comments/:pageId and PUT /comments/:id can\n// coexist without conflict because they live in separate trees.\nexport class Router {\n #treesByMethod: Map<string, RadixNode> = new Map();\n\n add(\n method: string,\n path: string,\n middleware: RouteMiddleware[],\n handler: Handler\n ) {\n const methodKey = method.toLowerCase();\n let root = this.#treesByMethod.get(methodKey);\n if (!root) {\n root = createNode();\n this.#treesByMethod.set(methodKey, root);\n }\n\n const segments = splitPath(path);\n const paramNames: string[] = [];\n let currentNode = root;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n const isLastSegment = i === segments.length - 1;\n\n // Named wildcards like \"*name\" are not a thing here. Only a bare \"*\"\n // is allowed, and only as the very last segment.\n if (segment.length > 1 && segment.startsWith(\"*\")) {\n throw frameworkError(\n `Invalid route \"${path}\": named wildcards (e.g. \"*name\") are not supported. Use a plain \"*\" at the end of the path.`,\n this.add,\n ErrorCode.INVALID_ROUTE\n );\n }\n\n // A \"*\" segment installs a tail wildcard on the current node. After\n // that there's nothing more to walk, so we register and bail out.\n if (segment === \"*\") {\n if (!isLastSegment) {\n throw frameworkError(\n `Invalid route \"${path}\": \"*\" is only allowed as the final path segment.`,\n this.add,\n ErrorCode.INVALID_ROUTE\n );\n }\n if (currentNode.wildcardChild) {\n throw frameworkError(\n `Duplicate route: ${method.toUpperCase()} ${path}`,\n this.add,\n ErrorCode.DUPLICATE_ROUTE\n );\n }\n currentNode.wildcardChild = { handler, middleware, paramNames };\n return;\n }\n\n // A \":name\" segment walks into the param branch at this depth, or\n // creates one. The name is collected positionally and resolved later\n // at the leaf, so two routes can disagree on the param name here as\n // long as their paths diverge before the leaf.\n if (segment.startsWith(\":\")) {\n const paramName = segment.slice(1);\n if (!paramName) {\n throw frameworkError(\n `Invalid route \"${path}\": empty parameter name.`,\n this.add,\n ErrorCode.INVALID_ROUTE\n );\n }\n paramNames.push(paramName);\n if (!currentNode.paramChild) {\n currentNode.paramChild = createNode();\n }\n currentNode = currentNode.paramChild;\n continue;\n }\n\n // Plain static segment. Walk into the existing child or create a new one.\n let staticChild = currentNode.staticChildren.get(segment);\n if (!staticChild) {\n staticChild = createNode();\n currentNode.staticChildren.set(segment, staticChild);\n }\n currentNode = staticChild;\n }\n\n // We have consumed every segment of the path. The terminal node is where\n // the handler gets attached. If something is already attached here, the\n // user registered this exact path twice.\n if (currentNode.handler) {\n throw frameworkError(\n `Duplicate route: ${method.toUpperCase()} ${path}`,\n this.add,\n ErrorCode.DUPLICATE_ROUTE\n );\n }\n currentNode.handler = handler;\n currentNode.middleware = middleware;\n currentNode.paramNames = paramNames;\n }\n\n find(method: string, path: string): RouteMatch | null {\n const root = this.#treesByMethod.get(method.toLowerCase());\n if (!root) return null;\n\n const segments = splitPath(path);\n return matchSegments(root, segments, 0, []);\n }\n}\n\n// Walk the tree one segment at a time, always trying static before param\n// before wildcard. That ordering is where our precedence rules come from:\n// static beats param beats wildcard. Because each branch is tried in turn\n// and recursion lets us unwind a failed path, the matcher also backtracks.\n// If the static branch dead-ends deeper down, we come back up and try the\n// param sibling with the same segment value.\n//\n// We collect captured param values positionally as we walk. The actual names\n// get zipped in at the terminal leaf, using the paramNames stored alongside\n// the handler. That way the same captured value can be called \"id\" on one\n// route and \"username\" on another without the tree caring.\nfunction matchSegments(\n node: RadixNode,\n segments: string[],\n segmentIndex: number,\n capturedValues: string[]\n): RouteMatch | null {\n // Out of segments to walk. If this node has a handler, that's our match.\n // Otherwise let a wildcard at this depth catch the empty remainder so\n // routes like \"/foo/*\" still match a request to \"/foo\".\n if (segmentIndex === segments.length) {\n if (node.handler) {\n return {\n middleware: node.middleware!,\n handler: node.handler,\n params: zipParams(node.paramNames!, capturedValues)\n };\n }\n if (node.wildcardChild) {\n return {\n middleware: node.wildcardChild.middleware,\n handler: node.wildcardChild.handler,\n params: zipParams(node.wildcardChild.paramNames, capturedValues)\n };\n }\n return null;\n }\n\n const segment = segments[segmentIndex];\n\n // Try the exact static child first. Exact matches always win.\n const staticChild = node.staticChildren.get(segment);\n if (staticChild) {\n const foundMatch = matchSegments(\n staticChild,\n segments,\n segmentIndex + 1,\n capturedValues\n );\n if (foundMatch) return foundMatch;\n }\n\n // Then try the param branch. We push the captured value before recursing\n // and pop it back off if the recursion fails, so any sibling branch (or the\n // caller unwinding above us) sees a clean capture list.\n if (node.paramChild) {\n capturedValues.push(safeDecode(segment));\n const foundMatch = matchSegments(\n node.paramChild,\n segments,\n segmentIndex + 1,\n capturedValues\n );\n if (foundMatch) return foundMatch;\n capturedValues.pop();\n }\n\n // Last resort. A wildcard at this node swallows whatever segments remain.\n if (node.wildcardChild) {\n return {\n middleware: node.wildcardChild.middleware,\n handler: node.wildcardChild.handler,\n params: zipParams(node.wildcardChild.paramNames, capturedValues)\n };\n }\n\n return null;\n}\n\nfunction zipParams(names: string[], values: string[]): StringMap {\n const params: StringMap = {};\n for (let i = 0; i < names.length; i++) {\n params[names[i]] = values[i];\n }\n return params;\n}\n\n// Decode a URL segment without ever throwing. Malformed percent encoding is\n// rare but it does happen in the wild. Falling back to the raw segment keeps\n// the request matchable instead of blowing up before the handler runs.\n// Example: safeDecode(\"a%20b%2Fc\") returns \"a b/c\", while safeDecode(\"a%ZZb\") returns \"a%ZZb\".\nfunction safeDecode(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n// Split a URL path into segments with no leading slash. We treat \"\" and \"/\"\n// the same way: zero segments, meaning the root of the tree.\n// Example: \"/a/b/c\" becomes [\"a\", \"b\", \"c\"]\nfunction splitPath(path: string): string[] {\n if (path === \"\" || path === \"/\") return [];\n const withoutLeadingSlash = path.startsWith(\"/\") ? path.slice(1) : path;\n return withoutLeadingSlash.split(\"/\");\n}\n","import type { CpeakRequest, CpeakResponse, Next } from \"../types\";\nimport { Buffer } from \"node:buffer\";\nimport { frameworkError, ErrorCode } from \"../index\";\n\n// Check if Content-Type is JSON\nfunction isJSON(contentType: string | undefined) {\n if (!contentType) return false;\n if (contentType === \"application/json\") return true;\n return (\n contentType.startsWith(\"application/json\") || contentType.includes(\"+json\")\n );\n}\n\n// Parsing JSON\nconst parseJSON = (options: { limit?: number } = {}) => {\n // Default limit to 1MB\n const limit = options.limit || 1024 * 1024;\n\n return (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n if (!isJSON(req.headers[\"content-type\"])) return next();\n\n const chunks: Buffer[] = [];\n let bytesReceived = 0;\n\n const onData = (chunk: Buffer) => {\n bytesReceived += chunk.length;\n\n // To prevent Denial of Service (DoS) attacks, enforce a maximum body size\n if (bytesReceived > limit) {\n // Stop listening to data\n req.pause();\n\n // Remove listeners so we don't trigger 'end' or more 'data'\n req.removeListener(\"data\", onData);\n req.removeListener(\"end\", onEnd);\n\n next(\n frameworkError(\n \"JSON body too large\",\n onData,\n ErrorCode.PAYLOAD_TOO_LARGE,\n 413 // HTTP 413 Payload Too Large\n )\n );\n\n return;\n }\n\n chunks.push(chunk);\n };\n\n const onEnd = () => {\n try {\n // For better performance, we concat buffers once, then convert to string\n // Optimization: If only one chunk exists, avoid the memory copy of concat\n const rawBody =\n chunks.length === 1\n ? chunks[0].toString(\"utf-8\")\n : Buffer.concat(chunks).toString(\"utf-8\");\n\n // Handle empty body case\n req.body = rawBody ? JSON.parse(rawBody) : {};\n\n next();\n } catch (err) {\n // Handle Invalid JSON without crashing\n next(\n frameworkError(\n \"Invalid JSON format\",\n onEnd,\n ErrorCode.INVALID_JSON,\n 400 // HTTP 400 Bad Request\n )\n );\n }\n };\n\n req.on(\"data\", onData);\n req.on(\"end\", onEnd);\n };\n};\n\nexport { parseJSON };\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { MIME_TYPES } from \"../internal/mimeTypes\";\nimport type { CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nconst serveStatic = (\n folderPath: string,\n options?: { prefix?: string; live?: boolean }\n) => {\n const prefix = options?.prefix ?? \"\";\n const live = options?.live ?? false;\n\n // This process the folder on every request, which is useful during development when files are changing often.\n // In production, it's better to process the folder once and store the file paths in memory for faster access if file names are not changing often.\n // If file names dynamically change often in production, then live option can be set to true to process the folder on every request, but it may have performance implications.\n if (live) {\n const resolvedFolder = path.resolve(folderPath);\n\n return async function (req: CpeakRequest, res: CpeakResponse, next: Next) {\n const url = req.url;\n if (typeof url !== \"string\") return next();\n\n const pathname = url.split(\"?\")[0];\n const unprefixed = prefix ? pathname.slice(prefix.length) : pathname;\n const filePath = path.join(resolvedFolder, unprefixed);\n const fileExtension = path.extname(filePath).slice(1);\n const mime = MIME_TYPES[fileExtension];\n\n if (!mime || !filePath.startsWith(resolvedFolder)) return next();\n\n const stat = await fs.promises.stat(filePath).catch(() => null);\n if (stat?.isFile()) return res.sendFile(filePath, mime);\n\n next();\n };\n }\n\n function processFolder(folderPath: string, parentFolder: string) {\n const staticFiles: string[] = [];\n\n // Read the contents of the folder\n const files = fs.readdirSync(folderPath);\n\n // Loop through the files and subfolders\n for (const file of files) {\n const fullPath = path.join(folderPath, file);\n\n // Check if it's a directory\n if (fs.statSync(fullPath).isDirectory()) {\n // If it's a directory, recursively process it\n const subfolderFiles = processFolder(fullPath, parentFolder);\n staticFiles.push(...subfolderFiles);\n } else {\n // If it's a file, add it to the array\n const relativePath = path.relative(parentFolder, fullPath);\n const fileExtension = path.extname(file).slice(1);\n if (MIME_TYPES[fileExtension]) staticFiles.push(\"/\" + relativePath);\n }\n }\n\n return staticFiles;\n }\n\n const filesArrayToFilesMap = (filesArray: string[]) => {\n const filesMap: Record<string, { path: string; mime: string }> = {};\n for (const file of filesArray) {\n const fileExtension = path.extname(file).slice(1);\n filesMap[prefix + file] = {\n path: folderPath + file,\n mime: MIME_TYPES[fileExtension]\n };\n }\n return filesMap;\n };\n\n // Start processing the folder\n const filesMap = filesArrayToFilesMap(processFolder(folderPath, folderPath));\n\n return function (req: CpeakRequest, res: CpeakResponse, next: Next) {\n const url = req.url;\n if (typeof url !== \"string\") return next();\n\n const pathname = url.split(\"?\")[0];\n if (Object.prototype.hasOwnProperty.call(filesMap, pathname)) {\n const fileRoute = filesMap[pathname];\n return res.sendFile(fileRoute.path, fileRoute.mime);\n }\n\n next();\n };\n};\n\nexport { serveStatic };\n","import fs from \"node:fs/promises\";\nimport { frameworkError } from \"../\";\nimport { compressAndSend } from \"../internal/compression\";\nimport { MIME_TYPES } from \"../internal/mimeTypes\";\nimport type { CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nfunction renderTemplate(\n templateStr: string,\n data: Record<string, unknown>\n): string {\n // Initialize variables\n let result: (string | unknown)[] = [];\n\n let currentIndex = 0;\n\n while (currentIndex < templateStr.length) {\n // Find the next opening placeholder\n const startIdx = templateStr.indexOf(\"{{\", currentIndex);\n if (startIdx === -1) {\n // No more placeholders, push the remaining string\n result.push(templateStr.slice(currentIndex));\n break;\n }\n\n // Push the part before the placeholder\n result.push(templateStr.slice(currentIndex, startIdx));\n\n // Find the closing placeholder\n const endIdx = templateStr.indexOf(\"}}\", startIdx);\n if (endIdx === -1) {\n // No closing brace found, treat the rest as plain text\n result.push(templateStr.slice(startIdx));\n break;\n }\n\n // Extract the variable name\n const varName = templateStr.slice(startIdx + 2, endIdx).trim();\n\n // Replace the variable with its value from the data, or use an empty string if not found\n const replacement = data[varName] !== undefined ? data[varName] : \"\";\n\n // Push the replacement to the result array\n result.push(replacement);\n\n // Move the index past the current closing placeholder\n currentIndex = endIdx + 2;\n }\n\n // Join all parts into a final string\n return result.join(\"\");\n}\n\n// Errors to return: recommend to not render files larger than 100KB\n// To Explore: Doing the operation in C++ and return the data as stream back to the client\n// @TODO: remove the file from static map\n// @TODO: escape the string to prevent XSS\n// @TODO: add another {{{ }}} option to not escape the string\nconst render = () => {\n return function (req: CpeakRequest, res: CpeakResponse, next: Next): void {\n res.render = async (\n path: string,\n data: Record<string, unknown>,\n mime?: string\n ) => {\n if (!mime) {\n const dotIndex = path.lastIndexOf(\".\");\n const fileExtension = dotIndex >= 0 ? path.slice(dotIndex + 1) : \"\";\n mime = MIME_TYPES[fileExtension];\n if (!mime) {\n throw frameworkError(\n `MIME type is missing for \"${path}\". Pass it as the third argument or register the extension via cpeak({ mimeTypes: { ${fileExtension || \"ext\"}: \"...\" } }).`,\n res.render\n );\n }\n }\n\n let fileStr = await fs.readFile(path, \"utf-8\");\n const finalStr = renderTemplate(fileStr, data);\n\n if (res._compression) {\n await compressAndSend(res, mime, finalStr, res._compression);\n return;\n }\n\n res.setHeader(\"Content-Type\", mime);\n res.end(finalStr);\n };\n\n next();\n };\n};\n\nexport { render };\n","import type { CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nconst swagger = (spec: object, prefix = \"/api-docs\") => {\n const initializerJs = `window.onload = function() {\n SwaggerUIBundle({\n url: \"${prefix}/spec.json\",\n dom_id: '#swagger-ui',\n presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],\n layout: \"StandaloneLayout\"\n });\n};`;\n\n return (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n if (req.url === prefix || req.url === `${prefix}/`) {\n res.writeHead(302, { Location: `${prefix}/index.html` });\n res.end();\n return;\n }\n if (req.url === `${prefix}/spec.json`) {\n return res.json(spec);\n }\n if (req.url === `${prefix}/swagger-initializer.js`) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(initializerJs);\n return;\n }\n next();\n };\n};\n\nexport { swagger };\n","import { randomBytes, pbkdf2, createHmac, timingSafeEqual } from \"node:crypto\";\nimport { promisify } from \"node:util\";\nimport type { Middleware } from \"../types\";\nimport { frameworkError, ErrorCode } from \"../index\";\nimport type { AuthOptions, PbkdfOptions } from \"./types\";\n\nconst pbkdf2Async = promisify(pbkdf2);\n\nconst DEFAULTS = {\n iterations: 210_000,\n keylen: 64,\n digest: \"sha512\",\n saltSize: 32,\n hmacAlgorithm: \"sha256\",\n tokenIdSize: 20,\n tokenExpiry: 7 * 24 * 60 * 60 * 1000 // 7 days in ms\n} as const;\n\nexport async function hashPassword(\n password: string,\n options?: PbkdfOptions\n): Promise<string> {\n const iterations = options?.iterations ?? DEFAULTS.iterations;\n const keylen = options?.keylen ?? DEFAULTS.keylen;\n const digest = options?.digest ?? DEFAULTS.digest;\n const saltSize = options?.saltSize ?? DEFAULTS.saltSize;\n const salt = randomBytes(saltSize);\n const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);\n return `pbkdf2:${iterations}:${keylen}:${digest}:${salt.toString(\"hex\")}:${hash.toString(\"hex\")}`;\n}\n\nexport async function verifyPassword(\n password: string,\n stored: string\n): Promise<boolean> {\n // When argon2 is added, dispatch on the prefix here.\n const withoutPrefix = stored.slice(stored.indexOf(\":\") + 1);\n const parts = withoutPrefix.split(\":\");\n if (parts.length !== 5) return false;\n const [itersStr, keylenStr, digest, saltHex, hashHex] = parts;\n const iterations = parseInt(itersStr, 10);\n const keylen = parseInt(keylenStr, 10);\n if (!digest || !saltHex || !hashHex || isNaN(iterations) || isNaN(keylen))\n return false;\n const salt = Buffer.from(saltHex, \"hex\");\n const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);\n const storedHash = Buffer.from(hashHex, \"hex\");\n if (storedHash.length !== hash.length) return false;\n return timingSafeEqual(hash, storedHash);\n}\n\nfunction signToken(tokenId: string, secret: string, algorithm: string): string {\n const sig = createHmac(algorithm, secret).update(tokenId).digest(\"hex\");\n return `${tokenId}.${sig}`;\n}\n\nfunction extractTokenId(\n token: string,\n secret: string,\n algorithm: string\n): string | null {\n const dot = token.indexOf(\".\");\n if (dot === -1) return null;\n const tokenId = token.slice(0, dot);\n const sig = token.slice(dot + 1);\n const expected = createHmac(algorithm, secret).update(tokenId).digest(\"hex\");\n const expectedBuf = Buffer.from(expected, \"hex\");\n const actualBuf = Buffer.from(sig, \"hex\");\n if (expectedBuf.length !== actualBuf.length) return null;\n if (!timingSafeEqual(expectedBuf, actualBuf)) return null;\n return tokenId;\n}\n\nexport function auth(options: AuthOptions): Middleware {\n if (!options.secret || options.secret.length < 32) {\n throw frameworkError(\n \"Secret must be at least 32 characters. HMAC security is only as strong as the key.\",\n auth,\n ErrorCode.WEAK_SECRET\n );\n }\n\n const {\n secret,\n saveToken,\n findToken,\n revokeToken,\n tokenExpiry = DEFAULTS.tokenExpiry,\n hmacAlgorithm = DEFAULTS.hmacAlgorithm,\n tokenIdSize = DEFAULTS.tokenIdSize\n } = options;\n\n const pbkdfOpts: PbkdfOptions = {\n iterations: options.iterations,\n keylen: options.keylen,\n digest: options.digest,\n saltSize: options.saltSize\n };\n\n const _hashPassword = ({ password }: { password: string }) =>\n hashPassword(password, pbkdfOpts);\n\n const login = async ({\n password,\n hashedPassword,\n userId\n }: {\n password: string;\n hashedPassword: string;\n userId: string;\n }): Promise<string | null> => {\n const isMatch = await verifyPassword(password, hashedPassword);\n if (!isMatch) return null;\n const tokenId = randomBytes(tokenIdSize).toString(\"hex\");\n const token = signToken(tokenId, secret, hmacAlgorithm);\n await saveToken(tokenId, userId, new Date(Date.now() + tokenExpiry));\n return token;\n };\n\n const verifyToken = async (\n token: string\n ): Promise<{ userId: string } | null> => {\n if (!token) return null;\n const tokenId = extractTokenId(token, secret, hmacAlgorithm);\n if (!tokenId) return null;\n const record = await findToken(tokenId);\n if (!record) return null;\n if (new Date(record.expiresAt) < new Date()) return null;\n return { userId: record.userId };\n };\n\n const logout = revokeToken\n ? async (token: string): Promise<boolean> => {\n const tokenId = extractTokenId(token, secret, hmacAlgorithm);\n if (!tokenId) return false;\n await revokeToken(tokenId);\n return true;\n }\n : undefined;\n\n return (req, _res, next) => {\n req.hashPassword = _hashPassword;\n req.login = login;\n req.verifyToken = verifyToken;\n if (logout) req.logout = logout;\n next();\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { CpeakRequest, CpeakResponse, Next } from \"../types\";\nimport { frameworkError, ErrorCode } from \"../index\";\nimport type { CookieOptions } from \"./types\";\n\n// This will sign the cookie value with HMAC with the secret.\n// Ideal for data like user IDs or session IDs, where you want to ensure the integrity of the cookie value without encryption.\n// So this way, imagine you save a user ID in the cookie. By signing it, you can detect if the client has tampered with the cookie value\n// (e.g., changing the user ID to impersonate another user).\n// However, since it's not encrypted, the actual user ID is still visible to the client.\n// This is a common approach for session cookies where you want to prevent tampering but don't mind if the value is visible.\nfunction sign(value: string, secret: string): string {\n const sig = createHmac(\"sha256\", secret).update(value).digest(\"base64url\");\n return `s:${value}.${sig}`;\n}\n\nfunction unsign(signed: string, secret: string): string | false {\n if (!signed.startsWith(\"s:\")) return false;\n const withoutPrefix = signed.slice(2);\n const lastDot = withoutPrefix.lastIndexOf(\".\");\n if (lastDot === -1) return false;\n const value = withoutPrefix.slice(0, lastDot);\n const sig = withoutPrefix.slice(lastDot + 1);\n const expected = createHmac(\"sha256\", secret)\n .update(value)\n .digest(\"base64url\");\n const expectedBuf = Buffer.from(expected);\n const actualBuf = Buffer.from(sig);\n if (expectedBuf.length !== actualBuf.length) return false;\n if (!timingSafeEqual(expectedBuf, actualBuf)) return false;\n return value;\n}\n\n// Parses the raw value of an HTTP `Cookie` request header into a name->value\n// This should be compatible with the RFC 6265 HTTP specification\nfunction parseRawCookies(header: string): Record<string, string> {\n // Use a null-prototype object to prevent prototype pollution attacks when assigning cookie names like \"__proto__\" or \"constructor\".\n const cookies: Record<string, string> = Object.create(null);\n if (!header) return cookies;\n\n const pairs = header.split(\";\");\n\n for (let i = 0; i < pairs.length; i++) {\n const pair = pairs[i];\n const equalSignIndex = pair.indexOf(\"=\");\n\n // RFC 6265: cookie-pair requires '='. Pairs without one (e.g. a\n // bare flag like `Cookie: foo`) are not valid cookie-pairs and we skip them.\n // Note we use the FIRST '=' only. So values like base64 padding (`token=YWJjPT0=`) must keep trailing '='s.\n if (equalSignIndex === -1) continue;\n\n const key = pair.slice(0, equalSignIndex).trim();\n // Drop empty names and honour the FIRST occurrence on duplicates (Specs say servers SHOULD NOT rely on order.\n // We pick first-wins for stability).\n if (!key || cookies[key] !== undefined) continue;\n\n let val = pair.slice(equalSignIndex + 1).trim();\n\n // Cookie values are sometimes sent wrapped in double quotes (like name=\"hello world\"), so we strip the outer \"\n // characters to get the actual value hello world.\n // The val.length > 1 guard handles the edge case where the value is literally just a single \" — without it, that one\n // character would match both the \"starts with quote\" and \"ends with quote\" checks, and slice(1, -1) would wipe it out\n // to an empty string.\n if (val.length > 1 && val[0] === '\"' && val[val.length - 1] === '\"') {\n val = val.slice(1, -1);\n }\n\n // Percent-decoding cookie values is a server-side convention (not part of\n // RFC 6265 itself), but it's what Express and most ecosystem libraries do,\n // so we follow suit for compatibility. Skip the decode entirely when there's no\n // '%' to save work on the common case, and fall back to the raw value if\n // decodeURIComponent throws on malformed input rather than crashing the\n // whole request.\n try {\n cookies[key] = val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n } catch (e) {\n cookies[key] = val;\n }\n }\n return cookies;\n}\n\n// One example output: \"session=abc123; Path=/dashboard; Domain=example.com; Max-Age=86400; Expires=Thu, 31 Dec 2026 00:00:00 GMT; HttpOnly; Secure; SameSite=Strict\"\nfunction buildSetCookieHeader(\n name: string,\n value: string,\n options: CookieOptions\n): string {\n const parts: string[] = [`${name}=${encodeURIComponent(value)}`];\n const path = options.path ?? \"/\";\n parts.push(`Path=${path}`);\n if (options.domain) parts.push(`Domain=${options.domain}`);\n if (options.maxAge !== undefined)\n parts.push(`Max-Age=${Math.floor(options.maxAge / 1000)}`);\n if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n if (options.httpOnly) parts.push(\"HttpOnly\");\n if (options.secure) parts.push(\"Secure\");\n if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);\n return parts.join(\"; \");\n}\n\n// Without this helper, calling res.cookie(\"a\", \"1\") then res.cookie(\"b\", \"2\") would overwrite the first cookie instead\n// of sending both.\nfunction appendSetCookie(res: CpeakResponse, header: string) {\n const existing = res.getHeader(\"Set-Cookie\");\n if (!existing) {\n res.setHeader(\"Set-Cookie\", [header]);\n } else if (Array.isArray(existing)) {\n res.setHeader(\"Set-Cookie\", [...existing, header]);\n } else {\n res.setHeader(\"Set-Cookie\", [String(existing), header]);\n }\n}\n\nexport function cookieParser(options: { secret?: string } = {}) {\n const { secret } = options;\n\n if (secret !== undefined && secret.length < 32) {\n throw frameworkError(\n \"Secret must be at least 32 characters. HMAC security is only as strong as the key.\",\n cookieParser,\n ErrorCode.WEAK_SECRET\n );\n }\n\n return (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n const rawHeader = req.headers[\"cookie\"] || \"\";\n const raw = parseRawCookies(rawHeader);\n\n // Mirror parseRawCookies and use null-prototype maps here too. If we used\n // a regular `{}`, the assignment below would invoke Object.prototype's\n // __proto__ setter (no-op for string values), silently dropping any\n // cookie literally named __proto__ — undoing the fix in parseRawCookies.\n const cookies: Record<string, string> = Object.create(null);\n const signedCookies: Record<string, string | false> = Object.create(null);\n\n for (const [key, val] of Object.entries(raw)) {\n // The \"s:\" prefix is the marker we add in `sign()` for HMAC-signed\n // cookies. Route those through unsign so the handler sees the original\n // value (or `false` if the signature didn't verify).\n if (val.startsWith(\"s:\") && secret) {\n signedCookies[key] = unsign(val, secret);\n } else {\n cookies[key] = val;\n }\n }\n\n // The separation is intentional signal: \"these were signed and verified, trust them more.\"\n req.cookies = cookies;\n req.signedCookies = signedCookies;\n\n res.cookie = (name: string, value: string, options: CookieOptions = {}) => {\n let finalValue = value;\n if (options.signed) {\n if (!secret)\n throw new Error(\n \"cookieParser: secret is required to use signed cookies\"\n );\n finalValue = sign(value, secret);\n }\n appendSetCookie(res, buildSetCookieHeader(name, finalValue, options));\n return res;\n };\n\n res.clearCookie = (name: string, options: CookieOptions = {}) => {\n appendSetCookie(\n res,\n buildSetCookieHeader(name, \"\", {\n ...options,\n maxAge: 0,\n expires: new Date(0)\n })\n );\n return res;\n };\n\n next();\n };\n}\n","import type { CpeakRequest, CpeakResponse, Next } from \"../types\";\nimport type { CorsOptions, OriginInput } from \"./types\";\n\n// Append a value to an existing header without overwriting prior entries\n// (e.g. compression already sets `Vary: Accept-Encoding`).\nfunction appendVary(res: CpeakResponse, value: string) {\n const existing = res.getHeader(\"Vary\");\n if (!existing) return res.setHeader(\"Vary\", value);\n const current = String(existing)\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n if (current.includes(\"*\") || current.includes(value)) return;\n res.setHeader(\"Vary\", [...current, value].join(\", \"));\n}\n\n// Determine if the given origin is allowed based on the rule.\n// Examples of what developers can specify for the rule:\n// - `true` or `*`: allow all origins\n// - `false`: disallow all origins\n// - `\"https://example.com\"`: allow only this origin\n// - `[\"https://example.com\", \"https://foo.com\"]`: allow these origins\n// - `/\\.example\\.com$/`: allow origins that match this regex\n// - `(origin) => origin === \"https://example.com\"`: custom function to determine if the origin is allowed\nasync function isAllowed(\n origin: string | undefined,\n rule: OriginInput\n): Promise<boolean> {\n if (rule === true || rule === \"*\") return true;\n if (rule === false || !origin) return false;\n if (typeof rule === \"string\") return rule === origin;\n if (Array.isArray(rule)) return rule.includes(origin);\n if (rule instanceof RegExp) return rule.test(origin);\n if (typeof rule === \"function\") return await rule(origin);\n return false;\n}\n\nconst cors = (options: CorsOptions = {}) => {\n const {\n origin = \"*\",\n methods = \"GET,HEAD,PUT,PATCH,POST,DELETE\",\n allowedHeaders,\n exposedHeaders,\n credentials = false,\n maxAge = 86400,\n preflightContinue = false,\n optionsSuccessStatus = 204\n } = options;\n\n const methodsStr = Array.isArray(methods) ? methods.join(\",\") : methods;\n const allowedHeadersStr = Array.isArray(allowedHeaders)\n ? allowedHeaders.join(\",\")\n : allowedHeaders;\n const exposedHeadersStr = Array.isArray(exposedHeaders)\n ? exposedHeaders.join(\",\")\n : exposedHeaders;\n\n return async (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n const requestOrigin = req.headers.origin;\n\n // Not a CORS request, nothing to do.\n if (!requestOrigin) return next();\n\n const allowed = await isAllowed(requestOrigin, origin);\n if (!allowed) return next();\n\n // We cannot combine Access-Control-Allow-Origin: * with\n // Access-Control-Allow-Credentials: true. Browsers will flat-out reject it.\n // Instead we'll reflect the origin.\n const allowOriginValue =\n origin === \"*\" && !credentials ? \"*\" : requestOrigin;\n res.setHeader(\"Access-Control-Allow-Origin\", allowOriginValue);\n if (allowOriginValue !== \"*\") appendVary(res, \"Origin\");\n\n if (credentials) res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n if (exposedHeadersStr)\n res.setHeader(\"Access-Control-Expose-Headers\", exposedHeadersStr);\n\n const isPreflight =\n req.method === \"OPTIONS\" &&\n req.headers[\"access-control-request-method\"] !== undefined;\n\n if (!isPreflight) return next();\n\n res.setHeader(\"Access-Control-Allow-Methods\", methodsStr);\n\n if (allowedHeadersStr) {\n res.setHeader(\"Access-Control-Allow-Headers\", allowedHeadersStr);\n } else if (origin === \"*\") {\n // If origin is *, just act like an echo chamber for requested headers. Give back whatever the browser asks for.\n const requested = req.headers[\"access-control-request-headers\"];\n if (requested) res.setHeader(\"Access-Control-Allow-Headers\", requested);\n } else {\n res.setHeader(\n \"Access-Control-Allow-Headers\",\n \"Content-Type, Authorization\"\n );\n }\n\n res.setHeader(\"Access-Control-Max-Age\", String(maxAge));\n\n if (preflightContinue) return next();\n\n res.statusCode = optionsSuccessStatus;\n res.end();\n };\n};\n\nexport { cors };\n"],"mappings":"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","names":["fs","pipeline","Buffer","ErrorCode","path","Buffer","Buffer","folderPath","filesMap","fs","path","fs","createHmac","timingSafeEqual","createHmac","timingSafeEqual","path","options","appendVary","path","fs","pipeline","req","res"]}
1
+ {"version":3,"sources":["../lib/index.ts","../lib/internal/compression.ts","../lib/internal/mimeTypes.ts","../lib/internal/errors.ts","../lib/internal/router.ts","../lib/utils/parseJSON.ts","../lib/utils/serveStatic.ts","../lib/utils/render.ts","../lib/utils/swagger.ts","../lib/utils/auth.ts","../lib/utils/cookieParser.ts","../lib/utils/cors.ts"],"sourcesContent":["import http from \"node:http\";\nimport fs from \"node:fs/promises\";\nimport { createReadStream } from \"node:fs\";\nimport { pipeline } from \"node:stream/promises\";\n\nimport type net from \"node:net\";\nimport type { Readable } from \"node:stream\";\nimport type { Buffer } from \"node:buffer\";\n\nimport {\n resolveCompressionOptions,\n compressAndSend\n} from \"./internal/compression\";\nimport { MIME_TYPES } from \"./internal/mimeTypes\";\nimport { Router } from \"./internal/router\";\nimport {\n frameworkError,\n ErrorCode,\n isClientDisconnect\n} from \"./internal/errors\";\n\nexport { frameworkError, ErrorCode, isClientDisconnect };\n\nimport type {\n StringMap,\n CpeakHttpServer,\n CpeakOptions,\n CpeakRequest,\n CpeakResponse,\n Middleware,\n RouteMiddleware,\n Handler\n} from \"./types\";\n\nimport type { ResolvedCompressionConfig } from \"./internal/types\";\n\nexport class CpeakIncomingMessage extends http.IncomingMessage {\n // We define body and params here for better V8 optimization (not changing the shape of the object at runtime)\n public body: any = undefined;\n public params: StringMap = {};\n\n #query?: StringMap;\n\n // Parse the URL parameters (like /users?key1=value1&key2=value2)\n // We will call this query to be more familiar with other node.js frameworks.\n // This is a getter method (accessed like a property)\n get query(): StringMap {\n // This way if a developer writes req.query multiple times, we don't parse it multiple times\n if (this.#query) return this.#query;\n\n const url = this.url || \"\";\n const qIndex = url.indexOf(\"?\");\n\n if (qIndex === -1) {\n this.#query = {};\n } else {\n const searchParams = new URLSearchParams(url.substring(qIndex + 1));\n this.#query = Object.fromEntries(searchParams.entries());\n }\n\n return this.#query;\n }\n}\n\nexport class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {\n // Set per-request from the Cpeak instance. Undefined when compression isn't enabled.\n _compression?: ResolvedCompressionConfig;\n\n // Send a file back to the client\n async sendFile(path: string, mime?: string) {\n if (this.headersSent) return;\n if (!mime) {\n const dotIndex = path.lastIndexOf(\".\");\n const fileExtension = dotIndex >= 0 ? path.slice(dotIndex + 1) : \"\";\n mime = MIME_TYPES[fileExtension];\n if (!mime) {\n throw frameworkError(\n `MIME type is missing for \"${path}\". Pass it as the second argument or register the extension via cpeak({ mimeTypes: { ${fileExtension || \"ext\"}: \"...\" } }).`,\n this.sendFile,\n ErrorCode.MISSING_MIME\n );\n }\n }\n\n try {\n const stat = await fs.stat(path);\n if (!stat.isFile()) {\n throw frameworkError(\n `Not a file: ${path}`,\n this.sendFile,\n ErrorCode.NOT_A_FILE\n );\n }\n\n if (this._compression) {\n await compressAndSend(\n this,\n mime,\n createReadStream(path),\n this._compression,\n stat.size\n );\n return;\n }\n\n this.setHeader(\"Content-Type\", mime);\n this.setHeader(\"Content-Length\", String(stat.size));\n\n // Properly propagate stream errors and respect backpressure\n await pipeline(createReadStream(path), this);\n } catch (err: any) {\n if (err?.code === \"ENOENT\") {\n throw frameworkError(\n `File not found: ${path}`,\n this.sendFile,\n ErrorCode.FILE_NOT_FOUND\n );\n }\n\n throw frameworkError(\n `Failed to send file: ${path}`,\n this.sendFile,\n ErrorCode.SEND_FILE_FAIL,\n undefined,\n isClientDisconnect(err)\n );\n }\n }\n\n // Set the status code of the response\n status(code: number) {\n this.statusCode = code;\n return this;\n }\n\n // Set the Content-Disposition header to prompt the user to download a file\n attachment(filename?: string) {\n const contentDisposition = filename\n ? `attachment; filename=\"${filename}\"`\n : \"attachment\";\n this.setHeader(\"Content-Disposition\", contentDisposition);\n return this;\n }\n\n // Redirects to a new URL\n redirect(location: string) {\n this.writeHead(302, { Location: location });\n this.end();\n }\n\n // Send a json data back to the client.\n // This is only good for bodies that their size is less than the highWaterMark value.\n json(data: any): Promise<void> {\n if (this.headersSent) return Promise.resolve();\n const body = JSON.stringify(data);\n if (this._compression) {\n return compressAndSend(this, \"application/json\", body, this._compression);\n }\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(body);\n return Promise.resolve();\n }\n\n render(): Promise<void> {\n throw frameworkError(\n \"render middleware not registered. Add render() via app.beforeEach(render()) to use res.render.\",\n this.render,\n ErrorCode.RENDER_NOT_ENABLED\n );\n }\n\n // Explicit compression entry point. A developer can use this in any custom handler to compress arbitrary responses\n compress(\n mime: string,\n body: Buffer | string | Readable,\n size?: number\n ): Promise<void> {\n if (this.headersSent) return Promise.resolve();\n if (!this._compression) {\n throw frameworkError(\n \"compression is not enabled. Pass `compression` to cpeak({ compression: true | { ... } }) to use res.compress.\",\n this.compress,\n ErrorCode.COMPRESSION_NOT_ENABLED\n );\n }\n return compressAndSend(this, mime, body, this._compression, size);\n }\n}\n\nexport class Cpeak {\n #server: CpeakHttpServer;\n #router: Router;\n #middleware: Middleware[];\n #handleErr?: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void;\n #fallback?: Handler;\n #compression?: ResolvedCompressionConfig;\n\n constructor(options: CpeakOptions = {}) {\n this.#server = http.createServer({\n IncomingMessage: CpeakIncomingMessage,\n ServerResponse: CpeakServerResponse\n });\n this.#router = new Router();\n this.#middleware = [];\n\n // Resolve compression options once at app startup.\n if (options.compression) {\n this.#compression = resolveCompressionOptions(options.compression);\n }\n\n // Merge developer-supplied mime types with the defaults once at startup\n if (options.mimeTypes) Object.assign(MIME_TYPES, options.mimeTypes);\n\n this.#server.on(\n \"request\",\n async (req: CpeakRequest, res: CpeakResponse) => {\n res._compression = this.#compression;\n\n // Get the url without the URL parameters (query strings)\n const qIndex = req.url?.indexOf(\"?\");\n const urlWithoutQueries =\n qIndex === -1 ? req.url || \"\" : req.url?.substring(0, qIndex);\n\n // Routes every error path through the registered handleErr. Awaits\n // handleErr so its own async work (or a rejecting res.json under\n // compression) is caught. If handleErr itself fails, we log and send a\n // bare 500 so the client never gets a hung socket. Returns a Promise\n // that never rejects to avoid unhandled promise rejections in case of errors in handleErr.\n const dispatchError = async (error: unknown) => {\n if (res.headersSent) {\n req.socket?.destroy();\n } else {\n res.setHeader(\"Connection\", \"close\");\n }\n\n if (isClientDisconnect(error) && !(error as any).clientDisconnect) {\n (error as any).clientDisconnect = true;\n }\n try {\n await this.#handleErr?.(error, req, res);\n } catch (handlerFailure) {\n console.error(\n \"[cpeak] handleErr failed while processing:\",\n error,\n \"\\nReason:\",\n handlerFailure\n );\n if (!res.headersSent) {\n try {\n res.statusCode = 500;\n res.end();\n } catch {}\n }\n }\n };\n\n // Run all the specific middleware functions for that router only and then run the handler\n const runHandler = async (\n req: CpeakRequest,\n res: CpeakResponse,\n middleware: RouteMiddleware[],\n cb: Handler,\n index: number\n ) => {\n // Our exit point...\n if (index === middleware.length) {\n // Call the route handler with the modified req and res objects.\n // Also handle the promise errors by passing them to handleErr to save developers from having to manually wrap every handler in try/catch.\n try {\n await cb(req, res);\n } catch (error) {\n dispatchError(error);\n }\n } else {\n // Handle the promise errors by passing them to handleErr to save developers from having to manually wrap every route middleware in try/catch.\n try {\n await middleware[index](req, res, async (error?: unknown) => {\n // this function only accepts an error argument to be more compatible with NPM modules that are built for express\n if (error) {\n return dispatchError(error);\n }\n await runHandler(req, res, middleware, cb, index + 1);\n });\n } catch (error) {\n dispatchError(error);\n }\n }\n };\n\n // Run all the middleware functions (beforeEach functions) before we run the corresponding route\n const runMiddleware = async (\n req: CpeakRequest,\n res: CpeakResponse,\n middleware: Middleware[],\n index: number\n ) => {\n // Our exit point...\n if (index === middleware.length) {\n const method = req.method?.toLowerCase() || \"\";\n const found = this.#router.find(method, urlWithoutQueries || \"\");\n\n if (found) {\n req.params = found.params;\n return await runHandler(\n req,\n res,\n found.middleware,\n found.handler,\n 0\n );\n }\n\n // If a fallback handler is registered, run it before falling back to the default 404\n if (this.#fallback) {\n try {\n return await this.#fallback(req, res);\n } catch (error) {\n return dispatchError(error);\n }\n }\n\n // If the requested route dose not exist, and developer has not registered the fallback handler, return 404\n return res\n .status(404)\n .json({ error: `Cannot ${req.method} ${urlWithoutQueries}` });\n } else {\n try {\n await middleware[index](req, res, async (err?: unknown) => {\n if (err) {\n return dispatchError(err);\n }\n await runMiddleware(req, res, middleware, index + 1);\n });\n } catch (error) {\n dispatchError(error);\n }\n }\n };\n\n await runMiddleware(req, res, this.#middleware, 0);\n }\n );\n }\n\n route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]) {\n // The last argument should always be our handler\n const cb = args.pop() as Handler;\n\n if (!cb || typeof cb !== \"function\") {\n throw new Error(\"Route definition must include a handler\");\n }\n\n // Rest will be our middleware functions\n const middleware = args.flat() as RouteMiddleware[];\n\n this.#router.add(method, path, middleware, cb);\n }\n\n beforeEach(cb: Middleware) {\n this.#middleware.push(cb);\n }\n\n handleErr(cb: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void) {\n this.#handleErr = cb;\n }\n\n // This will handle any request that doesn't match any of the routes and middleware functions\n fallback(cb: Handler) {\n if (this.#fallback) {\n throw frameworkError(\n \"Fallback handler is already registered. Only one fallback can be set per app.\",\n this.fallback,\n ErrorCode.DUPLICATE_FALLBACK\n );\n }\n this.#fallback = cb;\n }\n\n // The first 3 listens are just TS overloads for better type inference and editor autocompletion. The last one is the actual implementation.\n listen(port: number, cb?: () => void): CpeakHttpServer;\n listen(port: number, host: string, cb?: () => void): CpeakHttpServer;\n listen(options: net.ListenOptions, cb?: () => void): CpeakHttpServer;\n listen(...args: any[]) {\n return this.#server.listen(...args);\n }\n\n address() {\n return this.#server.address();\n }\n\n close(cb?: (err?: Error) => void) {\n return this.#server.close(cb);\n }\n\n // A getter for developers who want to access the underlying http server instance for advanced use cases that aren't covered by Cpeak\n get server() {\n return this.#server;\n }\n}\n\n// Util functions\nexport {\n serveStatic,\n parseJSON,\n render,\n swagger,\n auth,\n hashPassword,\n verifyPassword,\n cookieParser,\n cors\n} from \"./utils\";\n\nexport type {\n AuthOptions,\n PbkdfOptions,\n CookieOptions,\n CorsOptions\n} from \"./utils/types\";\n\nexport type { CompressionOptions } from \"./internal/types\";\n\nexport type {\n CpeakHttpServer,\n CpeakOptions,\n CpeakRequest,\n CpeakResponse,\n Next,\n Middleware,\n RouteMiddleware,\n Handler\n} from \"./types\";\n\nexport default function cpeak(options?: CpeakOptions): Cpeak {\n return new Cpeak(options);\n}\n","import zlib from \"node:zlib\";\nimport { Readable } from \"node:stream\";\nimport { Buffer } from \"node:buffer\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { Transform } from \"node:stream\";\nimport type { ServerResponse } from \"node:http\";\nimport type { CompressionOptions, ResolvedCompressionConfig } from \"./types\";\n\ntype Encoding = \"br\" | \"gzip\" | \"deflate\";\n\nconst COMPRESSIBLE_TYPE = /text|json|javascript|css|xml|svg/i;\nconst NO_TRANSFORM = /(?:^|,)\\s*no-transform\\s*(?:,|$)/i;\n\n// Parse Accept-Encoding and pick a compression algorithm the server supports.\n// Handles q=0 to disable an algorithm. Cpeak preference is fixed: br > gzip > deflate.\nfunction pickEncoding(header: string): Encoding | null {\n if (!header) return null;\n\n const accepted: Record<string, number> = {};\n let wildcard: number | undefined;\n\n for (const part of header.split(\",\")) {\n const [rawName, ...params] = part.trim().split(\";\");\n const name = rawName.trim().toLowerCase();\n if (!name) continue;\n\n let q = 1;\n for (const p of params) {\n const m = p.trim().match(/^q=([\\d.]+)$/i);\n if (m) q = Number(m[1]);\n }\n if (Number.isNaN(q)) q = 0;\n\n if (name === \"*\") wildcard = q;\n else accepted[name] = q;\n }\n\n const tryPick = (enc: Encoding): boolean => {\n const q = enc in accepted ? accepted[enc] : wildcard;\n return q !== undefined && q > 0;\n };\n\n if (tryPick(\"br\")) return \"br\";\n if (tryPick(\"gzip\")) return \"gzip\";\n if (tryPick(\"deflate\")) return \"deflate\";\n return null;\n}\n\n// Handling the Vary HTTP header\nfunction appendVary(res: ServerResponse, value: string) {\n const existing = res.getHeader(\"Vary\");\n if (!existing) return res.setHeader(\"Vary\", value);\n const current = String(existing)\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n if (\n current.includes(\"*\") ||\n current.some((v) => v.toLowerCase() === value.toLowerCase())\n )\n return;\n res.setHeader(\"Vary\", [...current, value].join(\", \"));\n}\n\n// Brotli options. Zlib uses 11 (max), which is really slow for live\n// responses. We go with 4 unless the developer specifies otherwise.\nfunction brotliOptsFor(config: ResolvedCompressionConfig): zlib.BrotliOptions {\n const userBrotli = config.brotli || {};\n return {\n ...userBrotli,\n params: {\n [zlib.constants.BROTLI_PARAM_QUALITY]: 4,\n ...(userBrotli.params || {})\n }\n };\n}\n\nfunction createCompressorStream(\n encoding: Encoding,\n config: ResolvedCompressionConfig\n): Transform {\n if (encoding === \"br\")\n return zlib.createBrotliCompress(brotliOptsFor(config));\n if (encoding === \"gzip\") return zlib.createGzip(config.gzip);\n return zlib.createDeflate(config.deflate);\n}\n\n// Decides what to do with this response\nfunction negotiate(\n res: ServerResponse,\n mime: string,\n size: number,\n config: ResolvedCompressionConfig\n): { encoding: Encoding | null; eligible: boolean } {\n // Whether this content type is worth trying to compress at all.\n // Some types are already compressed and don't compress well.\n if (!COMPRESSIBLE_TYPE.test(mime)) return { encoding: null, eligible: false };\n\n if (res.req?.method === \"HEAD\") return { encoding: null, eligible: false };\n\n // RFC specification: don't transform responses that ask not to be transformed.\n const cc = res.getHeader(\"Cache-Control\");\n if (cc && NO_TRANSFORM.test(String(cc)))\n return { encoding: null, eligible: false };\n\n const existing = res.getHeader(\"Content-Encoding\");\n if (existing && existing !== \"identity\")\n return { encoding: null, eligible: false };\n\n if (size < config.threshold) return { encoding: null, eligible: true };\n\n const encoding = pickEncoding(\n String(res.req?.headers[\"accept-encoding\"] || \"\")\n );\n return { encoding, eligible: true };\n}\n\n// Converts into a Readable stream\nfunction bodyAsReadable(body: Buffer | string | Readable): Readable {\n if (Buffer.isBuffer(body)) return Readable.from([body]);\n if (typeof body === \"string\") return Readable.from([Buffer.from(body)]);\n return body;\n}\n\n// Resolves compression options (or 'true' for defaults) into a\n// complete config. Called once at Cpeak construction.\nexport function resolveCompressionOptions(\n input: true | CompressionOptions\n): ResolvedCompressionConfig {\n const options: CompressionOptions = input === true ? {} : input;\n return {\n threshold: options.threshold ?? 1024,\n brotli: options.brotli ?? {},\n gzip: options.gzip ?? {},\n deflate: options.deflate ?? {}\n };\n}\n\n// The final point used by res.compress, res.json, res.sendFile and res.render\n// when compression is enabled by the developer.\n//\n// Compression always goes through createGzip/createBrotliCompress/createDeflate\n// streams which are async and run on libuv's thread pool.\nexport async function compressAndSend(\n res: ServerResponse,\n mime: string,\n body: Buffer | string | Readable,\n config: ResolvedCompressionConfig,\n size?: number\n): Promise<void> {\n res.setHeader(\"Content-Type\", mime);\n\n const knownSize: number = Buffer.isBuffer(body)\n ? body.length\n : typeof body === \"string\"\n ? Buffer.byteLength(body)\n : (size ?? Infinity);\n\n const { encoding, eligible } = negotiate(res, mime, knownSize, config);\n\n if (!encoding) {\n if (eligible) appendVary(res, \"Accept-Encoding\");\n if (Buffer.isBuffer(body) || typeof body === \"string\") {\n res.setHeader(\"Content-Length\", String(knownSize));\n res.end(body);\n return;\n }\n if (size !== undefined) res.setHeader(\"Content-Length\", String(size));\n await pipeline(body, res);\n return;\n }\n\n res.setHeader(\"Content-Encoding\", encoding);\n appendVary(res, \"Accept-Encoding\");\n await pipeline(\n bodyAsReadable(body),\n createCompressorStream(encoding, config),\n res\n );\n}\n","import type { StringMap } from \"../types\";\n\n// Developers can expand this if needed in the cpeak() constructor\nexport const MIME_TYPES: StringMap = {\n html: \"text/html\",\n css: \"text/css\",\n js: \"application/javascript\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n svg: \"image/svg+xml\",\n txt: \"text/plain\",\n eot: \"application/vnd.ms-fontobject\",\n otf: \"font/otf\",\n ttf: \"font/ttf\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n gif: \"image/gif\",\n ico: \"image/x-icon\",\n json: \"application/json\",\n map: \"application/json\",\n webmanifest: \"application/manifest+json\",\n xml: \"application/xml\",\n pdf: \"application/pdf\",\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n webp: \"image/webp\",\n avif: \"image/avif\"\n};","// A utility function to create an error with a custom stack trace\nexport function frameworkError(\n message: string,\n skipFn: Function,\n code?: string,\n status?: number,\n clientDisconnect?: boolean\n) {\n const err = new Error(message) as Error & {\n code?: string;\n cpeak_err?: boolean;\n clientDisconnect?: boolean;\n };\n Error.captureStackTrace(err, skipFn);\n\n err.cpeak_err = true;\n\n if (code) err.code = code;\n if (status) (err as any).status = status;\n if (clientDisconnect) err.clientDisconnect = true;\n\n return err;\n}\n\nconst CLIENT_DISCONNECT_CODES = new Set([\n \"ERR_STREAM_PREMATURE_CLOSE\",\n \"ERR_STREAM_DESTROYED\",\n \"ECONNRESET\",\n \"EPIPE\"\n]);\n\nexport function isClientDisconnect(err: unknown): boolean {\n return CLIENT_DISCONNECT_CODES.has((err as any)?.code);\n}\n\nexport enum ErrorCode {\n MISSING_MIME = \"CPEAK_ERR_MISSING_MIME\",\n FILE_NOT_FOUND = \"CPEAK_ERR_FILE_NOT_FOUND\",\n NOT_A_FILE = \"CPEAK_ERR_NOT_A_FILE\",\n SEND_FILE_FAIL = \"CPEAK_ERR_SEND_FILE_FAIL\",\n INVALID_JSON = \"CPEAK_ERR_INVALID_JSON\",\n PAYLOAD_TOO_LARGE = \"CPEAK_ERR_PAYLOAD_TOO_LARGE\",\n WEAK_SECRET = \"CPEAK_ERR_WEAK_SECRET\",\n COMPRESSION_NOT_ENABLED = \"CPEAK_ERR_COMPRESSION_NOT_ENABLED\",\n RENDER_NOT_ENABLED = \"CPEAK_ERR_RENDER_NOT_ENABLED\",\n // For router:\n DUPLICATE_ROUTE = \"CPEAK_ERR_DUPLICATE_ROUTE\",\n INVALID_ROUTE = \"CPEAK_ERR_INVALID_ROUTE\",\n DUPLICATE_FALLBACK = \"CPEAK_ERR_DUPLICATE_FALLBACK\",\n RENDER_FAIL = \"CPEAK_ERR_RENDER_FAIL\"\n}\n","import type { Handler, RouteMiddleware, StringMap } from \"../types\";\nimport { frameworkError, ErrorCode } from \"./errors\";\n\n// A node in our radix tree. Each one can hold up to three kinds of children:\n// an exact static segment, a single \":param\" placeholder, or a tail \"*\"\n// wildcard. The handler and middleware here belong to the route whose path\n// ends at this node, if any.\n//\n// Param names are not stored on the tree edges. We capture values positionally\n// as we walk, and zip them with the param names attached to whichever leaf we\n// land on. That lets two routes share the same param slot in the tree even\n// when they use different names, like \"/:id/profile\" and \"/:username/settings\".\ninterface RadixNode {\n staticChildren: Map<string, RadixNode>;\n paramChild?: RadixNode;\n wildcardChild?: WildcardLeaf;\n handler?: Handler;\n middleware?: RouteMiddleware[];\n // Names of params captured along the path to this leaf, in order. Only set\n // on nodes that own a handler.\n paramNames?: string[];\n}\n\ninterface WildcardLeaf {\n handler: Handler;\n middleware: RouteMiddleware[];\n // Names of params captured before reaching this wildcard, in order.\n paramNames: string[];\n}\n\nexport interface RouteMatch {\n middleware: RouteMiddleware[];\n handler: Handler;\n params: StringMap;\n}\n\nfunction createNode(): RadixNode {\n return { staticChildren: new Map() };\n}\n\n// We keep one radix tree per HTTP method so different methods can safely\n// share a path shape. POST /comments/:pageId and PUT /comments/:id can\n// coexist without conflict because they live in separate trees.\nexport class Router {\n #treesByMethod: Map<string, RadixNode> = new Map();\n\n add(\n method: string,\n path: string,\n middleware: RouteMiddleware[],\n handler: Handler\n ) {\n const methodKey = method.toLowerCase();\n let root = this.#treesByMethod.get(methodKey);\n if (!root) {\n root = createNode();\n this.#treesByMethod.set(methodKey, root);\n }\n\n const segments = splitPath(path);\n const paramNames: string[] = [];\n let currentNode = root;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n const isLastSegment = i === segments.length - 1;\n\n // Named wildcards like \"*name\" are not a thing here. Only a bare \"*\"\n // is allowed, and only as the very last segment.\n if (segment.length > 1 && segment.startsWith(\"*\")) {\n throw frameworkError(\n `Invalid route \"${path}\": named wildcards (e.g. \"*name\") are not supported. Use a plain \"*\" at the end of the path.`,\n this.add,\n ErrorCode.INVALID_ROUTE\n );\n }\n\n // A \"*\" segment installs a tail wildcard on the current node. After\n // that there's nothing more to walk, so we register and bail out.\n if (segment === \"*\") {\n if (!isLastSegment) {\n throw frameworkError(\n `Invalid route \"${path}\": \"*\" is only allowed as the final path segment.`,\n this.add,\n ErrorCode.INVALID_ROUTE\n );\n }\n if (currentNode.wildcardChild) {\n throw frameworkError(\n `Duplicate route: ${method.toUpperCase()} ${path}`,\n this.add,\n ErrorCode.DUPLICATE_ROUTE\n );\n }\n currentNode.wildcardChild = { handler, middleware, paramNames };\n return;\n }\n\n // A \":name\" segment walks into the param branch at this depth, or\n // creates one. The name is collected positionally and resolved later\n // at the leaf, so two routes can disagree on the param name here as\n // long as their paths diverge before the leaf.\n if (segment.startsWith(\":\")) {\n const paramName = segment.slice(1);\n if (!paramName) {\n throw frameworkError(\n `Invalid route \"${path}\": empty parameter name.`,\n this.add,\n ErrorCode.INVALID_ROUTE\n );\n }\n paramNames.push(paramName);\n if (!currentNode.paramChild) {\n currentNode.paramChild = createNode();\n }\n currentNode = currentNode.paramChild;\n continue;\n }\n\n // Plain static segment. Walk into the existing child or create a new one.\n let staticChild = currentNode.staticChildren.get(segment);\n if (!staticChild) {\n staticChild = createNode();\n currentNode.staticChildren.set(segment, staticChild);\n }\n currentNode = staticChild;\n }\n\n // We have consumed every segment of the path. The terminal node is where\n // the handler gets attached. If something is already attached here, the\n // user registered this exact path twice.\n if (currentNode.handler) {\n throw frameworkError(\n `Duplicate route: ${method.toUpperCase()} ${path}`,\n this.add,\n ErrorCode.DUPLICATE_ROUTE\n );\n }\n currentNode.handler = handler;\n currentNode.middleware = middleware;\n currentNode.paramNames = paramNames;\n }\n\n find(method: string, path: string): RouteMatch | null {\n const root = this.#treesByMethod.get(method.toLowerCase());\n if (!root) return null;\n\n const segments = splitPath(path);\n return matchSegments(root, segments, 0, []);\n }\n}\n\n// Walk the tree one segment at a time, always trying static before param\n// before wildcard. That ordering is where our precedence rules come from:\n// static beats param beats wildcard. Because each branch is tried in turn\n// and recursion lets us unwind a failed path, the matcher also backtracks.\n// If the static branch dead-ends deeper down, we come back up and try the\n// param sibling with the same segment value.\n//\n// We collect captured param values positionally as we walk. The actual names\n// get zipped in at the terminal leaf, using the paramNames stored alongside\n// the handler. That way the same captured value can be called \"id\" on one\n// route and \"username\" on another without the tree caring.\nfunction matchSegments(\n node: RadixNode,\n segments: string[],\n segmentIndex: number,\n capturedValues: string[]\n): RouteMatch | null {\n // Out of segments to walk. If this node has a handler, that's our match.\n // Otherwise let a wildcard at this depth catch the empty remainder so\n // routes like \"/foo/*\" still match a request to \"/foo\".\n if (segmentIndex === segments.length) {\n if (node.handler) {\n return {\n middleware: node.middleware!,\n handler: node.handler,\n params: zipParams(node.paramNames!, capturedValues)\n };\n }\n if (node.wildcardChild) {\n return {\n middleware: node.wildcardChild.middleware,\n handler: node.wildcardChild.handler,\n params: zipParams(node.wildcardChild.paramNames, capturedValues)\n };\n }\n return null;\n }\n\n const segment = segments[segmentIndex];\n\n // Try the exact static child first. Exact matches always win.\n const staticChild = node.staticChildren.get(segment);\n if (staticChild) {\n const foundMatch = matchSegments(\n staticChild,\n segments,\n segmentIndex + 1,\n capturedValues\n );\n if (foundMatch) return foundMatch;\n }\n\n // Then try the param branch. We push the captured value before recursing\n // and pop it back off if the recursion fails, so any sibling branch (or the\n // caller unwinding above us) sees a clean capture list.\n if (node.paramChild) {\n capturedValues.push(safeDecode(segment));\n const foundMatch = matchSegments(\n node.paramChild,\n segments,\n segmentIndex + 1,\n capturedValues\n );\n if (foundMatch) return foundMatch;\n capturedValues.pop();\n }\n\n // Last resort. A wildcard at this node swallows whatever segments remain.\n if (node.wildcardChild) {\n return {\n middleware: node.wildcardChild.middleware,\n handler: node.wildcardChild.handler,\n params: zipParams(node.wildcardChild.paramNames, capturedValues)\n };\n }\n\n return null;\n}\n\nfunction zipParams(names: string[], values: string[]): StringMap {\n const params: StringMap = {};\n for (let i = 0; i < names.length; i++) {\n params[names[i]] = values[i];\n }\n return params;\n}\n\n// Decode a URL segment without ever throwing. Malformed percent encoding is\n// rare but it does happen in the wild. Falling back to the raw segment keeps\n// the request matchable instead of blowing up before the handler runs.\n// Example: safeDecode(\"a%20b%2Fc\") returns \"a b/c\", while safeDecode(\"a%ZZb\") returns \"a%ZZb\".\nfunction safeDecode(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n// Split a URL path into segments with no leading slash. We treat \"\" and \"/\"\n// the same way: zero segments, meaning the root of the tree.\n// Example: \"/a/b/c\" becomes [\"a\", \"b\", \"c\"]\nfunction splitPath(path: string): string[] {\n if (path === \"\" || path === \"/\") return [];\n const withoutLeadingSlash = path.startsWith(\"/\") ? path.slice(1) : path;\n return withoutLeadingSlash.split(\"/\");\n}\n","import type { CpeakRequest, CpeakResponse, Next } from \"../types\";\nimport { Buffer } from \"node:buffer\";\nimport { frameworkError, ErrorCode } from \"../index\";\n\n// Check if Content-Type is JSON\nfunction isJSON(contentType: string | undefined) {\n if (!contentType) return false;\n if (contentType === \"application/json\") return true;\n return (\n contentType.startsWith(\"application/json\") || contentType.includes(\"+json\")\n );\n}\n\n// Parsing JSON\nconst parseJSON = (options: { limit?: number } = {}) => {\n // Default limit to 1MB\n const limit = options.limit || 1024 * 1024;\n\n return (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n if (!isJSON(req.headers[\"content-type\"])) return next();\n\n const chunks: Buffer[] = [];\n let bytesReceived = 0;\n\n const onData = (chunk: Buffer) => {\n bytesReceived += chunk.length;\n\n // To prevent Denial of Service (DoS) attacks, enforce a maximum body size\n if (bytesReceived > limit) {\n // Stop listening to data\n req.pause();\n\n // Remove listeners so we don't trigger 'end' or more 'data'\n req.removeListener(\"data\", onData);\n req.removeListener(\"end\", onEnd);\n\n next(\n frameworkError(\n \"JSON body too large\",\n onData,\n ErrorCode.PAYLOAD_TOO_LARGE,\n 413 // HTTP 413 Payload Too Large\n )\n );\n\n return;\n }\n\n chunks.push(chunk);\n };\n\n const onEnd = () => {\n try {\n // For better performance, we concat buffers once, then convert to string\n // Optimization: If only one chunk exists, avoid the memory copy of concat\n const rawBody =\n chunks.length === 1\n ? chunks[0].toString(\"utf-8\")\n : Buffer.concat(chunks).toString(\"utf-8\");\n\n // Handle empty body case\n req.body = rawBody ? JSON.parse(rawBody) : {};\n\n next();\n } catch (err) {\n // Handle Invalid JSON without crashing\n next(\n frameworkError(\n \"Invalid JSON format\",\n onEnd,\n ErrorCode.INVALID_JSON,\n 400 // HTTP 400 Bad Request\n )\n );\n }\n };\n\n req.on(\"data\", onData);\n req.on(\"end\", onEnd);\n };\n};\n\nexport { parseJSON };\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { MIME_TYPES } from \"../internal/mimeTypes\";\nimport type { CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nconst serveStatic = (\n folderPath: string,\n options?: { prefix?: string; live?: boolean; exclude?: string[] }\n) => {\n const prefix = options?.prefix ?? \"\";\n const live = options?.live ?? false;\n\n // This process the folder on every request, which is useful during development when files are changing often.\n // In production, it's better to process the folder once and store the file paths in memory for faster access if file names are not changing often.\n // If file names dynamically change often in production, then live option can be set to true to process the folder on every request, but it may have performance implications.\n if (live) {\n const resolvedFolder = path.resolve(folderPath);\n const excludes = (options?.exclude ?? []).map(e => path.join(resolvedFolder, e));\n\n return async function (req: CpeakRequest, res: CpeakResponse, next: Next) {\n const url = req.url;\n if (typeof url !== \"string\") return next();\n\n const pathname = url.split(\"?\")[0];\n const unprefixed = prefix ? pathname.slice(prefix.length) : pathname;\n const filePath = path.join(resolvedFolder, unprefixed);\n const fileExtension = path.extname(filePath).slice(1);\n const mime = MIME_TYPES[fileExtension];\n\n if (!mime || !filePath.startsWith(resolvedFolder)) return next();\n if (excludes.some(e => filePath.startsWith(e))) return next();\n\n const stat = await fs.promises.stat(filePath).catch(() => null);\n if (stat?.isFile()) return res.sendFile(filePath, mime);\n\n next();\n };\n }\n\n const resolvedFolder = path.resolve(folderPath);\n const excludes = (options?.exclude ?? []).map(e => path.join(resolvedFolder, e));\n\n function processFolder(folderPath: string, parentFolder: string) {\n const staticFiles: string[] = [];\n\n // Read the contents of the folder\n const files = fs.readdirSync(folderPath);\n\n // Loop through the files and subfolders\n for (const file of files) {\n const fullPath = path.join(folderPath, file);\n\n // Check if it's a directory\n if (fs.statSync(fullPath).isDirectory()) {\n if (excludes.some(e => fullPath.startsWith(e))) continue;\n // If it's a directory, recursively process it\n const subfolderFiles = processFolder(fullPath, parentFolder);\n staticFiles.push(...subfolderFiles);\n } else {\n if (excludes.some(e => fullPath.startsWith(e))) continue;\n // If it's a file, add it to the array\n const relativePath = path.relative(parentFolder, fullPath);\n const fileExtension = path.extname(file).slice(1);\n if (MIME_TYPES[fileExtension]) staticFiles.push(\"/\" + relativePath);\n }\n }\n\n return staticFiles;\n }\n\n const filesArrayToFilesMap = (filesArray: string[]) => {\n const filesMap: Record<string, { path: string; mime: string }> = {};\n for (const file of filesArray) {\n const fileExtension = path.extname(file).slice(1);\n filesMap[prefix + file] = {\n path: folderPath + file,\n mime: MIME_TYPES[fileExtension]\n };\n }\n return filesMap;\n };\n\n // Start processing the folder\n const filesMap = filesArrayToFilesMap(processFolder(folderPath, folderPath));\n\n return function (req: CpeakRequest, res: CpeakResponse, next: Next) {\n const url = req.url;\n if (typeof url !== \"string\") return next();\n\n const pathname = url.split(\"?\")[0];\n if (Object.prototype.hasOwnProperty.call(filesMap, pathname)) {\n const fileRoute = filesMap[pathname];\n return res.sendFile(fileRoute.path, fileRoute.mime);\n }\n\n next();\n };\n};\n\nexport { serveStatic };\n","import path from \"node:path\";\nimport { createReadStream } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { TransformCallback } from \"node:stream\";\nimport { frameworkError, ErrorCode } from \"../\";\nimport { isClientDisconnect } from \"../internal/errors\";\nimport { compressAndSend } from \"../internal/compression\";\nimport { MIME_TYPES } from \"../internal/mimeTypes\";\nimport type { CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nexport const MAX_PATTERN = 128;\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nclass TemplateTransform extends Transform {\n private tail = \"\";\n\n constructor(\n private readonly data: Record<string, unknown>,\n private readonly baseDir: string\n ) {\n super();\n }\n\n _transform(\n chunk: Buffer,\n _: BufferEncoding,\n callback: TransformCallback\n ): void {\n const str = this.tail + chunk.toString(\"utf8\");\n if (str.length <= MAX_PATTERN) {\n this.tail = str;\n callback();\n return;\n }\n\n let boundary = str.length - MAX_PATTERN;\n\n // Prevent cutting a tag in two\n for (const [opener, closer] of [\n [\"{{\", \"}}\"],\n [\"<cpeak\", \">\"]\n ]) {\n const last = str.lastIndexOf(opener, boundary - 1);\n if (last === -1) continue;\n const closeIdx = str.indexOf(closer, last + opener.length);\n if (closeIdx === -1 || closeIdx >= boundary)\n boundary = Math.min(boundary, last);\n }\n\n this.tail = str.slice(boundary);\n const safe = str.slice(0, boundary);\n if (safe)\n this.process(safe)\n .then(() => callback())\n .catch(callback);\n else callback();\n }\n\n _flush(callback: TransformCallback): void {\n if (this.tail)\n this.process(this.tail)\n .then(() => callback())\n .catch(callback);\n else callback();\n }\n\n private async process(str: string): Promise<void> {\n const RE =\n /<cpeak\\s+include=\"([^\"]+)\"\\s*\\/?>|<cpeak\\s+html=\\{([^}]+)\\}\\s*\\/?>|\\{\\{([^}]+)\\}\\}/g;\n let last = 0;\n\n for (const match of str.matchAll(RE)) {\n const idx = match.index!;\n if (idx > last) this.push(str.slice(last, idx));\n\n const [, includeSrc, rawKey, escapedKey] = match;\n\n if (includeSrc !== undefined) {\n const includePath = path.resolve(this.baseDir, includeSrc);\n const content = await readFile(includePath, \"utf8\");\n const chunks: Buffer[] = [];\n const nested = new TemplateTransform(\n this.data,\n path.dirname(includePath)\n );\n await new Promise<void>((resolve, reject) => {\n nested.on(\"data\", (c: Buffer) => chunks.push(c));\n nested.on(\"end\", resolve);\n nested.on(\"error\", reject);\n nested.end(Buffer.from(content, \"utf8\"));\n });\n this.push(Buffer.concat(chunks));\n } else if (rawKey !== undefined) {\n const val = this.data[rawKey.trim()];\n if (val !== undefined) this.push(String(val));\n } else {\n const val = this.data[escapedKey.trim()];\n if (val !== undefined) this.push(escapeHtml(String(val)));\n }\n\n last = idx + match[0].length;\n }\n\n if (last < str.length) this.push(str.slice(last));\n }\n}\n\nconst render = () => {\n return function (req: CpeakRequest, res: CpeakResponse, next: Next): void {\n res.render = async (\n filePath: string,\n data: Record<string, unknown>,\n mime?: string\n ) => {\n if (res.headersSent) return;\n if (!mime) {\n const dotIndex = filePath.lastIndexOf(\".\");\n const fileExtension = dotIndex >= 0 ? filePath.slice(dotIndex + 1) : \"\";\n mime = MIME_TYPES[fileExtension];\n if (!mime) {\n throw frameworkError(\n `MIME type is missing for \"${filePath}\". Pass it as the third argument or register the extension via cpeak({ mimeTypes: { ${fileExtension || \"ext\"}: \"...\" } }).`,\n res.render,\n ErrorCode.MISSING_MIME\n );\n }\n }\n\n const resolved = path.resolve(filePath);\n\n try {\n if (res._compression) {\n const readStream = createReadStream(resolved);\n const transform = new TemplateTransform(data, path.dirname(resolved));\n pipeline(readStream, transform).catch(() => {});\n await compressAndSend(res, mime, transform, res._compression);\n return;\n }\n\n res.setHeader(\"Content-Type\", mime);\n await pipeline(\n createReadStream(resolved),\n new TemplateTransform(data, path.dirname(resolved)),\n res\n );\n } catch (err: any) {\n throw frameworkError(\n `Failed to render \"${filePath}.\" Error: ${err as Error}`,\n res.render,\n ErrorCode.RENDER_FAIL,\n undefined,\n isClientDisconnect(err)\n );\n }\n };\n\n next();\n };\n};\n\nexport { render };\n","import type { CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nconst swagger = (spec: object, prefix = \"/api-docs\") => {\n const initializerJs = `window.onload = function() {\n SwaggerUIBundle({\n url: \"${prefix}/spec.json\",\n dom_id: '#swagger-ui',\n presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],\n layout: \"StandaloneLayout\"\n });\n};`;\n\n return (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n if (req.url === prefix || req.url === `${prefix}/`) {\n res.writeHead(302, { Location: `${prefix}/index.html` });\n res.end();\n return;\n }\n if (req.url === `${prefix}/spec.json`) {\n return res.json(spec);\n }\n if (req.url === `${prefix}/swagger-initializer.js`) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(initializerJs);\n return;\n }\n next();\n };\n};\n\nexport { swagger };\n","import { randomBytes, pbkdf2, createHmac, timingSafeEqual } from \"node:crypto\";\nimport { promisify } from \"node:util\";\nimport type { Middleware } from \"../types\";\nimport { frameworkError, ErrorCode } from \"../index\";\nimport type { AuthOptions, PbkdfOptions } from \"./types\";\n\nconst pbkdf2Async = promisify(pbkdf2);\n\nconst DEFAULTS = {\n iterations: 210_000,\n keylen: 64,\n digest: \"sha512\",\n saltSize: 32,\n hmacAlgorithm: \"sha256\",\n tokenIdSize: 20,\n tokenExpiry: 7 * 24 * 60 * 60 * 1000 // 7 days in ms\n} as const;\n\nexport async function hashPassword(\n password: string,\n options?: PbkdfOptions\n): Promise<string> {\n const iterations = options?.iterations ?? DEFAULTS.iterations;\n const keylen = options?.keylen ?? DEFAULTS.keylen;\n const digest = options?.digest ?? DEFAULTS.digest;\n const saltSize = options?.saltSize ?? DEFAULTS.saltSize;\n const salt = randomBytes(saltSize);\n const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);\n return `pbkdf2:${iterations}:${keylen}:${digest}:${salt.toString(\"hex\")}:${hash.toString(\"hex\")}`;\n}\n\nexport async function verifyPassword(\n password: string,\n stored: string\n): Promise<boolean> {\n // When argon2 is added, dispatch on the prefix here.\n const withoutPrefix = stored.slice(stored.indexOf(\":\") + 1);\n const parts = withoutPrefix.split(\":\");\n if (parts.length !== 5) return false;\n const [itersStr, keylenStr, digest, saltHex, hashHex] = parts;\n const iterations = parseInt(itersStr, 10);\n const keylen = parseInt(keylenStr, 10);\n if (!digest || !saltHex || !hashHex || isNaN(iterations) || isNaN(keylen))\n return false;\n const salt = Buffer.from(saltHex, \"hex\");\n const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);\n const storedHash = Buffer.from(hashHex, \"hex\");\n if (storedHash.length !== hash.length) return false;\n return timingSafeEqual(hash, storedHash);\n}\n\nfunction signToken(tokenId: string, secret: string, algorithm: string): string {\n const sig = createHmac(algorithm, secret).update(tokenId).digest(\"hex\");\n return `${tokenId}.${sig}`;\n}\n\nfunction extractTokenId(\n token: string,\n secret: string,\n algorithm: string\n): string | null {\n const dot = token.indexOf(\".\");\n if (dot === -1) return null;\n const tokenId = token.slice(0, dot);\n const sig = token.slice(dot + 1);\n const expected = createHmac(algorithm, secret).update(tokenId).digest(\"hex\");\n const expectedBuf = Buffer.from(expected, \"hex\");\n const actualBuf = Buffer.from(sig, \"hex\");\n if (expectedBuf.length !== actualBuf.length) return null;\n if (!timingSafeEqual(expectedBuf, actualBuf)) return null;\n return tokenId;\n}\n\nexport function auth(options: AuthOptions): Middleware {\n if (!options.secret || options.secret.length < 32) {\n throw frameworkError(\n \"Secret must be at least 32 characters. HMAC security is only as strong as the key.\",\n auth,\n ErrorCode.WEAK_SECRET\n );\n }\n\n const {\n secret,\n saveToken,\n findToken,\n revokeToken,\n tokenExpiry = DEFAULTS.tokenExpiry,\n hmacAlgorithm = DEFAULTS.hmacAlgorithm,\n tokenIdSize = DEFAULTS.tokenIdSize\n } = options;\n\n const pbkdfOpts: PbkdfOptions = {\n iterations: options.iterations,\n keylen: options.keylen,\n digest: options.digest,\n saltSize: options.saltSize\n };\n\n const _hashPassword = ({ password }: { password: string }) =>\n hashPassword(password, pbkdfOpts);\n\n const login = async ({\n password,\n hashedPassword,\n userId\n }: {\n password: string;\n hashedPassword: string;\n userId: string;\n }): Promise<string | null> => {\n const isMatch = await verifyPassword(password, hashedPassword);\n if (!isMatch) return null;\n const tokenId = randomBytes(tokenIdSize).toString(\"hex\");\n const token = signToken(tokenId, secret, hmacAlgorithm);\n await saveToken(tokenId, userId, new Date(Date.now() + tokenExpiry));\n return token;\n };\n\n const verifyToken = async (\n token: string\n ): Promise<{ userId: string } | null> => {\n if (!token) return null;\n const tokenId = extractTokenId(token, secret, hmacAlgorithm);\n if (!tokenId) return null;\n const record = await findToken(tokenId);\n if (!record) return null;\n if (new Date(record.expiresAt) < new Date()) return null;\n return { userId: record.userId };\n };\n\n const logout = revokeToken\n ? async (token: string): Promise<boolean> => {\n const tokenId = extractTokenId(token, secret, hmacAlgorithm);\n if (!tokenId) return false;\n await revokeToken(tokenId);\n return true;\n }\n : undefined;\n\n return (req, _res, next) => {\n req.hashPassword = _hashPassword;\n req.login = login;\n req.verifyToken = verifyToken;\n if (logout) req.logout = logout;\n next();\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { CpeakRequest, CpeakResponse, Next } from \"../types\";\nimport { frameworkError, ErrorCode } from \"../index\";\nimport type { CookieOptions } from \"./types\";\n\n// This will sign the cookie value with HMAC with the secret.\n// Ideal for data like user IDs or session IDs, where you want to ensure the integrity of the cookie value without encryption.\n// So this way, imagine you save a user ID in the cookie. By signing it, you can detect if the client has tampered with the cookie value\n// (e.g., changing the user ID to impersonate another user).\n// However, since it's not encrypted, the actual user ID is still visible to the client.\n// This is a common approach for session cookies where you want to prevent tampering but don't mind if the value is visible.\nfunction sign(value: string, secret: string): string {\n const sig = createHmac(\"sha256\", secret).update(value).digest(\"base64url\");\n return `s:${value}.${sig}`;\n}\n\nfunction unsign(signed: string, secret: string): string | false {\n if (!signed.startsWith(\"s:\")) return false;\n const withoutPrefix = signed.slice(2);\n const lastDot = withoutPrefix.lastIndexOf(\".\");\n if (lastDot === -1) return false;\n const value = withoutPrefix.slice(0, lastDot);\n const sig = withoutPrefix.slice(lastDot + 1);\n const expected = createHmac(\"sha256\", secret)\n .update(value)\n .digest(\"base64url\");\n const expectedBuf = Buffer.from(expected);\n const actualBuf = Buffer.from(sig);\n if (expectedBuf.length !== actualBuf.length) return false;\n if (!timingSafeEqual(expectedBuf, actualBuf)) return false;\n return value;\n}\n\n// Parses the raw value of an HTTP `Cookie` request header into a name->value\n// This should be compatible with the RFC 6265 HTTP specification\nfunction parseRawCookies(header: string): Record<string, string> {\n // Use a null-prototype object to prevent prototype pollution attacks when assigning cookie names like \"__proto__\" or \"constructor\".\n const cookies: Record<string, string> = Object.create(null);\n if (!header) return cookies;\n\n const pairs = header.split(\";\");\n\n for (let i = 0; i < pairs.length; i++) {\n const pair = pairs[i];\n const equalSignIndex = pair.indexOf(\"=\");\n\n // RFC 6265: cookie-pair requires '='. Pairs without one (e.g. a\n // bare flag like `Cookie: foo`) are not valid cookie-pairs and we skip them.\n // Note we use the FIRST '=' only. So values like base64 padding (`token=YWJjPT0=`) must keep trailing '='s.\n if (equalSignIndex === -1) continue;\n\n const key = pair.slice(0, equalSignIndex).trim();\n // Drop empty names and honour the FIRST occurrence on duplicates (Specs say servers SHOULD NOT rely on order.\n // We pick first-wins for stability).\n if (!key || cookies[key] !== undefined) continue;\n\n let val = pair.slice(equalSignIndex + 1).trim();\n\n // Cookie values are sometimes sent wrapped in double quotes (like name=\"hello world\"), so we strip the outer \"\n // characters to get the actual value hello world.\n // The val.length > 1 guard handles the edge case where the value is literally just a single \" — without it, that one\n // character would match both the \"starts with quote\" and \"ends with quote\" checks, and slice(1, -1) would wipe it out\n // to an empty string.\n if (val.length > 1 && val[0] === '\"' && val[val.length - 1] === '\"') {\n val = val.slice(1, -1);\n }\n\n // Percent-decoding cookie values is a server-side convention (not part of\n // RFC 6265 itself), but it's what Express and most ecosystem libraries do,\n // so we follow suit for compatibility. Skip the decode entirely when there's no\n // '%' to save work on the common case, and fall back to the raw value if\n // decodeURIComponent throws on malformed input rather than crashing the\n // whole request.\n try {\n cookies[key] = val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n } catch (e) {\n cookies[key] = val;\n }\n }\n return cookies;\n}\n\n// One example output: \"session=abc123; Path=/dashboard; Domain=example.com; Max-Age=86400; Expires=Thu, 31 Dec 2026 00:00:00 GMT; HttpOnly; Secure; SameSite=Strict\"\nfunction buildSetCookieHeader(\n name: string,\n value: string,\n options: CookieOptions\n): string {\n const parts: string[] = [`${name}=${encodeURIComponent(value)}`];\n const path = options.path ?? \"/\";\n parts.push(`Path=${path}`);\n if (options.domain) parts.push(`Domain=${options.domain}`);\n if (options.maxAge !== undefined)\n parts.push(`Max-Age=${Math.floor(options.maxAge / 1000)}`);\n if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n if (options.httpOnly) parts.push(\"HttpOnly\");\n if (options.secure) parts.push(\"Secure\");\n if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);\n return parts.join(\"; \");\n}\n\n// Without this helper, calling res.cookie(\"a\", \"1\") then res.cookie(\"b\", \"2\") would overwrite the first cookie instead\n// of sending both.\nfunction appendSetCookie(res: CpeakResponse, header: string) {\n const existing = res.getHeader(\"Set-Cookie\");\n if (!existing) {\n res.setHeader(\"Set-Cookie\", [header]);\n } else if (Array.isArray(existing)) {\n res.setHeader(\"Set-Cookie\", [...existing, header]);\n } else {\n res.setHeader(\"Set-Cookie\", [String(existing), header]);\n }\n}\n\nexport function cookieParser(options: { secret?: string } = {}) {\n const { secret } = options;\n\n if (secret !== undefined && secret.length < 32) {\n throw frameworkError(\n \"Secret must be at least 32 characters. HMAC security is only as strong as the key.\",\n cookieParser,\n ErrorCode.WEAK_SECRET\n );\n }\n\n return (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n const rawHeader = req.headers[\"cookie\"] || \"\";\n const raw = parseRawCookies(rawHeader);\n\n // Mirror parseRawCookies and use null-prototype maps here too. If we used\n // a regular `{}`, the assignment below would invoke Object.prototype's\n // __proto__ setter (no-op for string values), silently dropping any\n // cookie literally named __proto__ — undoing the fix in parseRawCookies.\n const cookies: Record<string, string> = Object.create(null);\n const signedCookies: Record<string, string | false> = Object.create(null);\n\n for (const [key, val] of Object.entries(raw)) {\n // The \"s:\" prefix is the marker we add in `sign()` for HMAC-signed\n // cookies. Route those through unsign so the handler sees the original\n // value (or `false` if the signature didn't verify).\n if (val.startsWith(\"s:\") && secret) {\n signedCookies[key] = unsign(val, secret);\n } else {\n cookies[key] = val;\n }\n }\n\n // The separation is intentional signal: \"these were signed and verified, trust them more.\"\n req.cookies = cookies;\n req.signedCookies = signedCookies;\n\n res.cookie = (name: string, value: string, options: CookieOptions = {}) => {\n let finalValue = value;\n if (options.signed) {\n if (!secret)\n throw new Error(\n \"cookieParser: secret is required to use signed cookies\"\n );\n finalValue = sign(value, secret);\n }\n appendSetCookie(res, buildSetCookieHeader(name, finalValue, options));\n return res;\n };\n\n res.clearCookie = (name: string, options: CookieOptions = {}) => {\n appendSetCookie(\n res,\n buildSetCookieHeader(name, \"\", {\n ...options,\n maxAge: 0,\n expires: new Date(0)\n })\n );\n return res;\n };\n\n next();\n };\n}\n","import type { CpeakRequest, CpeakResponse, Next } from \"../types\";\nimport type { CorsOptions, OriginInput } from \"./types\";\n\n// Append a value to an existing header without overwriting prior entries\n// (e.g. compression already sets `Vary: Accept-Encoding`).\nfunction appendVary(res: CpeakResponse, value: string) {\n const existing = res.getHeader(\"Vary\");\n if (!existing) return res.setHeader(\"Vary\", value);\n const current = String(existing)\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n if (current.includes(\"*\") || current.includes(value)) return;\n res.setHeader(\"Vary\", [...current, value].join(\", \"));\n}\n\n// Determine if the given origin is allowed based on the rule.\n// Examples of what developers can specify for the rule:\n// - `true` or `*`: allow all origins\n// - `false`: disallow all origins\n// - `\"https://example.com\"`: allow only this origin\n// - `[\"https://example.com\", \"https://foo.com\"]`: allow these origins\n// - `/\\.example\\.com$/`: allow origins that match this regex\n// - `(origin) => origin === \"https://example.com\"`: custom function to determine if the origin is allowed\nasync function isAllowed(\n origin: string | undefined,\n rule: OriginInput\n): Promise<boolean> {\n if (rule === true || rule === \"*\") return true;\n if (rule === false || !origin) return false;\n if (typeof rule === \"string\") return rule === origin;\n if (Array.isArray(rule)) return rule.includes(origin);\n if (rule instanceof RegExp) return rule.test(origin);\n if (typeof rule === \"function\") return await rule(origin);\n return false;\n}\n\nconst cors = (options: CorsOptions = {}) => {\n const {\n origin = \"*\",\n methods = \"GET,HEAD,PUT,PATCH,POST,DELETE\",\n allowedHeaders,\n exposedHeaders,\n credentials = false,\n maxAge = 86400,\n preflightContinue = false,\n optionsSuccessStatus = 204\n } = options;\n\n const methodsStr = Array.isArray(methods) ? methods.join(\",\") : methods;\n const allowedHeadersStr = Array.isArray(allowedHeaders)\n ? allowedHeaders.join(\",\")\n : allowedHeaders;\n const exposedHeadersStr = Array.isArray(exposedHeaders)\n ? exposedHeaders.join(\",\")\n : exposedHeaders;\n\n return async (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n const requestOrigin = req.headers.origin;\n\n // Not a CORS request, nothing to do.\n if (!requestOrigin) return next();\n\n const allowed = await isAllowed(requestOrigin, origin);\n if (!allowed) return next();\n\n // We cannot combine Access-Control-Allow-Origin: * with\n // Access-Control-Allow-Credentials: true. Browsers will flat-out reject it.\n // Instead we'll reflect the origin.\n const allowOriginValue =\n origin === \"*\" && !credentials ? \"*\" : requestOrigin;\n res.setHeader(\"Access-Control-Allow-Origin\", allowOriginValue);\n if (allowOriginValue !== \"*\") appendVary(res, \"Origin\");\n\n if (credentials) res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n if (exposedHeadersStr)\n res.setHeader(\"Access-Control-Expose-Headers\", exposedHeadersStr);\n\n const isPreflight =\n req.method === \"OPTIONS\" &&\n req.headers[\"access-control-request-method\"] !== undefined;\n\n if (!isPreflight) return next();\n\n res.setHeader(\"Access-Control-Allow-Methods\", methodsStr);\n\n if (allowedHeadersStr) {\n res.setHeader(\"Access-Control-Allow-Headers\", allowedHeadersStr);\n } else if (origin === \"*\") {\n // If origin is *, just act like an echo chamber for requested headers. Give back whatever the browser asks for.\n const requested = req.headers[\"access-control-request-headers\"];\n if (requested) res.setHeader(\"Access-Control-Allow-Headers\", requested);\n } else {\n res.setHeader(\n \"Access-Control-Allow-Headers\",\n \"Content-Type, Authorization\"\n );\n }\n\n res.setHeader(\"Access-Control-Max-Age\", String(maxAge));\n\n if (preflightContinue) return next();\n\n res.statusCode = optionsSuccessStatus;\n res.end();\n };\n};\n\nexport { cors };\n"],"mappings":"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","names":["fs","createReadStream","pipeline","Buffer","ErrorCode","path","Buffer","Buffer","resolvedFolder","excludes","folderPath","filesMap","path","pipeline","path","pipeline","createHmac","timingSafeEqual","createHmac","timingSafeEqual","path","options","appendVary","path","fs","createReadStream","pipeline","req","res"]}
package/lib/index.ts CHANGED
@@ -13,9 +13,13 @@ import {
13
13
  } from "./internal/compression";
14
14
  import { MIME_TYPES } from "./internal/mimeTypes";
15
15
  import { Router } from "./internal/router";
16
- import { frameworkError, ErrorCode } from "./internal/errors";
16
+ import {
17
+ frameworkError,
18
+ ErrorCode,
19
+ isClientDisconnect
20
+ } from "./internal/errors";
17
21
 
18
- export { frameworkError, ErrorCode };
22
+ export { frameworkError, ErrorCode, isClientDisconnect };
19
23
 
20
24
  import type {
21
25
  StringMap,
@@ -64,6 +68,7 @@ export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessag
64
68
 
65
69
  // Send a file back to the client
66
70
  async sendFile(path: string, mime?: string) {
71
+ if (this.headersSent) return;
67
72
  if (!mime) {
68
73
  const dotIndex = path.lastIndexOf(".");
69
74
  const fileExtension = dotIndex >= 0 ? path.slice(dotIndex + 1) : "";
@@ -115,7 +120,9 @@ export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessag
115
120
  throw frameworkError(
116
121
  `Failed to send file: ${path}`,
117
122
  this.sendFile,
118
- ErrorCode.SEND_FILE_FAIL
123
+ ErrorCode.SEND_FILE_FAIL,
124
+ undefined,
125
+ isClientDisconnect(err)
119
126
  );
120
127
  }
121
128
  }
@@ -144,6 +151,7 @@ export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessag
144
151
  // Send a json data back to the client.
145
152
  // This is only good for bodies that their size is less than the highWaterMark value.
146
153
  json(data: any): Promise<void> {
154
+ if (this.headersSent) return Promise.resolve();
147
155
  const body = JSON.stringify(data);
148
156
  if (this._compression) {
149
157
  return compressAndSend(this, "application/json", body, this._compression);
@@ -153,12 +161,21 @@ export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessag
153
161
  return Promise.resolve();
154
162
  }
155
163
 
164
+ render(): Promise<void> {
165
+ throw frameworkError(
166
+ "render middleware not registered. Add render() via app.beforeEach(render()) to use res.render.",
167
+ this.render,
168
+ ErrorCode.RENDER_NOT_ENABLED
169
+ );
170
+ }
171
+
156
172
  // Explicit compression entry point. A developer can use this in any custom handler to compress arbitrary responses
157
173
  compress(
158
174
  mime: string,
159
175
  body: Buffer | string | Readable,
160
176
  size?: number
161
177
  ): Promise<void> {
178
+ if (this.headersSent) return Promise.resolve();
162
179
  if (!this._compression) {
163
180
  throw frameworkError(
164
181
  "compression is not enabled. Pass `compression` to cpeak({ compression: true | { ... } }) to use res.compress.",
@@ -212,9 +229,13 @@ export class Cpeak {
212
229
  const dispatchError = async (error: unknown) => {
213
230
  if (res.headersSent) {
214
231
  req.socket?.destroy();
215
- return;
232
+ } else {
233
+ res.setHeader("Connection", "close");
234
+ }
235
+
236
+ if (isClientDisconnect(error) && !(error as any).clientDisconnect) {
237
+ (error as any).clientDisconnect = true;
216
238
  }
217
- res.setHeader("Connection", "close");
218
239
  try {
219
240
  await this.#handleErr?.(error, req, res);
220
241
  } catch (handlerFailure) {
@@ -3,11 +3,13 @@ export function frameworkError(
3
3
  message: string,
4
4
  skipFn: Function,
5
5
  code?: string,
6
- status?: number
6
+ status?: number,
7
+ clientDisconnect?: boolean
7
8
  ) {
8
9
  const err = new Error(message) as Error & {
9
10
  code?: string;
10
11
  cpeak_err?: boolean;
12
+ clientDisconnect?: boolean;
11
13
  };
12
14
  Error.captureStackTrace(err, skipFn);
13
15
 
@@ -15,10 +17,22 @@ export function frameworkError(
15
17
 
16
18
  if (code) err.code = code;
17
19
  if (status) (err as any).status = status;
20
+ if (clientDisconnect) err.clientDisconnect = true;
18
21
 
19
22
  return err;
20
23
  }
21
24
 
25
+ const CLIENT_DISCONNECT_CODES = new Set([
26
+ "ERR_STREAM_PREMATURE_CLOSE",
27
+ "ERR_STREAM_DESTROYED",
28
+ "ECONNRESET",
29
+ "EPIPE"
30
+ ]);
31
+
32
+ export function isClientDisconnect(err: unknown): boolean {
33
+ return CLIENT_DISCONNECT_CODES.has((err as any)?.code);
34
+ }
35
+
22
36
  export enum ErrorCode {
23
37
  MISSING_MIME = "CPEAK_ERR_MISSING_MIME",
24
38
  FILE_NOT_FOUND = "CPEAK_ERR_FILE_NOT_FOUND",
@@ -28,8 +42,10 @@ export enum ErrorCode {
28
42
  PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE",
29
43
  WEAK_SECRET = "CPEAK_ERR_WEAK_SECRET",
30
44
  COMPRESSION_NOT_ENABLED = "CPEAK_ERR_COMPRESSION_NOT_ENABLED",
45
+ RENDER_NOT_ENABLED = "CPEAK_ERR_RENDER_NOT_ENABLED",
31
46
  // For router:
32
47
  DUPLICATE_ROUTE = "CPEAK_ERR_DUPLICATE_ROUTE",
33
48
  INVALID_ROUTE = "CPEAK_ERR_INVALID_ROUTE",
34
- DUPLICATE_FALLBACK = "CPEAK_ERR_DUPLICATE_FALLBACK"
49
+ DUPLICATE_FALLBACK = "CPEAK_ERR_DUPLICATE_FALLBACK",
50
+ RENDER_FAIL = "CPEAK_ERR_RENDER_FAIL"
35
51
  }
@@ -18,5 +18,14 @@ export const MIME_TYPES: StringMap = {
18
18
  gif: "image/gif",
19
19
  ico: "image/x-icon",
20
20
  json: "application/json",
21
- webmanifest: "application/manifest+json"
21
+ map: "application/json",
22
+ webmanifest: "application/manifest+json",
23
+ xml: "application/xml",
24
+ pdf: "application/pdf",
25
+ mp4: "video/mp4",
26
+ webm: "video/webm",
27
+ mp3: "audio/mpeg",
28
+ wav: "audio/wav",
29
+ webp: "image/webp",
30
+ avif: "image/avif"
22
31
  };
package/lib/types.ts CHANGED
@@ -2,6 +2,7 @@ import { IncomingMessage, ServerResponse, type Server } from "node:http";
2
2
  import type { Readable } from "node:stream";
3
3
  import type { Buffer } from "node:buffer";
4
4
  import type { CompressionOptions } from "./internal/types";
5
+ import type { CookieOptions } from "./utils/types";
5
6
  import type { CpeakIncomingMessage, CpeakServerResponse } from "./index";
6
7
 
7
8
  export type { Cpeak } from "./index";
@@ -32,18 +33,23 @@ export interface CpeakRequest<
32
33
  [key: string]: any; // allow developers to add their onw extensions (e.g. req.test)
33
34
  }
34
35
 
35
- export interface CpeakResponse extends ServerResponse {
36
+ export interface CpeakResponse<ResBody = any> extends ServerResponse {
36
37
  sendFile: (path: string, mime?: string) => Promise<void>;
37
- status: (code: number) => CpeakResponse;
38
- attachment: (filename?: string) => CpeakResponse;
39
- cookie: (name: string, value: string, options?: any) => CpeakResponse;
38
+ status: (code: number) => CpeakResponse<ResBody>;
39
+ attachment: (filename?: string) => CpeakResponse<ResBody>;
40
+ cookie: (name: string, value: string, options?: CookieOptions) => CpeakResponse<ResBody>;
40
41
  redirect: (location: string) => void;
41
- json: (data: any) => Promise<void>;
42
+ json: (data: ResBody) => Promise<void>;
42
43
  compress: (
43
44
  mime: string,
44
45
  body: Buffer | string | Readable,
45
46
  size?: number
46
47
  ) => Promise<void>;
48
+ render: (
49
+ filePath: string,
50
+ data: Record<string, unknown>,
51
+ mime?: string
52
+ ) => Promise<void>;
47
53
  [key: string]: any; // allow developers to add their onw extensions (e.g. res.test)
48
54
  }
49
55
 
@@ -63,10 +69,10 @@ export type RouteMiddleware<ReqBody = any, ReqParams = any> = (
63
69
  next: Next
64
70
  ) => unknown;
65
71
 
66
- // Route handlers: (req, res). To signal an error, throw it.
67
- export type Handler<ReqBody = any, ReqParams = any> = (
72
+ // Route handlers: (req, res)
73
+ export type Handler<ReqBody = any, ReqParams = any, ResBody = any> = (
68
74
  req: CpeakRequest<ReqBody, ReqParams>,
69
- res: CpeakResponse
75
+ res: CpeakResponse<ResBody>
70
76
  ) => unknown;
71
77
 
72
78
  // Represents a single registered route.