cpeak 2.5.0 → 2.6.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/README.md +220 -2
- package/dist/index.d.ts +55 -12
- package/dist/index.js +434 -159
- package/dist/index.js.map +1 -1
- package/lib/index.ts +158 -151
- package/lib/types.ts +6 -4
- package/lib/utils/auth.ts +170 -0
- package/lib/utils/cookieParser.ts +189 -0
- package/lib/utils/index.ts +16 -1
- package/lib/utils/serveStatic.ts +16 -5
- package/lib/utils/swagger.ts +31 -0
- package/package.json +1 -1
- /package/lib/utils/{paseJSON.ts → parseJSON.ts} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/index.ts","../lib/utils/serveStatic.ts","../lib/utils/paseJSON.ts","../lib/utils/render.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 {\n StringMap,\n CpeakRequest,\n CpeakResponse,\n Middleware,\n RouteMiddleware,\n Handler,\n RoutesMap\n} from \"./types\";\n\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}\n\nclass 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 private _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\nclass CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {\n // Send a file back to the client\n async sendFile(path: string, mime: string) {\n if (!mime) {\n throw frameworkError(\n 'MIME type is missing. Use res.sendFile(path, \"mime-type\").',\n this.sendFile,\n ErrorCode.MISSING_MIME\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 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 // Redirects to a new URL\n redirect(location: string) {\n this.writeHead(302, { Location: location });\n this.end();\n return this;\n }\n\n // Send a json data back to the client (for small json data, less than the highWaterMark)\n json(data: any) {\n // This is only good for bodies that their size is less than the highWaterMark value\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(JSON.stringify(data));\n }\n}\n\nclass Cpeak {\n private server: http.Server<\n typeof CpeakIncomingMessage,\n typeof CpeakServerResponse\n >;\n private routes: RoutesMap;\n private middleware: Middleware[];\n private _handleErr?: (\n err: unknown,\n req: CpeakRequest,\n res: CpeakResponse\n ) => void;\n\n constructor() {\n this.server = http.createServer({\n IncomingMessage: CpeakIncomingMessage,\n ServerResponse: CpeakServerResponse\n });\n this.routes = {};\n this.middleware = [];\n\n this.server.on(\"request\", async (req: CpeakRequest, res: CpeakResponse) => {\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 // 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 the handleErr to save developers from having to manually wrap every handler in try catch.\n try {\n await cb(req, res, (error) => {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n });\n } catch (error) {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n }\n } else {\n // Handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler middleware in try catch.\n try {\n await middleware[index](\n req,\n res,\n // The next function\n async (error) => {\n // this function only accepts an error argument to be more compatible with NPM modules that are built for express\n if (error) {\n res.setHeader(\"Connection\", \"close\");\n return this._handleErr?.(error, req, res);\n }\n await runHandler(req, res, middleware, cb, index + 1);\n },\n // Error handler for a route middleware\n (error) => {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n }\n );\n } catch (error) {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\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 routes = this.routes[req.method?.toLowerCase() || \"\"];\n if (routes && typeof routes[Symbol.iterator] === \"function\")\n for (const route of routes) {\n const match = urlWithoutQueries?.match(route.regex);\n\n if (match) {\n // Parse the URL path variables from the matched route (like /users/:id)\n const pathVariables = this.#extractPathVariables(\n route.path,\n match\n );\n\n // We will call this params to be more familiar with other node.js frameworks.\n req.params = pathVariables;\n\n return await runHandler(\n req,\n res,\n route.middleware,\n route.cb,\n 0\n );\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 res.setHeader(\"Connection\", \"close\");\n return this._handleErr?.(err, req, res);\n }\n await runMiddleware(req, res, middleware, index + 1);\n });\n } catch (error) {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n }\n }\n };\n\n await runMiddleware(req, res, this.middleware, 0);\n });\n }\n\n route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]) {\n if (!this.routes[method]) this.routes[method] = [];\n\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 const regex = this.#pathToRegex(path);\n this.routes[method].push({ path, regex, 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 listen(port: number, cb?: () => void) {\n return this.server.listen(port, cb);\n }\n\n close(cb?: (err?: Error) => void) {\n this.server.close(cb);\n }\n\n // ------------------------------\n // PRIVATE METHODS:\n // ------------------------------\n #pathToRegex(path: string) {\n const paramNames: string[] = [];\n const regexString =\n \"^\" +\n path.replace(/:\\w+/g, (match, offset) => {\n paramNames.push(match.slice(1));\n return \"([^/]+)\";\n }) +\n \"$\";\n\n const regex = new RegExp(regexString);\n return regex;\n }\n\n #extractPathVariables(path: string, match: RegExpMatchArray) {\n // Extract path url variable values from the matched route\n const paramNames = (path.match(/:\\w+/g) || []).map((param) =>\n param.slice(1)\n );\n const params: StringMap = {};\n paramNames.forEach((name, index) => {\n params[name] = match[index + 1];\n });\n return params;\n }\n}\n\n// Util functions\nexport { serveStatic } from \"./utils/serveStatic.js\";\nexport { parseJSON } from \"./utils/paseJSON.js\";\nexport { render } from \"./utils/render.js\";\n\nexport type {\n Cpeak,\n CpeakRequest,\n CpeakResponse,\n Next,\n HandleErr,\n Middleware,\n RouteMiddleware,\n Handler,\n RoutesMap\n} from \"./types\";\n\nexport default function cpeak() {\n return new Cpeak();\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { StringMap, CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nconst 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};\n\nconst serveStatic = (folderPath: string, newMimeTypes?: StringMap) => {\n // For new user defined mime types\n if (newMimeTypes) {\n Object.assign(MIME_TYPES, newMimeTypes);\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[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 if (Object.prototype.hasOwnProperty.call(filesMap, url)) {\n const fileRoute = filesMap[url];\n return res.sendFile(fileRoute.path, fileRoute.mime);\n }\n\n next();\n };\n};\n\nexport { serveStatic };\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/promises\";\nimport { frameworkError } from \"../\";\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 // check if mime is specified, if not return an error\n if (!mime) {\n throw frameworkError(\n `MIME type is missing. You called res.render(\"${path}\", ...) but forgot to provide the third \"mime\" argument.`,\n res.render\n );\n }\n\n let fileStr = await fs.readFile(path, \"utf-8\");\n const finalStr = renderTemplate(fileStr, data);\n res.setHeader(\"Content-Type\", mime);\n res.end(finalStr);\n };\n\n next();\n };\n};\n\nexport { render };\n"],"mappings":"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","names":["fs","folderPath","filesMap","fs","path","fs","ErrorCode","path","fs","req","res"]}
|
|
1
|
+
{"version":3,"sources":["../lib/index.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"],"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 {\n StringMap,\n CpeakRequest,\n CpeakResponse,\n Middleware,\n RouteMiddleware,\n Handler,\n RoutesMap\n} from \"./types\";\n\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}\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 // Send a file back to the client\n async sendFile(path: string, mime: string) {\n if (!mime) {\n throw frameworkError(\n 'MIME type is missing. Use res.sendFile(path, \"mime-type\").',\n this.sendFile,\n ErrorCode.MISSING_MIME\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 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 (for small json data, less than the highWaterMark)\n json(data: any) {\n // This is only good for bodies that their size is less than the highWaterMark value\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(JSON.stringify(data));\n }\n}\n\nexport class Cpeak {\n #server: http.Server<typeof CpeakIncomingMessage, typeof CpeakServerResponse>;\n #routes: RoutesMap;\n #middleware: Middleware[];\n #handleErr?: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void;\n\n constructor() {\n this.#server = http.createServer({\n IncomingMessage: CpeakIncomingMessage,\n ServerResponse: CpeakServerResponse\n });\n this.#routes = {};\n this.#middleware = [];\n\n this.#server.on(\n \"request\",\n async (req: CpeakRequest, res: CpeakResponse) => {\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 const dispatchError = (error: unknown) => {\n if (res.headersSent) {\n req.socket?.destroy();\n return;\n }\n res.setHeader(\"Connection\", \"close\");\n this.#handleErr?.(error, req, res);\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 the handleErr to save developers from having to manually wrap every handler in try catch.\n try {\n await cb(req, res, dispatchError);\n } catch (error) {\n dispatchError(error);\n }\n } else {\n // Handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler middleware in try catch.\n try {\n await middleware[index](\n req,\n res,\n // The next function\n async (error) => {\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 // Error handler for a route middleware\n dispatchError\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 routes = this.#routes[req.method?.toLowerCase() || \"\"];\n if (routes && typeof routes[Symbol.iterator] === \"function\")\n for (const route of routes) {\n const match = urlWithoutQueries?.match(route.regex);\n\n if (match) {\n // Parse the URL path variables from the matched route (like /users/:id)\n const pathVariables = this.#extractPathVariables(\n route.path,\n match\n );\n\n // We will call this params to be more familiar with other node.js frameworks.\n req.params = pathVariables;\n\n return await runHandler(\n req,\n res,\n route.middleware,\n route.cb,\n 0\n );\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 if (!this.#routes[method]) this.#routes[method] = [];\n\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 const regex = this.#pathToRegex(path);\n this.#routes[method].push({ path, regex, 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 listen(port: number, cb?: () => void) {\n return this.#server.listen(port, cb);\n }\n\n address() {\n return this.#server.address();\n }\n\n close(cb?: (err?: Error) => void) {\n this.#server.close(cb);\n }\n\n // ------------------------------\n // PRIVATE METHODS:\n // ------------------------------\n #pathToRegex(path: string) {\n const regexString =\n \"^\" + path.replace(/:\\w+/g, \"([^/]+)\").replace(/\\*/g, \".*\") + \"$\";\n\n return new RegExp(regexString);\n }\n\n #extractPathVariables(path: string, match: RegExpMatchArray) {\n // Extract path url variable values from the matched route\n const paramNames = (path.match(/:\\w+/g) || []).map((param) =>\n param.slice(1)\n );\n const params: StringMap = {};\n paramNames.forEach((name, index) => {\n params[name] = match[index + 1];\n });\n return params;\n }\n}\n\n// Util functions\nexport {\n serveStatic,\n parseJSON,\n render,\n swagger,\n auth,\n hashPassword,\n verifyPassword,\n cookieParser\n} from \"./utils\";\nexport type { AuthOptions, PbkdfOptions, CookieOptions } from \"./utils\";\n\nexport type {\n CpeakRequest,\n CpeakResponse,\n Next,\n HandleErr,\n Middleware,\n RouteMiddleware,\n Handler,\n RoutesMap\n} from \"./types\";\n\nexport default function cpeak(): Cpeak {\n return new Cpeak();\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 type { StringMap, CpeakRequest, CpeakResponse, Next } from \"../types\";\n\nconst 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};\n\nconst serveStatic = (\n folderPath: string,\n newMimeTypes?: StringMap,\n options?: { prefix?: string }\n) => {\n // For new user defined mime types\n if (newMimeTypes) {\n Object.assign(MIME_TYPES, newMimeTypes);\n }\n\n const prefix = options?.prefix ?? \"\";\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 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 // check if mime is specified, if not return an error\n if (!mime) {\n throw frameworkError(\n `MIME type is missing. You called res.render(\"${path}\", ...) but forgot to provide the third \"mime\" argument.`,\n res.render\n );\n }\n\n let fileStr = await fs.readFile(path, \"utf-8\");\n const finalStr = renderTemplate(fileStr, data);\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\";\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 interface PbkdfOptions {\n iterations?: number;\n keylen?: number;\n digest?: string;\n saltSize?: number;\n}\n\nexport interface AuthOptions extends PbkdfOptions {\n secret: string;\n saveToken: (\n tokenId: string,\n userId: string,\n expiresAt: Date\n ) => Promise<void>;\n findToken: (\n tokenId: string\n ) => Promise<{ userId: string; expiresAt: Date } | null>;\n tokenExpiry?: number;\n hmacAlgorithm?: string;\n tokenIdSize?: number;\n revokeToken?: (tokenId: string) => Promise<void>;\n}\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\";\n\nexport interface CookieOptions {\n signed?: boolean;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"strict\" | \"lax\" | \"none\";\n maxAge?: number; // ms\n expires?: Date;\n path?: string;\n domain?: string;\n}\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"],"mappings":"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","names":["fs","Buffer","Buffer","folderPath","filesMap","fs","path","fs","createHmac","timingSafeEqual","createHmac","timingSafeEqual","path","options","ErrorCode","path","fs","req","res"]}
|
package/lib/index.ts
CHANGED
|
@@ -40,38 +40,39 @@ export enum ErrorCode {
|
|
|
40
40
|
NOT_A_FILE = "CPEAK_ERR_NOT_A_FILE",
|
|
41
41
|
SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL",
|
|
42
42
|
INVALID_JSON = "CPEAK_ERR_INVALID_JSON",
|
|
43
|
-
PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE"
|
|
43
|
+
PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE",
|
|
44
|
+
WEAK_SECRET = "CPEAK_ERR_WEAK_SECRET"
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
class CpeakIncomingMessage extends http.IncomingMessage {
|
|
47
|
+
export class CpeakIncomingMessage extends http.IncomingMessage {
|
|
47
48
|
// We define body and params here for better V8 optimization (not changing the shape of the object at runtime)
|
|
48
49
|
public body: any = undefined;
|
|
49
50
|
public params: StringMap = {};
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
#query?: StringMap;
|
|
52
53
|
|
|
53
54
|
// Parse the URL parameters (like /users?key1=value1&key2=value2)
|
|
54
55
|
// We will call this query to be more familiar with other node.js frameworks.
|
|
55
56
|
// This is a getter method (accessed like a property)
|
|
56
57
|
get query(): StringMap {
|
|
57
58
|
// This way if a developer writes req.query multiple times, we don't parse it multiple times
|
|
58
|
-
if (this
|
|
59
|
+
if (this.#query) return this.#query;
|
|
59
60
|
|
|
60
61
|
const url = this.url || "";
|
|
61
62
|
const qIndex = url.indexOf("?");
|
|
62
63
|
|
|
63
64
|
if (qIndex === -1) {
|
|
64
|
-
this
|
|
65
|
+
this.#query = {};
|
|
65
66
|
} else {
|
|
66
67
|
const searchParams = new URLSearchParams(url.substring(qIndex + 1));
|
|
67
|
-
this
|
|
68
|
+
this.#query = Object.fromEntries(searchParams.entries());
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
return this
|
|
71
|
+
return this.#query;
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
|
|
75
|
+
export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
|
|
75
76
|
// Send a file back to the client
|
|
76
77
|
async sendFile(path: string, mime: string) {
|
|
77
78
|
if (!mime) {
|
|
@@ -120,11 +121,19 @@ class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
|
|
|
120
121
|
return this;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
// Set the Content-Disposition header to prompt the user to download a file
|
|
125
|
+
attachment(filename?: string) {
|
|
126
|
+
const contentDisposition = filename
|
|
127
|
+
? `attachment; filename="${filename}"`
|
|
128
|
+
: "attachment";
|
|
129
|
+
this.setHeader("Content-Disposition", contentDisposition);
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
123
133
|
// Redirects to a new URL
|
|
124
134
|
redirect(location: string) {
|
|
125
135
|
this.writeHead(302, { Location: location });
|
|
126
136
|
this.end();
|
|
127
|
-
return this;
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
// Send a json data back to the client (for small json data, less than the highWaterMark)
|
|
@@ -135,142 +144,136 @@ class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
|
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
|
|
138
|
-
class Cpeak {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
private routes: RoutesMap;
|
|
144
|
-
private middleware: Middleware[];
|
|
145
|
-
private _handleErr?: (
|
|
146
|
-
err: unknown,
|
|
147
|
-
req: CpeakRequest,
|
|
148
|
-
res: CpeakResponse
|
|
149
|
-
) => void;
|
|
147
|
+
export class Cpeak {
|
|
148
|
+
#server: http.Server<typeof CpeakIncomingMessage, typeof CpeakServerResponse>;
|
|
149
|
+
#routes: RoutesMap;
|
|
150
|
+
#middleware: Middleware[];
|
|
151
|
+
#handleErr?: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void;
|
|
150
152
|
|
|
151
153
|
constructor() {
|
|
152
|
-
this
|
|
154
|
+
this.#server = http.createServer({
|
|
153
155
|
IncomingMessage: CpeakIncomingMessage,
|
|
154
156
|
ServerResponse: CpeakServerResponse
|
|
155
157
|
});
|
|
156
|
-
this
|
|
157
|
-
this
|
|
158
|
-
|
|
159
|
-
this
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
qIndex
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
index: number
|
|
172
|
-
) => {
|
|
173
|
-
// Our exit point...
|
|
174
|
-
if (index === middleware.length) {
|
|
175
|
-
// Call the route handler with the modified req and res objects.
|
|
176
|
-
// Also handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler in try catch.
|
|
177
|
-
try {
|
|
178
|
-
await cb(req, res, (error) => {
|
|
179
|
-
res.setHeader("Connection", "close");
|
|
180
|
-
this._handleErr?.(error, req, res);
|
|
181
|
-
});
|
|
182
|
-
} catch (error) {
|
|
183
|
-
res.setHeader("Connection", "close");
|
|
184
|
-
this._handleErr?.(error, req, res);
|
|
158
|
+
this.#routes = {};
|
|
159
|
+
this.#middleware = [];
|
|
160
|
+
|
|
161
|
+
this.#server.on(
|
|
162
|
+
"request",
|
|
163
|
+
async (req: CpeakRequest, res: CpeakResponse) => {
|
|
164
|
+
// Get the url without the URL parameters (query strings)
|
|
165
|
+
const qIndex = req.url?.indexOf("?");
|
|
166
|
+
const urlWithoutQueries =
|
|
167
|
+
qIndex === -1 ? req.url || "" : req.url?.substring(0, qIndex);
|
|
168
|
+
|
|
169
|
+
const dispatchError = (error: unknown) => {
|
|
170
|
+
if (res.headersSent) {
|
|
171
|
+
req.socket?.destroy();
|
|
172
|
+
return;
|
|
185
173
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
174
|
+
res.setHeader("Connection", "close");
|
|
175
|
+
this.#handleErr?.(error, req, res);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Run all the specific middleware functions for that router only and then run the handler
|
|
179
|
+
const runHandler = async (
|
|
180
|
+
req: CpeakRequest,
|
|
181
|
+
res: CpeakResponse,
|
|
182
|
+
middleware: RouteMiddleware[],
|
|
183
|
+
cb: Handler,
|
|
184
|
+
index: number
|
|
185
|
+
) => {
|
|
186
|
+
// Our exit point...
|
|
187
|
+
if (index === middleware.length) {
|
|
188
|
+
// Call the route handler with the modified req and res objects.
|
|
189
|
+
// Also handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler in try catch.
|
|
190
|
+
try {
|
|
191
|
+
await cb(req, res, dispatchError);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
dispatchError(error);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// Handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler middleware in try catch.
|
|
197
|
+
try {
|
|
198
|
+
await middleware[index](
|
|
199
|
+
req,
|
|
200
|
+
res,
|
|
201
|
+
// The next function
|
|
202
|
+
async (error) => {
|
|
203
|
+
// this function only accepts an error argument to be more compatible with NPM modules that are built for express
|
|
204
|
+
if (error) {
|
|
205
|
+
return dispatchError(error);
|
|
206
|
+
}
|
|
207
|
+
await runHandler(req, res, middleware, cb, index + 1);
|
|
208
|
+
},
|
|
209
|
+
// Error handler for a route middleware
|
|
210
|
+
dispatchError
|
|
211
|
+
);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
dispatchError(error);
|
|
214
|
+
}
|
|
210
215
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Run all the middleware functions (beforeEach functions) before we run the corresponding route
|
|
219
|
+
const runMiddleware = async (
|
|
220
|
+
req: CpeakRequest,
|
|
221
|
+
res: CpeakResponse,
|
|
222
|
+
middleware: Middleware[],
|
|
223
|
+
index: number
|
|
224
|
+
) => {
|
|
225
|
+
// Our exit point...
|
|
226
|
+
if (index === middleware.length) {
|
|
227
|
+
const routes = this.#routes[req.method?.toLowerCase() || ""];
|
|
228
|
+
if (routes && typeof routes[Symbol.iterator] === "function")
|
|
229
|
+
for (const route of routes) {
|
|
230
|
+
const match = urlWithoutQueries?.match(route.regex);
|
|
231
|
+
|
|
232
|
+
if (match) {
|
|
233
|
+
// Parse the URL path variables from the matched route (like /users/:id)
|
|
234
|
+
const pathVariables = this.#extractPathVariables(
|
|
235
|
+
route.path,
|
|
236
|
+
match
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// We will call this params to be more familiar with other node.js frameworks.
|
|
240
|
+
req.params = pathVariables;
|
|
241
|
+
|
|
242
|
+
return await runHandler(
|
|
243
|
+
req,
|
|
244
|
+
res,
|
|
245
|
+
route.middleware,
|
|
246
|
+
route.cb,
|
|
247
|
+
0
|
|
248
|
+
);
|
|
249
|
+
}
|
|
245
250
|
}
|
|
246
|
-
}
|
|
247
251
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this._handleErr?.(error, req, res);
|
|
252
|
+
// If the requested route dose not exist, return 404
|
|
253
|
+
return res
|
|
254
|
+
.status(404)
|
|
255
|
+
.json({ error: `Cannot ${req.method} ${urlWithoutQueries}` });
|
|
256
|
+
} else {
|
|
257
|
+
try {
|
|
258
|
+
await middleware[index](req, res, async (err?: unknown) => {
|
|
259
|
+
if (err) {
|
|
260
|
+
return dispatchError(err);
|
|
261
|
+
}
|
|
262
|
+
await runMiddleware(req, res, middleware, index + 1);
|
|
263
|
+
});
|
|
264
|
+
} catch (error) {
|
|
265
|
+
dispatchError(error);
|
|
266
|
+
}
|
|
264
267
|
}
|
|
265
|
-
}
|
|
266
|
-
};
|
|
268
|
+
};
|
|
267
269
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
await runMiddleware(req, res, this.#middleware, 0);
|
|
271
|
+
}
|
|
272
|
+
);
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]) {
|
|
273
|
-
if (!this
|
|
276
|
+
if (!this.#routes[method]) this.#routes[method] = [];
|
|
274
277
|
|
|
275
278
|
// The last argument should always be our handler
|
|
276
279
|
const cb = args.pop() as Handler;
|
|
@@ -283,40 +286,37 @@ class Cpeak {
|
|
|
283
286
|
const middleware = args.flat() as RouteMiddleware[];
|
|
284
287
|
|
|
285
288
|
const regex = this.#pathToRegex(path);
|
|
286
|
-
this
|
|
289
|
+
this.#routes[method].push({ path, regex, middleware, cb });
|
|
287
290
|
}
|
|
288
291
|
|
|
289
292
|
beforeEach(cb: Middleware) {
|
|
290
|
-
this
|
|
293
|
+
this.#middleware.push(cb);
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
handleErr(cb: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void) {
|
|
294
|
-
this
|
|
297
|
+
this.#handleErr = cb;
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
listen(port: number, cb?: () => void) {
|
|
298
|
-
return this
|
|
301
|
+
return this.#server.listen(port, cb);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
address() {
|
|
305
|
+
return this.#server.address();
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
close(cb?: (err?: Error) => void) {
|
|
302
|
-
this
|
|
309
|
+
this.#server.close(cb);
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
// ------------------------------
|
|
306
313
|
// PRIVATE METHODS:
|
|
307
314
|
// ------------------------------
|
|
308
315
|
#pathToRegex(path: string) {
|
|
309
|
-
const paramNames: string[] = [];
|
|
310
316
|
const regexString =
|
|
311
|
-
"^" +
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return "([^/]+)";
|
|
315
|
-
}) +
|
|
316
|
-
"$";
|
|
317
|
-
|
|
318
|
-
const regex = new RegExp(regexString);
|
|
319
|
-
return regex;
|
|
317
|
+
"^" + path.replace(/:\w+/g, "([^/]+)").replace(/\*/g, ".*") + "$";
|
|
318
|
+
|
|
319
|
+
return new RegExp(regexString);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
#extractPathVariables(path: string, match: RegExpMatchArray) {
|
|
@@ -333,12 +333,19 @@ class Cpeak {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// Util functions
|
|
336
|
-
export {
|
|
337
|
-
|
|
338
|
-
|
|
336
|
+
export {
|
|
337
|
+
serveStatic,
|
|
338
|
+
parseJSON,
|
|
339
|
+
render,
|
|
340
|
+
swagger,
|
|
341
|
+
auth,
|
|
342
|
+
hashPassword,
|
|
343
|
+
verifyPassword,
|
|
344
|
+
cookieParser
|
|
345
|
+
} from "./utils";
|
|
346
|
+
export type { AuthOptions, PbkdfOptions, CookieOptions } from "./utils";
|
|
339
347
|
|
|
340
348
|
export type {
|
|
341
|
-
Cpeak,
|
|
342
349
|
CpeakRequest,
|
|
343
350
|
CpeakResponse,
|
|
344
351
|
Next,
|
|
@@ -349,6 +356,6 @@ export type {
|
|
|
349
356
|
RoutesMap
|
|
350
357
|
} from "./types";
|
|
351
358
|
|
|
352
|
-
export default function cpeak() {
|
|
359
|
+
export default function cpeak(): Cpeak {
|
|
353
360
|
return new Cpeak();
|
|
354
361
|
}
|
package/lib/types.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import cpeak from "./index";
|
|
3
2
|
|
|
4
|
-
export type Cpeak
|
|
3
|
+
export type { Cpeak } from "./index";
|
|
5
4
|
|
|
6
5
|
// Extending Node.js's Request and Response objects to add our custom properties
|
|
7
6
|
export type StringMap = Record<string, string>;
|
|
@@ -12,15 +11,18 @@ export interface CpeakRequest<
|
|
|
12
11
|
> extends IncomingMessage {
|
|
13
12
|
params: StringMap;
|
|
14
13
|
query: ReqQueries;
|
|
15
|
-
// vars?: StringMap;
|
|
16
14
|
body?: ReqBody;
|
|
15
|
+
cookies?: StringMap;
|
|
16
|
+
signedCookies?: Record<string, string | false>;
|
|
17
17
|
[key: string]: any; // allow developers to add their onw extensions (e.g. req.test)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface CpeakResponse extends ServerResponse {
|
|
21
21
|
sendFile: (path: string, mime: string) => Promise<void>;
|
|
22
22
|
status: (code: number) => CpeakResponse;
|
|
23
|
-
|
|
23
|
+
attachment: (filename?: string) => CpeakResponse;
|
|
24
|
+
cookie: (name: string, value: string, options?: any) => CpeakResponse;
|
|
25
|
+
redirect: (location: string) => void;
|
|
24
26
|
json: (data: any) => void;
|
|
25
27
|
[key: string]: any; // allow developers to add their onw extensions (e.g. res.test)
|
|
26
28
|
}
|