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/dist/index.js CHANGED
@@ -4,6 +4,60 @@ 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
+ import { Buffer as Buffer2 } from "buffer";
9
+ function isJSON(contentType) {
10
+ if (!contentType) return false;
11
+ if (contentType === "application/json") return true;
12
+ return contentType.startsWith("application/json") || contentType.includes("+json");
13
+ }
14
+ var parseJSON = (options = {}) => {
15
+ const limit = options.limit || 1024 * 1024;
16
+ return (req, res, next) => {
17
+ if (!isJSON(req.headers["content-type"])) return next();
18
+ const chunks = [];
19
+ let bytesReceived = 0;
20
+ const onData = (chunk) => {
21
+ bytesReceived += chunk.length;
22
+ if (bytesReceived > limit) {
23
+ req.pause();
24
+ req.removeListener("data", onData);
25
+ req.removeListener("end", onEnd);
26
+ next(
27
+ frameworkError(
28
+ "JSON body too large",
29
+ onData,
30
+ "CPEAK_ERR_PAYLOAD_TOO_LARGE" /* PAYLOAD_TOO_LARGE */,
31
+ 413
32
+ // HTTP 413 Payload Too Large
33
+ )
34
+ );
35
+ return;
36
+ }
37
+ chunks.push(chunk);
38
+ };
39
+ const onEnd = () => {
40
+ try {
41
+ const rawBody = chunks.length === 1 ? chunks[0].toString("utf-8") : Buffer2.concat(chunks).toString("utf-8");
42
+ req.body = rawBody ? JSON.parse(rawBody) : {};
43
+ next();
44
+ } catch (err) {
45
+ next(
46
+ frameworkError(
47
+ "Invalid JSON format",
48
+ onEnd,
49
+ "CPEAK_ERR_INVALID_JSON" /* INVALID_JSON */,
50
+ 400
51
+ // HTTP 400 Bad Request
52
+ )
53
+ );
54
+ }
55
+ };
56
+ req.on("data", onData);
57
+ req.on("end", onEnd);
58
+ };
59
+ };
60
+
7
61
  // lib/utils/serveStatic.ts
8
62
  import fs from "fs";
9
63
  import path from "path";
@@ -20,12 +74,17 @@ var MIME_TYPES = {
20
74
  otf: "font/otf",
21
75
  ttf: "font/ttf",
22
76
  woff: "font/woff",
23
- woff2: "font/woff2"
77
+ woff2: "font/woff2",
78
+ gif: "image/gif",
79
+ ico: "image/x-icon",
80
+ json: "application/json",
81
+ webmanifest: "application/manifest+json"
24
82
  };
25
- var serveStatic = (folderPath, newMimeTypes) => {
83
+ var serveStatic = (folderPath, newMimeTypes, options) => {
26
84
  if (newMimeTypes) {
27
85
  Object.assign(MIME_TYPES, newMimeTypes);
28
86
  }
87
+ const prefix = options?.prefix ?? "";
29
88
  function processFolder(folderPath2, parentFolder) {
30
89
  const staticFiles = [];
31
90
  const files = fs.readdirSync(folderPath2);
@@ -46,7 +105,7 @@ var serveStatic = (folderPath, newMimeTypes) => {
46
105
  const filesMap2 = {};
47
106
  for (const file of filesArray) {
48
107
  const fileExtension = path.extname(file).slice(1);
49
- filesMap2[file] = {
108
+ filesMap2[prefix + file] = {
50
109
  path: folderPath + file,
51
110
  mime: MIME_TYPES[fileExtension]
52
111
  };
@@ -57,68 +116,15 @@ var serveStatic = (folderPath, newMimeTypes) => {
57
116
  return function(req, res, next) {
58
117
  const url = req.url;
59
118
  if (typeof url !== "string") return next();
60
- if (Object.prototype.hasOwnProperty.call(filesMap, url)) {
61
- const fileRoute = filesMap[url];
119
+ const pathname = url.split("?")[0];
120
+ if (Object.prototype.hasOwnProperty.call(filesMap, pathname)) {
121
+ const fileRoute = filesMap[pathname];
62
122
  return res.sendFile(fileRoute.path, fileRoute.mime);
63
123
  }
64
124
  next();
65
125
  };
66
126
  };
67
127
 
68
- // lib/utils/paseJSON.ts
69
- import { Buffer } from "buffer";
70
- function isJSON(contentType) {
71
- if (!contentType) return false;
72
- if (contentType === "application/json") return true;
73
- return contentType.startsWith("application/json") || contentType.includes("+json");
74
- }
75
- var parseJSON = (options = {}) => {
76
- const limit = options.limit || 1024 * 1024;
77
- return (req, res, next) => {
78
- if (!isJSON(req.headers["content-type"])) return next();
79
- const chunks = [];
80
- let bytesReceived = 0;
81
- const onData = (chunk) => {
82
- bytesReceived += chunk.length;
83
- if (bytesReceived > limit) {
84
- req.pause();
85
- req.removeListener("data", onData);
86
- req.removeListener("end", onEnd);
87
- next(
88
- frameworkError(
89
- "JSON body too large",
90
- onData,
91
- "CPEAK_ERR_PAYLOAD_TOO_LARGE" /* PAYLOAD_TOO_LARGE */,
92
- 413
93
- // HTTP 413 Payload Too Large
94
- )
95
- );
96
- return;
97
- }
98
- chunks.push(chunk);
99
- };
100
- const onEnd = () => {
101
- try {
102
- const rawBody = chunks.length === 1 ? chunks[0].toString("utf-8") : Buffer.concat(chunks).toString("utf-8");
103
- req.body = rawBody ? JSON.parse(rawBody) : {};
104
- next();
105
- } catch (err) {
106
- next(
107
- frameworkError(
108
- "Invalid JSON format",
109
- onEnd,
110
- "CPEAK_ERR_INVALID_JSON" /* INVALID_JSON */,
111
- 400
112
- // HTTP 400 Bad Request
113
- )
114
- );
115
- }
116
- };
117
- req.on("data", onData);
118
- req.on("end", onEnd);
119
- };
120
- };
121
-
122
128
  // lib/utils/render.ts
123
129
  import fs2 from "fs/promises";
124
130
  function renderTemplate(templateStr, data) {
@@ -161,6 +167,263 @@ var render = () => {
161
167
  };
162
168
  };
163
169
 
170
+ // lib/utils/swagger.ts
171
+ var swagger = (spec, prefix = "/api-docs") => {
172
+ const initializerJs = `window.onload = function() {
173
+ SwaggerUIBundle({
174
+ url: "${prefix}/spec.json",
175
+ dom_id: '#swagger-ui',
176
+ presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
177
+ layout: "StandaloneLayout"
178
+ });
179
+ };`;
180
+ return (req, res, next) => {
181
+ if (req.url === prefix || req.url === `${prefix}/`) {
182
+ res.writeHead(302, { Location: `${prefix}/index.html` });
183
+ res.end();
184
+ return;
185
+ }
186
+ if (req.url === `${prefix}/spec.json`) {
187
+ return res.json(spec);
188
+ }
189
+ if (req.url === `${prefix}/swagger-initializer.js`) {
190
+ res.setHeader("Content-Type", "application/javascript");
191
+ res.end(initializerJs);
192
+ return;
193
+ }
194
+ next();
195
+ };
196
+ };
197
+
198
+ // lib/utils/auth.ts
199
+ import { randomBytes, pbkdf2, createHmac, timingSafeEqual } from "crypto";
200
+ import { promisify } from "util";
201
+ var pbkdf2Async = promisify(pbkdf2);
202
+ var DEFAULTS = {
203
+ iterations: 21e4,
204
+ keylen: 64,
205
+ digest: "sha512",
206
+ saltSize: 32,
207
+ hmacAlgorithm: "sha256",
208
+ tokenIdSize: 20,
209
+ tokenExpiry: 7 * 24 * 60 * 60 * 1e3
210
+ // 7 days in ms
211
+ };
212
+ async function hashPassword(password, options) {
213
+ const iterations = options?.iterations ?? DEFAULTS.iterations;
214
+ const keylen = options?.keylen ?? DEFAULTS.keylen;
215
+ const digest = options?.digest ?? DEFAULTS.digest;
216
+ const saltSize = options?.saltSize ?? DEFAULTS.saltSize;
217
+ const salt = randomBytes(saltSize);
218
+ const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);
219
+ return `pbkdf2:${iterations}:${keylen}:${digest}:${salt.toString("hex")}:${hash.toString("hex")}`;
220
+ }
221
+ async function verifyPassword(password, stored) {
222
+ const withoutPrefix = stored.slice(stored.indexOf(":") + 1);
223
+ const parts = withoutPrefix.split(":");
224
+ if (parts.length !== 5) return false;
225
+ const [itersStr, keylenStr, digest, saltHex, hashHex] = parts;
226
+ const iterations = parseInt(itersStr, 10);
227
+ const keylen = parseInt(keylenStr, 10);
228
+ if (!digest || !saltHex || !hashHex || isNaN(iterations) || isNaN(keylen))
229
+ return false;
230
+ const salt = Buffer.from(saltHex, "hex");
231
+ const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);
232
+ const storedHash = Buffer.from(hashHex, "hex");
233
+ if (storedHash.length !== hash.length) return false;
234
+ return timingSafeEqual(hash, storedHash);
235
+ }
236
+ function signToken(tokenId, secret, algorithm) {
237
+ const sig = createHmac(algorithm, secret).update(tokenId).digest("hex");
238
+ return `${tokenId}.${sig}`;
239
+ }
240
+ function extractTokenId(token, secret, algorithm) {
241
+ const dot = token.indexOf(".");
242
+ if (dot === -1) return null;
243
+ const tokenId = token.slice(0, dot);
244
+ const sig = token.slice(dot + 1);
245
+ const expected = createHmac(algorithm, secret).update(tokenId).digest("hex");
246
+ const expectedBuf = Buffer.from(expected, "hex");
247
+ const actualBuf = Buffer.from(sig, "hex");
248
+ if (expectedBuf.length !== actualBuf.length) return null;
249
+ if (!timingSafeEqual(expectedBuf, actualBuf)) return null;
250
+ return tokenId;
251
+ }
252
+ function auth(options) {
253
+ if (!options.secret || options.secret.length < 32) {
254
+ throw frameworkError(
255
+ "Secret must be at least 32 characters. HMAC security is only as strong as the key.",
256
+ auth,
257
+ "CPEAK_ERR_WEAK_SECRET" /* WEAK_SECRET */
258
+ );
259
+ }
260
+ const {
261
+ secret,
262
+ saveToken,
263
+ findToken,
264
+ revokeToken,
265
+ tokenExpiry = DEFAULTS.tokenExpiry,
266
+ hmacAlgorithm = DEFAULTS.hmacAlgorithm,
267
+ tokenIdSize = DEFAULTS.tokenIdSize
268
+ } = options;
269
+ const pbkdfOpts = {
270
+ iterations: options.iterations,
271
+ keylen: options.keylen,
272
+ digest: options.digest,
273
+ saltSize: options.saltSize
274
+ };
275
+ const _hashPassword = ({ password }) => hashPassword(password, pbkdfOpts);
276
+ const login = async ({
277
+ password,
278
+ hashedPassword,
279
+ userId
280
+ }) => {
281
+ const isMatch = await verifyPassword(password, hashedPassword);
282
+ if (!isMatch) return null;
283
+ const tokenId = randomBytes(tokenIdSize).toString("hex");
284
+ const token = signToken(tokenId, secret, hmacAlgorithm);
285
+ await saveToken(tokenId, userId, new Date(Date.now() + tokenExpiry));
286
+ return token;
287
+ };
288
+ const verifyToken = async (token) => {
289
+ if (!token) return null;
290
+ const tokenId = extractTokenId(token, secret, hmacAlgorithm);
291
+ if (!tokenId) return null;
292
+ const record = await findToken(tokenId);
293
+ if (!record) return null;
294
+ if (new Date(record.expiresAt) < /* @__PURE__ */ new Date()) return null;
295
+ return { userId: record.userId };
296
+ };
297
+ const logout = revokeToken ? async (token) => {
298
+ const tokenId = extractTokenId(token, secret, hmacAlgorithm);
299
+ if (!tokenId) return false;
300
+ await revokeToken(tokenId);
301
+ return true;
302
+ } : void 0;
303
+ return (req, _res, next) => {
304
+ req.hashPassword = _hashPassword;
305
+ req.login = login;
306
+ req.verifyToken = verifyToken;
307
+ if (logout) req.logout = logout;
308
+ next();
309
+ };
310
+ }
311
+
312
+ // lib/utils/cookieParser.ts
313
+ import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
314
+ function sign(value, secret) {
315
+ const sig = createHmac2("sha256", secret).update(value).digest("base64url");
316
+ return `s:${value}.${sig}`;
317
+ }
318
+ function unsign(signed, secret) {
319
+ if (!signed.startsWith("s:")) return false;
320
+ const withoutPrefix = signed.slice(2);
321
+ const lastDot = withoutPrefix.lastIndexOf(".");
322
+ if (lastDot === -1) return false;
323
+ const value = withoutPrefix.slice(0, lastDot);
324
+ const sig = withoutPrefix.slice(lastDot + 1);
325
+ const expected = createHmac2("sha256", secret).update(value).digest("base64url");
326
+ const expectedBuf = Buffer.from(expected);
327
+ const actualBuf = Buffer.from(sig);
328
+ if (expectedBuf.length !== actualBuf.length) return false;
329
+ if (!timingSafeEqual2(expectedBuf, actualBuf)) return false;
330
+ return value;
331
+ }
332
+ function parseRawCookies(header) {
333
+ const cookies = /* @__PURE__ */ Object.create(null);
334
+ if (!header) return cookies;
335
+ const pairs = header.split(";");
336
+ for (let i = 0; i < pairs.length; i++) {
337
+ const pair = pairs[i];
338
+ const equalSignIndex = pair.indexOf("=");
339
+ if (equalSignIndex === -1) continue;
340
+ const key = pair.slice(0, equalSignIndex).trim();
341
+ if (!key || cookies[key] !== void 0) continue;
342
+ let val = pair.slice(equalSignIndex + 1).trim();
343
+ if (val.length > 1 && val[0] === '"' && val[val.length - 1] === '"') {
344
+ val = val.slice(1, -1);
345
+ }
346
+ try {
347
+ cookies[key] = val.indexOf("%") !== -1 ? decodeURIComponent(val) : val;
348
+ } catch (e) {
349
+ cookies[key] = val;
350
+ }
351
+ }
352
+ return cookies;
353
+ }
354
+ function buildSetCookieHeader(name, value, options) {
355
+ const parts = [`${name}=${encodeURIComponent(value)}`];
356
+ const path2 = options.path ?? "/";
357
+ parts.push(`Path=${path2}`);
358
+ if (options.domain) parts.push(`Domain=${options.domain}`);
359
+ if (options.maxAge !== void 0)
360
+ parts.push(`Max-Age=${Math.floor(options.maxAge / 1e3)}`);
361
+ if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
362
+ if (options.httpOnly) parts.push("HttpOnly");
363
+ if (options.secure) parts.push("Secure");
364
+ if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
365
+ return parts.join("; ");
366
+ }
367
+ function appendSetCookie(res, header) {
368
+ const existing = res.getHeader("Set-Cookie");
369
+ if (!existing) {
370
+ res.setHeader("Set-Cookie", [header]);
371
+ } else if (Array.isArray(existing)) {
372
+ res.setHeader("Set-Cookie", [...existing, header]);
373
+ } else {
374
+ res.setHeader("Set-Cookie", [String(existing), header]);
375
+ }
376
+ }
377
+ function cookieParser(options = {}) {
378
+ const { secret } = options;
379
+ if (secret !== void 0 && secret.length < 32) {
380
+ throw frameworkError(
381
+ "Secret must be at least 32 characters. HMAC security is only as strong as the key.",
382
+ cookieParser,
383
+ "CPEAK_ERR_WEAK_SECRET" /* WEAK_SECRET */
384
+ );
385
+ }
386
+ return (req, res, next) => {
387
+ const rawHeader = req.headers["cookie"] || "";
388
+ const raw = parseRawCookies(rawHeader);
389
+ const cookies = /* @__PURE__ */ Object.create(null);
390
+ const signedCookies = /* @__PURE__ */ Object.create(null);
391
+ for (const [key, val] of Object.entries(raw)) {
392
+ if (val.startsWith("s:") && secret) {
393
+ signedCookies[key] = unsign(val, secret);
394
+ } else {
395
+ cookies[key] = val;
396
+ }
397
+ }
398
+ req.cookies = cookies;
399
+ req.signedCookies = signedCookies;
400
+ res.cookie = (name, value, options2 = {}) => {
401
+ let finalValue = value;
402
+ if (options2.signed) {
403
+ if (!secret)
404
+ throw new Error(
405
+ "cookieParser: secret is required to use signed cookies"
406
+ );
407
+ finalValue = sign(value, secret);
408
+ }
409
+ appendSetCookie(res, buildSetCookieHeader(name, finalValue, options2));
410
+ return res;
411
+ };
412
+ res.clearCookie = (name, options2 = {}) => {
413
+ appendSetCookie(
414
+ res,
415
+ buildSetCookieHeader(name, "", {
416
+ ...options2,
417
+ maxAge: 0,
418
+ expires: /* @__PURE__ */ new Date(0)
419
+ })
420
+ );
421
+ return res;
422
+ };
423
+ next();
424
+ };
425
+ }
426
+
164
427
  // lib/index.ts
165
428
  function frameworkError(message, skipFn, code, status) {
166
429
  const err = new Error(message);
@@ -177,27 +440,28 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
177
440
  ErrorCode2["SEND_FILE_FAIL"] = "CPEAK_ERR_SEND_FILE_FAIL";
178
441
  ErrorCode2["INVALID_JSON"] = "CPEAK_ERR_INVALID_JSON";
179
442
  ErrorCode2["PAYLOAD_TOO_LARGE"] = "CPEAK_ERR_PAYLOAD_TOO_LARGE";
443
+ ErrorCode2["WEAK_SECRET"] = "CPEAK_ERR_WEAK_SECRET";
180
444
  return ErrorCode2;
181
445
  })(ErrorCode || {});
182
446
  var CpeakIncomingMessage = class extends http.IncomingMessage {
183
447
  // We define body and params here for better V8 optimization (not changing the shape of the object at runtime)
184
448
  body = void 0;
185
449
  params = {};
186
- _query;
450
+ #query;
187
451
  // Parse the URL parameters (like /users?key1=value1&key2=value2)
188
452
  // We will call this query to be more familiar with other node.js frameworks.
189
453
  // This is a getter method (accessed like a property)
190
454
  get query() {
191
- if (this._query) return this._query;
455
+ if (this.#query) return this.#query;
192
456
  const url = this.url || "";
193
457
  const qIndex = url.indexOf("?");
194
458
  if (qIndex === -1) {
195
- this._query = {};
459
+ this.#query = {};
196
460
  } else {
197
461
  const searchParams = new URLSearchParams(url.substring(qIndex + 1));
198
- this._query = Object.fromEntries(searchParams.entries());
462
+ this.#query = Object.fromEntries(searchParams.entries());
199
463
  }
200
- return this._query;
464
+ return this.#query;
201
465
  }
202
466
  };
203
467
  var CpeakServerResponse = class extends http.ServerResponse {
@@ -242,11 +506,16 @@ var CpeakServerResponse = class extends http.ServerResponse {
242
506
  this.statusCode = code;
243
507
  return this;
244
508
  }
509
+ // Set the Content-Disposition header to prompt the user to download a file
510
+ attachment(filename) {
511
+ const contentDisposition = filename ? `attachment; filename="${filename}"` : "attachment";
512
+ this.setHeader("Content-Disposition", contentDisposition);
513
+ return this;
514
+ }
245
515
  // Redirects to a new URL
246
516
  redirect(location) {
247
517
  this.writeHead(302, { Location: location });
248
518
  this.end();
249
- return this;
250
519
  }
251
520
  // Send a json data back to the client (for small json data, less than the highWaterMark)
252
521
  json(data) {
@@ -255,129 +524,127 @@ var CpeakServerResponse = class extends http.ServerResponse {
255
524
  }
256
525
  };
257
526
  var Cpeak = class {
258
- server;
259
- routes;
260
- middleware;
261
- _handleErr;
527
+ #server;
528
+ #routes;
529
+ #middleware;
530
+ #handleErr;
262
531
  constructor() {
263
- this.server = http.createServer({
532
+ this.#server = http.createServer({
264
533
  IncomingMessage: CpeakIncomingMessage,
265
534
  ServerResponse: CpeakServerResponse
266
535
  });
267
- this.routes = {};
268
- this.middleware = [];
269
- this.server.on("request", async (req, res) => {
270
- const qIndex = req.url?.indexOf("?");
271
- const urlWithoutQueries = qIndex === -1 ? req.url || "" : req.url?.substring(0, qIndex);
272
- const runHandler = async (req2, res2, middleware, cb, index) => {
273
- if (index === middleware.length) {
274
- try {
275
- await cb(req2, res2, (error) => {
276
- res2.setHeader("Connection", "close");
277
- this._handleErr?.(error, req2, res2);
278
- });
279
- } catch (error) {
280
- res2.setHeader("Connection", "close");
281
- this._handleErr?.(error, req2, res2);
536
+ this.#routes = {};
537
+ this.#middleware = [];
538
+ this.#server.on(
539
+ "request",
540
+ async (req, res) => {
541
+ const qIndex = req.url?.indexOf("?");
542
+ const urlWithoutQueries = qIndex === -1 ? req.url || "" : req.url?.substring(0, qIndex);
543
+ const dispatchError = (error) => {
544
+ if (res.headersSent) {
545
+ req.socket?.destroy();
546
+ return;
282
547
  }
283
- } else {
284
- try {
285
- await middleware[index](
286
- req2,
287
- res2,
288
- // The next function
289
- async (error) => {
290
- if (error) {
291
- res2.setHeader("Connection", "close");
292
- return this._handleErr?.(error, req2, res2);
293
- }
294
- await runHandler(req2, res2, middleware, cb, index + 1);
295
- },
296
- // Error handler for a route middleware
297
- (error) => {
298
- res2.setHeader("Connection", "close");
299
- this._handleErr?.(error, req2, res2);
300
- }
301
- );
302
- } catch (error) {
303
- res2.setHeader("Connection", "close");
304
- this._handleErr?.(error, req2, res2);
548
+ res.setHeader("Connection", "close");
549
+ this.#handleErr?.(error, req, res);
550
+ };
551
+ const runHandler = async (req2, res2, middleware, cb, index) => {
552
+ if (index === middleware.length) {
553
+ try {
554
+ await cb(req2, res2, dispatchError);
555
+ } catch (error) {
556
+ dispatchError(error);
557
+ }
558
+ } else {
559
+ try {
560
+ await middleware[index](
561
+ req2,
562
+ res2,
563
+ // The next function
564
+ async (error) => {
565
+ if (error) {
566
+ return dispatchError(error);
567
+ }
568
+ await runHandler(req2, res2, middleware, cb, index + 1);
569
+ },
570
+ // Error handler for a route middleware
571
+ dispatchError
572
+ );
573
+ } catch (error) {
574
+ dispatchError(error);
575
+ }
305
576
  }
306
- }
307
- };
308
- const runMiddleware = async (req2, res2, middleware, index) => {
309
- if (index === middleware.length) {
310
- const routes = this.routes[req2.method?.toLowerCase() || ""];
311
- if (routes && typeof routes[Symbol.iterator] === "function")
312
- for (const route of routes) {
313
- const match = urlWithoutQueries?.match(route.regex);
314
- if (match) {
315
- const pathVariables = this.#extractPathVariables(
316
- route.path,
317
- match
318
- );
319
- req2.params = pathVariables;
320
- return await runHandler(
321
- req2,
322
- res2,
323
- route.middleware,
324
- route.cb,
325
- 0
326
- );
577
+ };
578
+ const runMiddleware = async (req2, res2, middleware, index) => {
579
+ if (index === middleware.length) {
580
+ const routes = this.#routes[req2.method?.toLowerCase() || ""];
581
+ if (routes && typeof routes[Symbol.iterator] === "function")
582
+ for (const route of routes) {
583
+ const match = urlWithoutQueries?.match(route.regex);
584
+ if (match) {
585
+ const pathVariables = this.#extractPathVariables(
586
+ route.path,
587
+ match
588
+ );
589
+ req2.params = pathVariables;
590
+ return await runHandler(
591
+ req2,
592
+ res2,
593
+ route.middleware,
594
+ route.cb,
595
+ 0
596
+ );
597
+ }
327
598
  }
599
+ return res2.status(404).json({ error: `Cannot ${req2.method} ${urlWithoutQueries}` });
600
+ } else {
601
+ try {
602
+ await middleware[index](req2, res2, async (err) => {
603
+ if (err) {
604
+ return dispatchError(err);
605
+ }
606
+ await runMiddleware(req2, res2, middleware, index + 1);
607
+ });
608
+ } catch (error) {
609
+ dispatchError(error);
328
610
  }
329
- return res2.status(404).json({ error: `Cannot ${req2.method} ${urlWithoutQueries}` });
330
- } else {
331
- try {
332
- await middleware[index](req2, res2, async (err) => {
333
- if (err) {
334
- res2.setHeader("Connection", "close");
335
- return this._handleErr?.(err, req2, res2);
336
- }
337
- await runMiddleware(req2, res2, middleware, index + 1);
338
- });
339
- } catch (error) {
340
- res2.setHeader("Connection", "close");
341
- this._handleErr?.(error, req2, res2);
342
611
  }
343
- }
344
- };
345
- await runMiddleware(req, res, this.middleware, 0);
346
- });
612
+ };
613
+ await runMiddleware(req, res, this.#middleware, 0);
614
+ }
615
+ );
347
616
  }
348
617
  route(method, path2, ...args) {
349
- if (!this.routes[method]) this.routes[method] = [];
618
+ if (!this.#routes[method]) this.#routes[method] = [];
350
619
  const cb = args.pop();
351
620
  if (!cb || typeof cb !== "function") {
352
621
  throw new Error("Route definition must include a handler");
353
622
  }
354
623
  const middleware = args.flat();
355
624
  const regex = this.#pathToRegex(path2);
356
- this.routes[method].push({ path: path2, regex, middleware, cb });
625
+ this.#routes[method].push({ path: path2, regex, middleware, cb });
357
626
  }
358
627
  beforeEach(cb) {
359
- this.middleware.push(cb);
628
+ this.#middleware.push(cb);
360
629
  }
361
630
  handleErr(cb) {
362
- this._handleErr = cb;
631
+ this.#handleErr = cb;
363
632
  }
364
633
  listen(port, cb) {
365
- return this.server.listen(port, cb);
634
+ return this.#server.listen(port, cb);
635
+ }
636
+ address() {
637
+ return this.#server.address();
366
638
  }
367
639
  close(cb) {
368
- this.server.close(cb);
640
+ this.#server.close(cb);
369
641
  }
370
642
  // ------------------------------
371
643
  // PRIVATE METHODS:
372
644
  // ------------------------------
373
645
  #pathToRegex(path2) {
374
- const paramNames = [];
375
- const regexString = "^" + path2.replace(/:\w+/g, (match, offset) => {
376
- paramNames.push(match.slice(1));
377
- return "([^/]+)";
378
- }) + "$";
379
- const regex = new RegExp(regexString);
380
- return regex;
646
+ const regexString = "^" + path2.replace(/:\w+/g, "([^/]+)").replace(/\*/g, ".*") + "$";
647
+ return new RegExp(regexString);
381
648
  }
382
649
  #extractPathVariables(path2, match) {
383
650
  const paramNames = (path2.match(/:\w+/g) || []).map(
@@ -394,11 +661,19 @@ function cpeak() {
394
661
  return new Cpeak();
395
662
  }
396
663
  export {
664
+ Cpeak,
665
+ CpeakIncomingMessage,
666
+ CpeakServerResponse,
397
667
  ErrorCode,
668
+ auth,
669
+ cookieParser,
398
670
  cpeak as default,
399
671
  frameworkError,
672
+ hashPassword,
400
673
  parseJSON,
401
674
  render,
402
- serveStatic
675
+ serveStatic,
676
+ swagger,
677
+ verifyPassword
403
678
  };
404
679
  //# sourceMappingURL=index.js.map