cpeak 2.4.3 → 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,32 +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/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
128
  // lib/utils/render.ts
87
129
  import fs2 from "fs/promises";
88
130
  function renderTemplate(templateStr, data) {
@@ -108,7 +150,6 @@ function renderTemplate(templateStr, data) {
108
150
  return result.join("");
109
151
  }
110
152
  var render = () => {
111
- console.log("render.ts loaded");
112
153
  return function(req, res, next) {
113
154
  res.render = async (path2, data, mime) => {
114
155
  if (!mime) {
@@ -126,11 +167,270 @@ var render = () => {
126
167
  };
127
168
  };
128
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
+
129
427
  // lib/index.ts
130
- function frameworkError(message, skipFn, code) {
428
+ function frameworkError(message, skipFn, code, status) {
131
429
  const err = new Error(message);
132
430
  Error.captureStackTrace(err, skipFn);
431
+ err.cpeak_err = true;
133
432
  if (code) err.code = code;
433
+ if (status) err.status = status;
134
434
  return err;
135
435
  }
136
436
  var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
@@ -138,196 +438,242 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
138
438
  ErrorCode2["FILE_NOT_FOUND"] = "CPEAK_ERR_FILE_NOT_FOUND";
139
439
  ErrorCode2["NOT_A_FILE"] = "CPEAK_ERR_NOT_A_FILE";
140
440
  ErrorCode2["SEND_FILE_FAIL"] = "CPEAK_ERR_SEND_FILE_FAIL";
441
+ ErrorCode2["INVALID_JSON"] = "CPEAK_ERR_INVALID_JSON";
442
+ ErrorCode2["PAYLOAD_TOO_LARGE"] = "CPEAK_ERR_PAYLOAD_TOO_LARGE";
443
+ ErrorCode2["WEAK_SECRET"] = "CPEAK_ERR_WEAK_SECRET";
141
444
  return ErrorCode2;
142
445
  })(ErrorCode || {});
446
+ var CpeakIncomingMessage = class extends http.IncomingMessage {
447
+ // We define body and params here for better V8 optimization (not changing the shape of the object at runtime)
448
+ body = void 0;
449
+ params = {};
450
+ #query;
451
+ // Parse the URL parameters (like /users?key1=value1&key2=value2)
452
+ // We will call this query to be more familiar with other node.js frameworks.
453
+ // This is a getter method (accessed like a property)
454
+ get query() {
455
+ if (this.#query) return this.#query;
456
+ const url = this.url || "";
457
+ const qIndex = url.indexOf("?");
458
+ if (qIndex === -1) {
459
+ this.#query = {};
460
+ } else {
461
+ const searchParams = new URLSearchParams(url.substring(qIndex + 1));
462
+ this.#query = Object.fromEntries(searchParams.entries());
463
+ }
464
+ return this.#query;
465
+ }
466
+ };
467
+ var CpeakServerResponse = class extends http.ServerResponse {
468
+ // Send a file back to the client
469
+ async sendFile(path2, mime) {
470
+ if (!mime) {
471
+ throw frameworkError(
472
+ 'MIME type is missing. Use res.sendFile(path, "mime-type").',
473
+ this.sendFile,
474
+ "CPEAK_ERR_MISSING_MIME" /* MISSING_MIME */
475
+ );
476
+ }
477
+ try {
478
+ const stat = await fs3.stat(path2);
479
+ if (!stat.isFile()) {
480
+ throw frameworkError(
481
+ `Not a file: ${path2}`,
482
+ this.sendFile,
483
+ "CPEAK_ERR_NOT_A_FILE" /* NOT_A_FILE */
484
+ );
485
+ }
486
+ this.setHeader("Content-Type", mime);
487
+ this.setHeader("Content-Length", String(stat.size));
488
+ await pipeline(createReadStream(path2), this);
489
+ } catch (err) {
490
+ if (err?.code === "ENOENT") {
491
+ throw frameworkError(
492
+ `File not found: ${path2}`,
493
+ this.sendFile,
494
+ "CPEAK_ERR_FILE_NOT_FOUND" /* FILE_NOT_FOUND */
495
+ );
496
+ }
497
+ throw frameworkError(
498
+ `Failed to send file: ${path2}`,
499
+ this.sendFile,
500
+ "CPEAK_ERR_SEND_FILE_FAIL" /* SEND_FILE_FAIL */
501
+ );
502
+ }
503
+ }
504
+ // Set the status code of the response
505
+ status(code) {
506
+ this.statusCode = code;
507
+ return this;
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
+ }
515
+ // Redirects to a new URL
516
+ redirect(location) {
517
+ this.writeHead(302, { Location: location });
518
+ this.end();
519
+ }
520
+ // Send a json data back to the client (for small json data, less than the highWaterMark)
521
+ json(data) {
522
+ this.setHeader("Content-Type", "application/json");
523
+ this.end(JSON.stringify(data));
524
+ }
525
+ };
143
526
  var Cpeak = class {
144
- server;
145
- routes;
146
- middleware;
147
- _handleErr;
527
+ #server;
528
+ #routes;
529
+ #middleware;
530
+ #handleErr;
148
531
  constructor() {
149
- this.server = http.createServer();
150
- this.routes = {};
151
- this.middleware = [];
152
- this.server.on("request", (req, res) => {
153
- res.sendFile = async (path2, mime) => {
154
- if (!mime) {
155
- throw frameworkError(
156
- 'MIME type is missing. Use res.sendFile(path, "mime-type").',
157
- res.sendFile,
158
- "CPEAK_ERR_MISSING_MIME" /* MISSING_MIME */
159
- );
160
- }
161
- try {
162
- const stat = await fs3.stat(path2);
163
- if (!stat.isFile()) {
164
- throw frameworkError(
165
- `Not a file: ${path2}`,
166
- res.sendFile,
167
- "CPEAK_ERR_NOT_A_FILE" /* NOT_A_FILE */
168
- );
169
- }
170
- res.setHeader("Content-Type", mime);
171
- res.setHeader("Content-Length", String(stat.size));
172
- await pipeline(createReadStream(path2), res);
173
- } catch (err) {
174
- if (err?.code === "ENOENT") {
175
- throw frameworkError(
176
- `File not found: ${path2}`,
177
- res.sendFile,
178
- "CPEAK_ERR_FILE_NOT_FOUND" /* FILE_NOT_FOUND */
179
- );
532
+ this.#server = http.createServer({
533
+ IncomingMessage: CpeakIncomingMessage,
534
+ ServerResponse: CpeakServerResponse
535
+ });
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;
180
547
  }
181
- throw frameworkError(
182
- `Failed to send file: ${path2}`,
183
- res.sendFile,
184
- "CPEAK_ERR_SEND_FILE_FAIL" /* SEND_FILE_FAIL */
185
- );
186
- }
187
- };
188
- res.status = (code) => {
189
- res.statusCode = code;
190
- return res;
191
- };
192
- res.redirect = (location) => {
193
- res.writeHead(302, { Location: location });
194
- res.end();
195
- return res;
196
- };
197
- res.json = (data) => {
198
- res.setHeader("Content-Type", "application/json");
199
- res.end(JSON.stringify(data));
200
- };
201
- const urlWithoutParams = req.url?.split("?")[0];
202
- const params = new URLSearchParams(req.url?.split("?")[1]);
203
- const paramsObject = Object.fromEntries(params.entries());
204
- req.params = paramsObject;
205
- req.query = paramsObject;
206
- const runHandler = (req2, res2, middleware, cb, index) => {
207
- if (index === middleware.length) {
208
- try {
209
- const handlerResult = cb(req2, res2, (error) => {
210
- res2.setHeader("Connection", "close");
211
- this._handleErr?.(error, req2, res2);
212
- });
213
- if (handlerResult && typeof handlerResult.then === "function") {
214
- handlerResult.catch((error) => {
215
- res2.setHeader("Connection", "close");
216
- this._handleErr?.(error, req2, res2);
217
- });
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);
218
575
  }
219
- return handlerResult;
220
- } catch (error) {
221
- res2.setHeader("Connection", "close");
222
- this._handleErr?.(error, req2, res2);
223
576
  }
224
- } else {
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);
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
+ );
234
597
  }
235
- runHandler(req2, res2, middleware, cb, index + 1);
236
- },
237
- // Error handler for a route middleware
238
- (error) => {
239
- res2.setHeader("Connection", "close");
240
- this._handleErr?.(error, req2, res2);
241
598
  }
242
- );
243
- if (middlewareResult && typeof middlewareResult.then === "function") {
244
- middlewareResult.catch((error) => {
245
- res2.setHeader("Connection", "close");
246
- this._handleErr?.(error, req2, res2);
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);
247
607
  });
608
+ } catch (error) {
609
+ dispatchError(error);
248
610
  }
249
- } catch (error) {
250
- res2.setHeader("Connection", "close");
251
- this._handleErr?.(error, req2, res2);
252
611
  }
253
- }
254
- };
255
- const runMiddleware = (req2, res2, middleware, index) => {
256
- if (index === middleware.length) {
257
- const routes = this.routes[req2.method?.toLowerCase() || ""];
258
- if (routes && typeof routes[Symbol.iterator] === "function")
259
- for (const route of routes) {
260
- const match = urlWithoutParams?.match(route.regex);
261
- if (match) {
262
- const vars = this.#extractVars(route.path, match);
263
- req2.vars = vars;
264
- return runHandler(req2, res2, route.middleware, route.cb, 0);
265
- }
266
- }
267
- return res2.status(404).json({ error: `Cannot ${req2.method} ${urlWithoutParams}` });
268
- } else {
269
- middleware[index](req2, res2, () => {
270
- runMiddleware(req2, res2, middleware, index + 1);
271
- });
272
- }
273
- };
274
- runMiddleware(req, res, this.middleware, 0);
275
- });
612
+ };
613
+ await runMiddleware(req, res, this.#middleware, 0);
614
+ }
615
+ );
276
616
  }
277
617
  route(method, path2, ...args) {
278
- if (!this.routes[method]) this.routes[method] = [];
618
+ if (!this.#routes[method]) this.#routes[method] = [];
279
619
  const cb = args.pop();
280
620
  if (!cb || typeof cb !== "function") {
281
621
  throw new Error("Route definition must include a handler");
282
622
  }
283
623
  const middleware = args.flat();
284
624
  const regex = this.#pathToRegex(path2);
285
- this.routes[method].push({ path: path2, regex, middleware, cb });
625
+ this.#routes[method].push({ path: path2, regex, middleware, cb });
286
626
  }
287
627
  beforeEach(cb) {
288
- this.middleware.push(cb);
628
+ this.#middleware.push(cb);
289
629
  }
290
630
  handleErr(cb) {
291
- this._handleErr = cb;
631
+ this.#handleErr = cb;
292
632
  }
293
633
  listen(port, cb) {
294
- return this.server.listen(port, cb);
634
+ return this.#server.listen(port, cb);
635
+ }
636
+ address() {
637
+ return this.#server.address();
295
638
  }
296
639
  close(cb) {
297
- this.server.close(cb);
640
+ this.#server.close(cb);
298
641
  }
299
642
  // ------------------------------
300
643
  // PRIVATE METHODS:
301
644
  // ------------------------------
302
645
  #pathToRegex(path2) {
303
- const varNames = [];
304
- const regexString = "^" + path2.replace(/:\w+/g, (match, offset) => {
305
- varNames.push(match.slice(1));
306
- return "([^/]+)";
307
- }) + "$";
308
- const regex = new RegExp(regexString);
309
- return regex;
646
+ const regexString = "^" + path2.replace(/:\w+/g, "([^/]+)").replace(/\*/g, ".*") + "$";
647
+ return new RegExp(regexString);
310
648
  }
311
- #extractVars(path2, match) {
312
- const varNames = (path2.match(/:\w+/g) || []).map(
313
- (varParam) => varParam.slice(1)
649
+ #extractPathVariables(path2, match) {
650
+ const paramNames = (path2.match(/:\w+/g) || []).map(
651
+ (param) => param.slice(1)
314
652
  );
315
- const vars = {};
316
- varNames.forEach((name, index) => {
317
- vars[name] = match[index + 1];
653
+ const params = {};
654
+ paramNames.forEach((name, index) => {
655
+ params[name] = match[index + 1];
318
656
  });
319
- return vars;
657
+ return params;
320
658
  }
321
659
  };
322
660
  function cpeak() {
323
661
  return new Cpeak();
324
662
  }
325
663
  export {
664
+ Cpeak,
665
+ CpeakIncomingMessage,
666
+ CpeakServerResponse,
326
667
  ErrorCode,
668
+ auth,
669
+ cookieParser,
327
670
  cpeak as default,
328
671
  frameworkError,
672
+ hashPassword,
329
673
  parseJSON,
330
674
  render,
331
- serveStatic
675
+ serveStatic,
676
+ swagger,
677
+ verifyPassword
332
678
  };
333
679
  //# sourceMappingURL=index.js.map