cpeak 2.7.0 → 2.8.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/lib/types.ts CHANGED
@@ -1,13 +1,20 @@
1
- import { IncomingMessage, ServerResponse } from "node:http";
1
+ import { IncomingMessage, ServerResponse, type Server } from "node:http";
2
2
  import type { Readable } from "node:stream";
3
3
  import type { Buffer } from "node:buffer";
4
4
  import type { CompressionOptions } from "./internal/types";
5
+ import type { CpeakIncomingMessage, CpeakServerResponse } from "./index";
5
6
 
6
7
  export type { Cpeak } from "./index";
7
8
 
9
+ export type CpeakHttpServer = Server<
10
+ typeof CpeakIncomingMessage,
11
+ typeof CpeakServerResponse
12
+ >;
13
+
8
14
  // For constructor options passed to `cpeak()`
9
15
  export interface CpeakOptions {
10
16
  compression?: boolean | CompressionOptions;
17
+ mimeTypes?: StringMap;
11
18
  }
12
19
 
13
20
  // Extending Node.js's Request and Response objects to add our custom properties
@@ -26,12 +33,12 @@ export interface CpeakRequest<
26
33
  }
27
34
 
28
35
  export interface CpeakResponse extends ServerResponse {
29
- sendFile: (path: string, mime: string) => Promise<void>;
36
+ sendFile: (path: string, mime?: string) => Promise<void>;
30
37
  status: (code: number) => CpeakResponse;
31
38
  attachment: (filename?: string) => CpeakResponse;
32
39
  cookie: (name: string, value: string, options?: any) => CpeakResponse;
33
40
  redirect: (location: string) => void;
34
- json: (data: any) => void | Promise<void>; // sync when compression is off, async when enabled
41
+ json: (data: any) => Promise<void>;
35
42
  compress: (
36
43
  mime: string,
37
44
  body: Buffer | string | Readable,
@@ -41,39 +48,30 @@ export interface CpeakResponse extends ServerResponse {
41
48
  }
42
49
 
43
50
  export type Next = (err?: any) => void;
44
- export type HandleErr = (err: any) => void;
45
51
 
46
52
  // beforeEach middleware: (req, res, next)
47
53
  export type Middleware<ReqBody = any, ReqParams = any> = (
48
54
  req: CpeakRequest<ReqBody, ReqParams>,
49
55
  res: CpeakResponse,
50
56
  next: Next
51
- ) => void;
57
+ ) => unknown;
52
58
 
53
- // Route middleware: (req, res, next, handleErr)
59
+ // Route middleware: (req, res, next)
54
60
  export type RouteMiddleware<ReqBody = any, ReqParams = any> = (
55
61
  req: CpeakRequest<ReqBody, ReqParams>,
56
62
  res: CpeakResponse,
57
- next: Next,
58
- handleErr: HandleErr
59
- ) => void | Promise<void>;
63
+ next: Next
64
+ ) => unknown;
60
65
 
61
- // Route handlers: (req, res, handleErr)
66
+ // Route handlers: (req, res). To signal an error, throw it.
62
67
  export type Handler<ReqBody = any, ReqParams = any> = (
63
68
  req: CpeakRequest<ReqBody, ReqParams>,
64
- res: CpeakResponse,
65
- handleErr: HandleErr
66
- ) => void | Promise<void>;
69
+ res: CpeakResponse
70
+ ) => unknown;
67
71
 
68
- // For a route object value in Cpeak.routes. The key is the method name.
72
+ // Represents a single registered route.
69
73
  export interface Route {
70
74
  path: string;
71
- regex: RegExp;
72
75
  middleware: RouteMiddleware[];
73
76
  cb: Handler;
74
77
  }
75
-
76
- // For Cpeak.routes:
77
- export interface RoutesMap {
78
- [method: string]: Route[];
79
- }
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import { frameworkError } from "../";
3
3
  import { compressAndSend } from "../internal/compression";
4
+ import { MIME_TYPES } from "../internal/mimeTypes";
4
5
  import type { CpeakRequest, CpeakResponse, Next } from "../types";
5
6
 
6
7
  function renderTemplate(
@@ -59,14 +60,18 @@ const render = () => {
59
60
  res.render = async (
60
61
  path: string,
61
62
  data: Record<string, unknown>,
62
- mime: string
63
+ mime?: string
63
64
  ) => {
64
- // check if mime is specified, if not return an error
65
65
  if (!mime) {
66
- throw frameworkError(
67
- `MIME type is missing. You called res.render("${path}", ...) but forgot to provide the third "mime" argument.`,
68
- res.render
69
- );
66
+ const dotIndex = path.lastIndexOf(".");
67
+ const fileExtension = dotIndex >= 0 ? path.slice(dotIndex + 1) : "";
68
+ mime = MIME_TYPES[fileExtension];
69
+ if (!mime) {
70
+ throw frameworkError(
71
+ `MIME type is missing for "${path}". Pass it as the third argument or register the extension via cpeak({ mimeTypes: { ${fileExtension || "ext"}: "..." } }).`,
72
+ res.render
73
+ );
74
+ }
70
75
  }
71
76
 
72
77
  let fileStr = await fs.readFile(path, "utf-8");
@@ -1,39 +1,40 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- import type { StringMap, CpeakRequest, CpeakResponse, Next } from "../types";
5
-
6
- const MIME_TYPES: StringMap = {
7
- html: "text/html",
8
- css: "text/css",
9
- js: "application/javascript",
10
- jpg: "image/jpeg",
11
- jpeg: "image/jpeg",
12
- png: "image/png",
13
- svg: "image/svg+xml",
14
- txt: "text/plain",
15
- eot: "application/vnd.ms-fontobject",
16
- otf: "font/otf",
17
- ttf: "font/ttf",
18
- woff: "font/woff",
19
- woff2: "font/woff2",
20
- gif: "image/gif",
21
- ico: "image/x-icon",
22
- json: "application/json",
23
- webmanifest: "application/manifest+json"
24
- };
4
+ import { MIME_TYPES } from "../internal/mimeTypes";
5
+ import type { CpeakRequest, CpeakResponse, Next } from "../types";
25
6
 
26
7
  const serveStatic = (
27
8
  folderPath: string,
28
- newMimeTypes?: StringMap,
29
- options?: { prefix?: string }
9
+ options?: { prefix?: string; live?: boolean }
30
10
  ) => {
31
- // For new user defined mime types
32
- if (newMimeTypes) {
33
- Object.assign(MIME_TYPES, newMimeTypes);
34
- }
35
-
36
11
  const prefix = options?.prefix ?? "";
12
+ const live = options?.live ?? false;
13
+
14
+ // This process the folder on every request, which is useful during development when files are changing often.
15
+ // In production, it's better to process the folder once and store the file paths in memory for faster access if file names are not changing often.
16
+ // If file names dynamically change often in production, then live option can be set to true to process the folder on every request, but it may have performance implications.
17
+ if (live) {
18
+ const resolvedFolder = path.resolve(folderPath);
19
+
20
+ return async function (req: CpeakRequest, res: CpeakResponse, next: Next) {
21
+ const url = req.url;
22
+ if (typeof url !== "string") return next();
23
+
24
+ const pathname = url.split("?")[0];
25
+ const unprefixed = prefix ? pathname.slice(prefix.length) : pathname;
26
+ const filePath = path.join(resolvedFolder, unprefixed);
27
+ const fileExtension = path.extname(filePath).slice(1);
28
+ const mime = MIME_TYPES[fileExtension];
29
+
30
+ if (!mime || !filePath.startsWith(resolvedFolder)) return next();
31
+
32
+ const stat = await fs.promises.stat(filePath).catch(() => null);
33
+ if (stat?.isFile()) return res.sendFile(filePath, mime);
34
+
35
+ next();
36
+ };
37
+ }
37
38
 
38
39
  function processFolder(folderPath: string, parentFolder: string) {
39
40
  const staticFiles: string[] = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cpeak",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "A minimal and fast Node.js HTTP framework.",
5
5
  "type": "module",
6
6
  "scripts": {