cpeak 2.4.1 → 2.4.2

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 CHANGED
@@ -8,8 +8,6 @@ This project is designed to be improved until it's ready for use in complex prod
8
8
 
9
9
  This is an educational project that was started as part of the [Understanding Node.js: Core Concepts](https://www.udemy.com/course/understanding-nodejs-core-concepts/?referralCode=0BC21AC4DD6958AE6A95) course. If you want to learn how to build a framework like this, and get to a point where you can build things like this yourself, check out this course!
10
10
 
11
- <em>This is the current demo, and the development of the project will begin starting from **September 2025.**</em>
12
-
13
11
  ## Why Cpeak?
14
12
 
15
13
  - **Minimalism**: No unnecessary bloat, with zero dependencies. Just the core essentials you need to build fast and reliable applications.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import http, { IncomingMessage, ServerResponse } from 'node:http';
2
2
 
3
- type Cpeak$1 = InstanceType<typeof Cpeak>;
3
+ type Cpeak$1 = ReturnType<typeof cpeak>;
4
4
  type StringMap = Record<string, string>;
5
5
  interface CpeakRequest<ReqBody = any, ReqParams = any> extends IncomingMessage {
6
6
  params: ReqParams;
@@ -31,10 +31,10 @@ interface RoutesMap {
31
31
  [method: string]: Route[];
32
32
  }
33
33
 
34
- declare const parseJSON: (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
35
-
36
34
  declare const serveStatic: (folderPath: string, newMimeTypes?: StringMap) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void | Promise<void>;
37
35
 
36
+ declare const parseJSON: (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
37
+
38
38
  declare const render: () => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
39
39
 
40
40
  declare function frameworkError(message: string, skipFn: Function, code?: string): Error & {
@@ -60,4 +60,6 @@ declare class Cpeak {
60
60
  close(cb?: (err?: Error) => void): void;
61
61
  }
62
62
 
63
- export { type Cpeak$1 as Cpeak, type CpeakRequest, type CpeakResponse, ErrorCode, type HandleErr, type Handler, type Middleware, type Next, type RouteMiddleware, type RoutesMap, Cpeak as default, frameworkError, parseJSON, render, serveStatic };
63
+ declare function cpeak(): Cpeak;
64
+
65
+ export { type Cpeak$1 as Cpeak, type CpeakRequest, type CpeakResponse, ErrorCode, type HandleErr, type Handler, type Middleware, type Next, type RouteMiddleware, type RoutesMap, cpeak as default, frameworkError, parseJSON, render, serveStatic };
package/dist/index.js CHANGED
@@ -4,24 +4,6 @@ import fs3 from "fs/promises";
4
4
  import { createReadStream } from "fs";
5
5
  import { pipeline } from "stream/promises";
6
6
 
7
- // lib/utils/parseJSON.ts
8
- var parseJSON = (req, res, next) => {
9
- function isJSON(contentType = "") {
10
- const [type] = contentType.split(";");
11
- return type.trim().toLowerCase() === "application/json" || /\+json$/i.test(type.trim());
12
- }
13
- if (!isJSON(req.headers["content-type"])) return next();
14
- let body = "";
15
- req.on("data", (chunk) => {
16
- body += chunk.toString("utf-8");
17
- });
18
- req.on("end", () => {
19
- body = JSON.parse(body);
20
- req.body = body;
21
- return next();
22
- });
23
- };
24
-
25
7
  // lib/utils/serveStatic.ts
26
8
  import fs from "fs";
27
9
  import path from "path";
@@ -83,6 +65,24 @@ var serveStatic = (folderPath, newMimeTypes) => {
83
65
  };
84
66
  };
85
67
 
68
+ // lib/utils/parseJSON.ts
69
+ var parseJSON = (req, res, next) => {
70
+ function isJSON(contentType = "") {
71
+ const [type] = contentType.split(";");
72
+ return type.trim().toLowerCase() === "application/json" || /\+json$/i.test(type.trim());
73
+ }
74
+ if (!isJSON(req.headers["content-type"])) return next();
75
+ let body = "";
76
+ req.on("data", (chunk) => {
77
+ body += chunk.toString("utf-8");
78
+ });
79
+ req.on("end", () => {
80
+ body = JSON.parse(body);
81
+ req.body = body;
82
+ return next();
83
+ });
84
+ };
85
+
86
86
  // lib/utils/render.ts
87
87
  import fs2 from "fs/promises";
88
88
  function renderTemplate(templateStr, data) {
@@ -108,6 +108,7 @@ function renderTemplate(templateStr, data) {
108
108
  return result.join("");
109
109
  }
110
110
  var render = () => {
111
+ console.log("render.ts loaded");
111
112
  return function(req, res, next) {
112
113
  res.render = async (path2, data, mime) => {
113
114
  if (!mime) {
@@ -221,23 +222,34 @@ var Cpeak = class {
221
222
  this._handleErr?.(error, req2, res2);
222
223
  }
223
224
  } else {
224
- middleware[index](
225
- req2,
226
- res2,
227
- // The next function
228
- (error) => {
229
- if (error) {
225
+ try {
226
+ const middlewareResult = middleware[index](
227
+ req2,
228
+ res2,
229
+ // The next function
230
+ (error) => {
231
+ if (error) {
232
+ res2.setHeader("Connection", "close");
233
+ return this._handleErr?.(error, req2, res2);
234
+ }
235
+ runHandler(req2, res2, middleware, cb, index + 1);
236
+ },
237
+ // Error handler for a route middleware
238
+ (error) => {
230
239
  res2.setHeader("Connection", "close");
231
- return this._handleErr?.(error, req2, res2);
240
+ this._handleErr?.(error, req2, res2);
232
241
  }
233
- runHandler(req2, res2, middleware, cb, index + 1);
234
- },
235
- // Error handler for a route middleware
236
- (error) => {
237
- res2.setHeader("Connection", "close");
238
- this._handleErr?.(error, req2, res2);
242
+ );
243
+ if (middlewareResult && typeof middlewareResult.then === "function") {
244
+ middlewareResult.catch((error) => {
245
+ res2.setHeader("Connection", "close");
246
+ this._handleErr?.(error, req2, res2);
247
+ });
239
248
  }
240
- );
249
+ } catch (error) {
250
+ res2.setHeader("Connection", "close");
251
+ this._handleErr?.(error, req2, res2);
252
+ }
241
253
  }
242
254
  };
243
255
  const runMiddleware = (req2, res2, middleware, index) => {
@@ -307,10 +319,12 @@ var Cpeak = class {
307
319
  return vars;
308
320
  }
309
321
  };
310
- var index_default = Cpeak;
322
+ function cpeak() {
323
+ return new Cpeak();
324
+ }
311
325
  export {
312
326
  ErrorCode,
313
- index_default as default,
327
+ cpeak as default,
314
328
  frameworkError,
315
329
  parseJSON,
316
330
  render,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../lib/index.ts","../lib/utils/parseJSON.ts","../lib/utils/serveStatic.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 { serveStatic, parseJSON, render } from \"./utils\";\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) {\n const err = new Error(message) as Error & { code?: string };\n Error.captureStackTrace(err, skipFn);\n\n if (code) err.code = code;\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}\n\nclass Cpeak {\n private server: http.Server;\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 this.routes = {};\n this.middleware = [];\n\n this.server.on(\"request\", (req: CpeakRequest, res: CpeakResponse) => {\n // Send a file back to the client\n res.sendFile = async (path: string, mime: string) => {\n if (!mime) {\n throw frameworkError(\n 'MIME type is missing. Use res.sendFile(path, \"mime-type\").',\n res.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 res.sendFile,\n ErrorCode.NOT_A_FILE\n );\n }\n\n res.setHeader(\"Content-Type\", mime);\n res.setHeader(\"Content-Length\", String(stat.size));\n\n // Properly propagate stream errors and respect backpressure\n await pipeline(createReadStream(path), res);\n } catch (err: any) {\n if (err?.code === \"ENOENT\") {\n throw frameworkError(\n `File not found: ${path}`,\n res.sendFile,\n ErrorCode.FILE_NOT_FOUND\n );\n }\n\n throw frameworkError(\n `Failed to send file: ${path}`,\n res.sendFile,\n ErrorCode.SEND_FILE_FAIL\n );\n }\n };\n\n // Set the status code of the response\n res.status = (code: number) => {\n res.statusCode = code;\n return res;\n };\n\n // Redirects to a new URL\n res.redirect = (location: string) => {\n res.writeHead(302, { Location: location });\n res.end();\n return res;\n };\n\n // Send a json data back to the client (for small json data, less than the highWaterMark)\n res.json = (data: any) => {\n // This is only good for bodies that their size is less than the highWaterMark value\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(data));\n };\n\n // Get the url without the URL parameters\n const urlWithoutParams = req.url?.split(\"?\")[0];\n\n // Parse the URL parameters (like /users?key1=value1&key2=value2)\n // We put this here to also parse them for all the middleware functions\n const params = new URLSearchParams(req.url?.split(\"?\")[1]);\n\n const paramsObject = Object.fromEntries(params.entries());\n\n req.params = paramsObject;\n req.query = paramsObject; // only for compatibility with frameworks built for express\n\n // Run all the specific middleware functions for that router only and then run the handler\n const runHandler = (\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 const handlerResult = cb(req, res, (error) => {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n });\n\n if (handlerResult && typeof handlerResult.then === \"function\") {\n handlerResult.catch((error) => {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n });\n }\n\n return handlerResult;\n } catch (error) {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n }\n } else {\n middleware[index](\n req,\n res,\n // The next function\n (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 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 }\n };\n\n // Run all the middleware functions (beforeEach functions) before we run the corresponding route\n const runMiddleware = (\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 = urlWithoutParams?.match(route.regex);\n\n if (match) {\n // Parse the URL variables from the matched route (like /users/:id)\n const vars = this.#extractVars(route.path, match);\n req.vars = vars;\n\n return runHandler(req, res, route.middleware, route.cb, 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} ${urlWithoutParams}` });\n } else {\n middleware[index](req, res, () => {\n runMiddleware(req, res, middleware, index + 1);\n });\n }\n };\n\n 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 varNames: string[] = [];\n const regexString =\n \"^\" +\n path.replace(/:\\w+/g, (match, offset) => {\n varNames.push(match.slice(1));\n return \"([^/]+)\";\n }) +\n \"$\";\n\n const regex = new RegExp(regexString);\n return regex;\n }\n\n #extractVars(path: string, match: RegExpMatchArray) {\n // Extract url variable values from the matched route\n const varNames = (path.match(/:\\w+/g) || []).map((varParam) =>\n varParam.slice(1)\n );\n const vars: StringMap = {};\n varNames.forEach((name, index) => {\n vars[name] = match[index + 1];\n });\n return vars;\n }\n}\n\n// Util functions\nexport { serveStatic, parseJSON, render };\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 Cpeak;\n","import type { CpeakRequest, CpeakResponse, Next } from \"../types\";\n\n// Parsing JSON\nconst parseJSON = (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n // This is only good for bodies that their size is less than the highWaterMark value\n\n function isJSON(contentType: string = \"\") {\n // Remove any params like \"; charset=UTF-8\"\n const [type] = contentType.split(\";\");\n return (\n type.trim().toLowerCase() === \"application/json\" ||\n /\\+json$/i.test(type.trim())\n );\n }\n\n if (!isJSON(req.headers[\"content-type\"] as string)) return next();\n\n let body = \"\";\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString(\"utf-8\");\n });\n\n req.on(\"end\", () => {\n body = JSON.parse(body);\n req.body = body;\n return next();\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};\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 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/serveStatic.ts","../lib/utils/parseJSON.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) {\n const err = new Error(message) as Error & { code?: string };\n Error.captureStackTrace(err, skipFn);\n\n if (code) err.code = code;\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}\n\nclass Cpeak {\n private server: http.Server;\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 this.routes = {};\n this.middleware = [];\n\n this.server.on(\"request\", (req: CpeakRequest, res: CpeakResponse) => {\n // Send a file back to the client\n res.sendFile = async (path: string, mime: string) => {\n if (!mime) {\n throw frameworkError(\n 'MIME type is missing. Use res.sendFile(path, \"mime-type\").',\n res.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 res.sendFile,\n ErrorCode.NOT_A_FILE\n );\n }\n\n res.setHeader(\"Content-Type\", mime);\n res.setHeader(\"Content-Length\", String(stat.size));\n\n // Properly propagate stream errors and respect backpressure\n await pipeline(createReadStream(path), res);\n } catch (err: any) {\n if (err?.code === \"ENOENT\") {\n throw frameworkError(\n `File not found: ${path}`,\n res.sendFile,\n ErrorCode.FILE_NOT_FOUND\n );\n }\n\n throw frameworkError(\n `Failed to send file: ${path}`,\n res.sendFile,\n ErrorCode.SEND_FILE_FAIL\n );\n }\n };\n\n // Set the status code of the response\n res.status = (code: number) => {\n res.statusCode = code;\n return res;\n };\n\n // Redirects to a new URL\n res.redirect = (location: string) => {\n res.writeHead(302, { Location: location });\n res.end();\n return res;\n };\n\n // Send a json data back to the client (for small json data, less than the highWaterMark)\n res.json = (data: any) => {\n // This is only good for bodies that their size is less than the highWaterMark value\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(data));\n };\n\n // Get the url without the URL parameters\n const urlWithoutParams = req.url?.split(\"?\")[0];\n\n // Parse the URL parameters (like /users?key1=value1&key2=value2)\n // We put this here to also parse them for all the middleware functions\n const params = new URLSearchParams(req.url?.split(\"?\")[1]);\n\n const paramsObject = Object.fromEntries(params.entries());\n\n req.params = paramsObject;\n req.query = paramsObject; // only for compatibility with frameworks built for express\n\n // Run all the specific middleware functions for that router only and then run the handler\n const runHandler = (\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 const handlerResult = cb(req, res, (error) => {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n });\n\n if (handlerResult && typeof handlerResult.then === \"function\") {\n handlerResult.catch((error) => {\n res.setHeader(\"Connection\", \"close\");\n this._handleErr?.(error, req, res);\n });\n }\n\n return handlerResult;\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 const middlewareResult = middleware[index](\n req,\n res,\n // The next function\n (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 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\n // If the middleware is async, handle the promise rejection\n if (\n middlewareResult &&\n typeof middlewareResult.then === \"function\"\n ) {\n middlewareResult.catch((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 = (\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 = urlWithoutParams?.match(route.regex);\n\n if (match) {\n // Parse the URL variables from the matched route (like /users/:id)\n const vars = this.#extractVars(route.path, match);\n req.vars = vars;\n\n return runHandler(req, res, route.middleware, route.cb, 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} ${urlWithoutParams}` });\n } else {\n middleware[index](req, res, () => {\n runMiddleware(req, res, middleware, index + 1);\n });\n }\n };\n\n 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 varNames: string[] = [];\n const regexString =\n \"^\" +\n path.replace(/:\\w+/g, (match, offset) => {\n varNames.push(match.slice(1));\n return \"([^/]+)\";\n }) +\n \"$\";\n\n const regex = new RegExp(regexString);\n return regex;\n }\n\n #extractVars(path: string, match: RegExpMatchArray) {\n // Extract url variable values from the matched route\n const varNames = (path.match(/:\\w+/g) || []).map((varParam) =>\n varParam.slice(1)\n );\n const vars: StringMap = {};\n varNames.forEach((name, index) => {\n vars[name] = match[index + 1];\n });\n return vars;\n }\n}\n\n// Util functions\nexport { serveStatic } from \"./utils/serveStatic.js\";\nexport { parseJSON } from \"./utils/parseJSON.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\";\n\n// Parsing JSON\nconst parseJSON = (req: CpeakRequest, res: CpeakResponse, next: Next) => {\n // This is only good for bodies that their size is less than the highWaterMark value\n\n function isJSON(contentType: string = \"\") {\n // Remove any params like \"; charset=UTF-8\"\n const [type] = contentType.split(\";\");\n return (\n type.trim().toLowerCase() === \"application/json\" ||\n /\\+json$/i.test(type.trim())\n );\n }\n\n if (!isJSON(req.headers[\"content-type\"] as string)) return next();\n\n let body = \"\";\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString(\"utf-8\");\n });\n\n req.on(\"end\", () => {\n body = JSON.parse(body);\n req.body = body;\n return next();\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 console.log(\"render.ts loaded\");\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":";AAAA,OAAO,UAAU;AACjB,OAAOA,SAAQ;AACf,SAAS,wBAAwB;AACjC,SAAS,gBAAgB;;;ACHzB,OAAO,QAAQ;AACf,OAAO,UAAU;AAIjB,IAAM,aAAwB;AAAA,EAC5B,MAAM;AAAA,EACN,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAM,cAAc,CAAC,YAAoB,iBAA6B;AAEpE,MAAI,cAAc;AAChB,WAAO,OAAO,YAAY,YAAY;AAAA,EACxC;AAEA,WAAS,cAAcC,aAAoB,cAAsB;AAC/D,UAAM,cAAwB,CAAC;AAG/B,UAAM,QAAQ,GAAG,YAAYA,WAAU;AAGvC,eAAW,QAAQ,OAAO;AACxB,YAAM,WAAW,KAAK,KAAKA,aAAY,IAAI;AAG3C,UAAI,GAAG,SAAS,QAAQ,EAAE,YAAY,GAAG;AAEvC,cAAM,iBAAiB,cAAc,UAAU,YAAY;AAC3D,oBAAY,KAAK,GAAG,cAAc;AAAA,MACpC,OAAO;AAEL,cAAM,eAAe,KAAK,SAAS,cAAc,QAAQ;AACzD,cAAM,gBAAgB,KAAK,QAAQ,IAAI,EAAE,MAAM,CAAC;AAChD,YAAI,WAAW,aAAa,EAAG,aAAY,KAAK,MAAM,YAAY;AAAA,MACpE;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,uBAAuB,CAAC,eAAyB;AACrD,UAAMC,YAA2D,CAAC;AAClE,eAAW,QAAQ,YAAY;AAC7B,YAAM,gBAAgB,KAAK,QAAQ,IAAI,EAAE,MAAM,CAAC;AAChD,MAAAA,UAAS,IAAI,IAAI;AAAA,QACf,MAAM,aAAa;AAAA,QACnB,MAAM,WAAW,aAAa;AAAA,MAChC;AAAA,IACF;AACA,WAAOA;AAAA,EACT;AAGA,QAAM,WAAW,qBAAqB,cAAc,YAAY,UAAU,CAAC;AAE3E,SAAO,SAAU,KAAmB,KAAoB,MAAY;AAClE,UAAM,MAAM,IAAI;AAChB,QAAI,OAAO,QAAQ,SAAU,QAAO,KAAK;AAEzC,QAAI,OAAO,UAAU,eAAe,KAAK,UAAU,GAAG,GAAG;AACvD,YAAM,YAAY,SAAS,GAAG;AAC9B,aAAO,IAAI,SAAS,UAAU,MAAM,UAAU,IAAI;AAAA,IACpD;AAEA,SAAK;AAAA,EACP;AACF;;;AC5EA,IAAM,YAAY,CAAC,KAAmB,KAAoB,SAAe;AAGvE,WAAS,OAAO,cAAsB,IAAI;AAExC,UAAM,CAAC,IAAI,IAAI,YAAY,MAAM,GAAG;AACpC,WACE,KAAK,KAAK,EAAE,YAAY,MAAM,sBAC9B,WAAW,KAAK,KAAK,KAAK,CAAC;AAAA,EAE/B;AAEA,MAAI,CAAC,OAAO,IAAI,QAAQ,cAAc,CAAW,EAAG,QAAO,KAAK;AAEhE,MAAI,OAAO;AACX,MAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,YAAQ,MAAM,SAAS,OAAO;AAAA,EAChC,CAAC;AAED,MAAI,GAAG,OAAO,MAAM;AAClB,WAAO,KAAK,MAAM,IAAI;AACtB,QAAI,OAAO;AACX,WAAO,KAAK;AAAA,EACd,CAAC;AACH;;;AC3BA,OAAOC,SAAQ;AAIf,SAAS,eACP,aACA,MACQ;AAER,MAAI,SAA+B,CAAC;AAEpC,MAAI,eAAe;AAEnB,SAAO,eAAe,YAAY,QAAQ;AAExC,UAAM,WAAW,YAAY,QAAQ,MAAM,YAAY;AACvD,QAAI,aAAa,IAAI;AAEnB,aAAO,KAAK,YAAY,MAAM,YAAY,CAAC;AAC3C;AAAA,IACF;AAGA,WAAO,KAAK,YAAY,MAAM,cAAc,QAAQ,CAAC;AAGrD,UAAM,SAAS,YAAY,QAAQ,MAAM,QAAQ;AACjD,QAAI,WAAW,IAAI;AAEjB,aAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC;AAAA,IACF;AAGA,UAAM,UAAU,YAAY,MAAM,WAAW,GAAG,MAAM,EAAE,KAAK;AAG7D,UAAM,cAAc,KAAK,OAAO,MAAM,SAAY,KAAK,OAAO,IAAI;AAGlE,WAAO,KAAK,WAAW;AAGvB,mBAAe,SAAS;AAAA,EAC1B;AAGA,SAAO,OAAO,KAAK,EAAE;AACvB;AAOA,IAAM,SAAS,MAAM;AACnB,UAAQ,IAAI,kBAAkB;AAC9B,SAAO,SAAU,KAAmB,KAAoB,MAAkB;AACxE,QAAI,SAAS,OACXC,OACA,MACA,SACG;AAEH,UAAI,CAAC,MAAM;AACT,cAAM;AAAA,UACJ,gDAAgDA,KAAI;AAAA,UACpD,IAAI;AAAA,QACN;AAAA,MACF;AAEA,UAAI,UAAU,MAAMC,IAAG,SAASD,OAAM,OAAO;AAC7C,YAAM,WAAW,eAAe,SAAS,IAAI;AAC7C,UAAI,UAAU,gBAAgB,IAAI;AAClC,UAAI,IAAI,QAAQ;AAAA,IAClB;AAEA,SAAK;AAAA,EACP;AACF;;;AH/DO,SAAS,eACd,SACA,QACA,MACA;AACA,QAAM,MAAM,IAAI,MAAM,OAAO;AAC7B,QAAM,kBAAkB,KAAK,MAAM;AAEnC,MAAI,KAAM,KAAI,OAAO;AAErB,SAAO;AACT;AAEO,IAAK,YAAL,kBAAKE,eAAL;AACL,EAAAA,WAAA,kBAAe;AACf,EAAAA,WAAA,oBAAiB;AACjB,EAAAA,WAAA,gBAAa;AACb,EAAAA,WAAA,oBAAiB;AAJP,SAAAA;AAAA,GAAA;AAOZ,IAAM,QAAN,MAAY;AAAA,EACF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAMR,cAAc;AACZ,SAAK,SAAS,KAAK,aAAa;AAChC,SAAK,SAAS,CAAC;AACf,SAAK,aAAa,CAAC;AAEnB,SAAK,OAAO,GAAG,WAAW,CAAC,KAAmB,QAAuB;AAEnE,UAAI,WAAW,OAAOC,OAAc,SAAiB;AACnD,YAAI,CAAC,MAAM;AACT,gBAAM;AAAA,YACJ;AAAA,YACA,IAAI;AAAA,YACJ;AAAA,UACF;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,OAAO,MAAMC,IAAG,KAAKD,KAAI;AAC/B,cAAI,CAAC,KAAK,OAAO,GAAG;AAClB,kBAAM;AAAA,cACJ,eAAeA,KAAI;AAAA,cACnB,IAAI;AAAA,cACJ;AAAA,YACF;AAAA,UACF;AAEA,cAAI,UAAU,gBAAgB,IAAI;AAClC,cAAI,UAAU,kBAAkB,OAAO,KAAK,IAAI,CAAC;AAGjD,gBAAM,SAAS,iBAAiBA,KAAI,GAAG,GAAG;AAAA,QAC5C,SAAS,KAAU;AACjB,cAAI,KAAK,SAAS,UAAU;AAC1B,kBAAM;AAAA,cACJ,mBAAmBA,KAAI;AAAA,cACvB,IAAI;AAAA,cACJ;AAAA,YACF;AAAA,UACF;AAEA,gBAAM;AAAA,YACJ,wBAAwBA,KAAI;AAAA,YAC5B,IAAI;AAAA,YACJ;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,SAAS,CAAC,SAAiB;AAC7B,YAAI,aAAa;AACjB,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,CAAC,aAAqB;AACnC,YAAI,UAAU,KAAK,EAAE,UAAU,SAAS,CAAC;AACzC,YAAI,IAAI;AACR,eAAO;AAAA,MACT;AAGA,UAAI,OAAO,CAAC,SAAc;AAExB,YAAI,UAAU,gBAAgB,kBAAkB;AAChD,YAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,MAC9B;AAGA,YAAM,mBAAmB,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC;AAI9C,YAAM,SAAS,IAAI,gBAAgB,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC;AAEzD,YAAM,eAAe,OAAO,YAAY,OAAO,QAAQ,CAAC;AAExD,UAAI,SAAS;AACb,UAAI,QAAQ;AAGZ,YAAM,aAAa,CACjBE,MACAC,MACA,YACA,IACA,UACG;AAEH,YAAI,UAAU,WAAW,QAAQ;AAG/B,cAAI;AACF,kBAAM,gBAAgB,GAAGD,MAAKC,MAAK,CAAC,UAAU;AAC5C,cAAAA,KAAI,UAAU,cAAc,OAAO;AACnC,mBAAK,aAAa,OAAOD,MAAKC,IAAG;AAAA,YACnC,CAAC;AAED,gBAAI,iBAAiB,OAAO,cAAc,SAAS,YAAY;AAC7D,4BAAc,MAAM,CAAC,UAAU;AAC7B,gBAAAA,KAAI,UAAU,cAAc,OAAO;AACnC,qBAAK,aAAa,OAAOD,MAAKC,IAAG;AAAA,cACnC,CAAC;AAAA,YACH;AAEA,mBAAO;AAAA,UACT,SAAS,OAAO;AACd,YAAAA,KAAI,UAAU,cAAc,OAAO;AACnC,iBAAK,aAAa,OAAOD,MAAKC,IAAG;AAAA,UACnC;AAAA,QACF,OAAO;AAEL,cAAI;AACF,kBAAM,mBAAmB,WAAW,KAAK;AAAA,cACvCD;AAAA,cACAC;AAAA;AAAA,cAEA,CAAC,UAAU;AAET,oBAAI,OAAO;AACT,kBAAAA,KAAI,UAAU,cAAc,OAAO;AACnC,yBAAO,KAAK,aAAa,OAAOD,MAAKC,IAAG;AAAA,gBAC1C;AACA,2BAAWD,MAAKC,MAAK,YAAY,IAAI,QAAQ,CAAC;AAAA,cAChD;AAAA;AAAA,cAEA,CAAC,UAAU;AACT,gBAAAA,KAAI,UAAU,cAAc,OAAO;AACnC,qBAAK,aAAa,OAAOD,MAAKC,IAAG;AAAA,cACnC;AAAA,YACF;AAGA,gBACE,oBACA,OAAO,iBAAiB,SAAS,YACjC;AACA,+BAAiB,MAAM,CAAC,UAAU;AAChC,gBAAAA,KAAI,UAAU,cAAc,OAAO;AACnC,qBAAK,aAAa,OAAOD,MAAKC,IAAG;AAAA,cACnC,CAAC;AAAA,YACH;AAAA,UACF,SAAS,OAAO;AACd,YAAAA,KAAI,UAAU,cAAc,OAAO;AACnC,iBAAK,aAAa,OAAOD,MAAKC,IAAG;AAAA,UACnC;AAAA,QACF;AAAA,MACF;AAGA,YAAM,gBAAgB,CACpBD,MACAC,MACA,YACA,UACG;AAEH,YAAI,UAAU,WAAW,QAAQ;AAC/B,gBAAM,SAAS,KAAK,OAAOD,KAAI,QAAQ,YAAY,KAAK,EAAE;AAC1D,cAAI,UAAU,OAAO,OAAO,OAAO,QAAQ,MAAM;AAC/C,uBAAW,SAAS,QAAQ;AAC1B,oBAAM,QAAQ,kBAAkB,MAAM,MAAM,KAAK;AAEjD,kBAAI,OAAO;AAET,sBAAM,OAAO,KAAK,aAAa,MAAM,MAAM,KAAK;AAChD,gBAAAA,KAAI,OAAO;AAEX,uBAAO,WAAWA,MAAKC,MAAK,MAAM,YAAY,MAAM,IAAI,CAAC;AAAA,cAC3D;AAAA,YACF;AAGF,iBAAOA,KACJ,OAAO,GAAG,EACV,KAAK,EAAE,OAAO,UAAUD,KAAI,MAAM,IAAI,gBAAgB,GAAG,CAAC;AAAA,QAC/D,OAAO;AACL,qBAAW,KAAK,EAAEA,MAAKC,MAAK,MAAM;AAChC,0BAAcD,MAAKC,MAAK,YAAY,QAAQ,CAAC;AAAA,UAC/C,CAAC;AAAA,QACH;AAAA,MACF;AAEA,oBAAc,KAAK,KAAK,KAAK,YAAY,CAAC;AAAA,IAC5C,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAgBH,UAAiB,MAAqC;AAC1E,QAAI,CAAC,KAAK,OAAO,MAAM,EAAG,MAAK,OAAO,MAAM,IAAI,CAAC;AAGjD,UAAM,KAAK,KAAK,IAAI;AAEpB,QAAI,CAAC,MAAM,OAAO,OAAO,YAAY;AACnC,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAGA,UAAM,aAAa,KAAK,KAAK;AAE7B,UAAM,QAAQ,KAAK,aAAaA,KAAI;AACpC,SAAK,OAAO,MAAM,EAAE,KAAK,EAAE,MAAAA,OAAM,OAAO,YAAY,GAAG,CAAC;AAAA,EAC1D;AAAA,EAEA,WAAW,IAAgB;AACzB,SAAK,WAAW,KAAK,EAAE;AAAA,EACzB;AAAA,EAEA,UAAU,IAAmE;AAC3E,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAO,MAAc,IAAiB;AACpC,WAAO,KAAK,OAAO,OAAO,MAAM,EAAE;AAAA,EACpC;AAAA,EAEA,MAAM,IAA4B;AAChC,SAAK,OAAO,MAAM,EAAE;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAaA,OAAc;AACzB,UAAM,WAAqB,CAAC;AAC5B,UAAM,cACJ,MACAA,MAAK,QAAQ,SAAS,CAAC,OAAO,WAAW;AACvC,eAAS,KAAK,MAAM,MAAM,CAAC,CAAC;AAC5B,aAAO;AAAA,IACT,CAAC,IACD;AAEF,UAAM,QAAQ,IAAI,OAAO,WAAW;AACpC,WAAO;AAAA,EACT;AAAA,EAEA,aAAaA,OAAc,OAAyB;AAElD,UAAM,YAAYA,MAAK,MAAM,OAAO,KAAK,CAAC,GAAG;AAAA,MAAI,CAAC,aAChD,SAAS,MAAM,CAAC;AAAA,IAClB;AACA,UAAM,OAAkB,CAAC;AACzB,aAAS,QAAQ,CAAC,MAAM,UAAU;AAChC,WAAK,IAAI,IAAI,MAAM,QAAQ,CAAC;AAAA,IAC9B,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAmBe,SAAR,QAAyB;AAC9B,SAAO,IAAI,MAAM;AACnB;","names":["fs","folderPath","filesMap","fs","path","fs","ErrorCode","path","fs","req","res"]}
package/lib/index.ts CHANGED
@@ -3,8 +3,6 @@ import fs from "node:fs/promises";
3
3
  import { createReadStream } from "node:fs";
4
4
  import { pipeline } from "node:stream/promises";
5
5
 
6
- import { serveStatic, parseJSON, render } from "./utils";
7
-
8
6
  import type {
9
7
  StringMap,
10
8
  CpeakRequest,
@@ -157,24 +155,41 @@ class Cpeak {
157
155
  this._handleErr?.(error, req, res);
158
156
  }
159
157
  } else {
160
- middleware[index](
161
- req,
162
- res,
163
- // The next function
164
- (error) => {
165
- // this function only accepts an error argument to be more compatible with NPM modules that are built for express
166
- if (error) {
158
+ // Handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler middleware in try catch.
159
+ try {
160
+ const middlewareResult = middleware[index](
161
+ req,
162
+ res,
163
+ // The next function
164
+ (error) => {
165
+ // this function only accepts an error argument to be more compatible with NPM modules that are built for express
166
+ if (error) {
167
+ res.setHeader("Connection", "close");
168
+ return this._handleErr?.(error, req, res);
169
+ }
170
+ runHandler(req, res, middleware, cb, index + 1);
171
+ },
172
+ // Error handler for a route middleware
173
+ (error) => {
167
174
  res.setHeader("Connection", "close");
168
- return this._handleErr?.(error, req, res);
175
+ this._handleErr?.(error, req, res);
169
176
  }
170
- runHandler(req, res, middleware, cb, index + 1);
171
- },
172
- // Error handler for a route middleware
173
- (error) => {
174
- res.setHeader("Connection", "close");
175
- this._handleErr?.(error, req, res);
177
+ );
178
+
179
+ // If the middleware is async, handle the promise rejection
180
+ if (
181
+ middlewareResult &&
182
+ typeof middlewareResult.then === "function"
183
+ ) {
184
+ middlewareResult.catch((error) => {
185
+ res.setHeader("Connection", "close");
186
+ this._handleErr?.(error, req, res);
187
+ });
176
188
  }
177
- );
189
+ } catch (error) {
190
+ res.setHeader("Connection", "close");
191
+ this._handleErr?.(error, req, res);
192
+ }
178
193
  }
179
194
  };
180
195
 
@@ -280,7 +295,9 @@ class Cpeak {
280
295
  }
281
296
 
282
297
  // Util functions
283
- export { serveStatic, parseJSON, render };
298
+ export { serveStatic } from "./utils/serveStatic.js";
299
+ export { parseJSON } from "./utils/parseJSON.js";
300
+ export { render } from "./utils/render.js";
284
301
 
285
302
  export type {
286
303
  Cpeak,
@@ -294,4 +311,6 @@ export type {
294
311
  RoutesMap,
295
312
  } from "./types";
296
313
 
297
- export default Cpeak;
314
+ export default function cpeak() {
315
+ return new Cpeak();
316
+ }
package/lib/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
- import CpeakClass from "./index";
2
+ import cpeak from "./index";
3
3
 
4
- export type Cpeak = InstanceType<typeof CpeakClass>;
4
+ export type Cpeak = ReturnType<typeof cpeak>;
5
5
 
6
6
  // Extending Node.js's Request and Response objects to add our custom properties
7
7
  export type StringMap = Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cpeak",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "A minimal and fast Node.js HTTP framework.",
5
5
  "type": "module",
6
6
  "scripts": {