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/README.md +120 -72
- package/dist/index.d.ts +77 -75
- package/dist/index.js +307 -135
- package/dist/index.js.map +1 -1
- package/lib/index.ts +108 -118
- package/lib/internal/errors.ts +35 -0
- package/lib/internal/mimeTypes.ts +22 -0
- package/lib/internal/router.ts +259 -0
- package/lib/types.ts +18 -20
- package/lib/utils/render.ts +11 -6
- package/lib/utils/serveStatic.ts +29 -28
- package/package.json +1 -1
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
|
|
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) =>
|
|
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
|
-
) =>
|
|
57
|
+
) => unknown;
|
|
52
58
|
|
|
53
|
-
// Route middleware:
|
|
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
|
-
|
|
59
|
-
) => void | Promise<void>;
|
|
63
|
+
next: Next
|
|
64
|
+
) => unknown;
|
|
60
65
|
|
|
61
|
-
// Route handlers: (req, res,
|
|
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
|
-
|
|
66
|
-
) => void | Promise<void>;
|
|
69
|
+
res: CpeakResponse
|
|
70
|
+
) => unknown;
|
|
67
71
|
|
|
68
|
-
//
|
|
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
|
-
}
|
package/lib/utils/render.ts
CHANGED
|
@@ -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
|
|
63
|
+
mime?: string
|
|
63
64
|
) => {
|
|
64
|
-
// check if mime is specified, if not return an error
|
|
65
65
|
if (!mime) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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");
|
package/lib/utils/serveStatic.ts
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import
|
|
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
|
-
|
|
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[] = [];
|