cpeak 2.4.2 → 2.5.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 +48 -40
- package/dist/index.d.ts +24 -8
- package/dist/index.js +181 -110
- package/dist/index.js.map +1 -1
- package/lib/index.ts +166 -128
- package/lib/types.ts +7 -7
- package/lib/utils/paseJSON.ts +83 -0
- package/package.json +1 -1
- package/lib/utils/parseJSON.ts +0 -30
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Cpeak is a **pure ESM** package, and to use it, your project needs to be an ESM
|
|
|
52
52
|
```javascript
|
|
53
53
|
import cpeak from "cpeak";
|
|
54
54
|
|
|
55
|
-
const server =
|
|
55
|
+
const server = cpeak();
|
|
56
56
|
|
|
57
57
|
server.route("get", "/", (req, res) => {
|
|
58
58
|
res.json({ message: "Hi there!" });
|
|
@@ -84,7 +84,7 @@ import cpeak, { serveStatic, parseJSON } from "cpeak";
|
|
|
84
84
|
Initialize the Cpeak server like this:
|
|
85
85
|
|
|
86
86
|
```javascript
|
|
87
|
-
const server =
|
|
87
|
+
const server = cpeak();
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
Now you can use this server object to start listening, add route logic, add middleware functions, and handle errors.
|
|
@@ -131,7 +131,7 @@ const requireAuth = (req, res, next, handleErr) => {
|
|
|
131
131
|
return handleErr({ status: 401, message: "Unauthorized" });
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
-
server.route("get", "/profile", requireAuth, (req, res
|
|
134
|
+
server.route("get", "/profile", requireAuth, (req, res) => {
|
|
135
135
|
console.log(req.test); // this is a test value
|
|
136
136
|
});
|
|
137
137
|
```
|
|
@@ -145,7 +145,7 @@ server.route(
|
|
|
145
145
|
requireAuth,
|
|
146
146
|
anotherFunction,
|
|
147
147
|
oneMore,
|
|
148
|
-
(req, res
|
|
148
|
+
(req, res) => {
|
|
149
149
|
// your logic
|
|
150
150
|
}
|
|
151
151
|
);
|
|
@@ -165,16 +165,15 @@ First add the HTTP method name you want to handle, then the path, and finally, t
|
|
|
165
165
|
|
|
166
166
|
### URL Variables & Parameters
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
We can also do custom path management, and we call them `vars` (short for URL variables).
|
|
168
|
+
To be more consistent with the broader Node.js community and frameworks, we call the HTTP URL parameters (query strings) '**query**', and the path variables (route parameters) '**params**'.
|
|
170
169
|
|
|
171
170
|
Here’s how we can read both:
|
|
172
171
|
|
|
173
172
|
```javascript
|
|
174
173
|
// Imagine request URL is example.com/test/my-title/more-text?filter=newest
|
|
175
174
|
server.route("patch", "/test/:title/more-text", (req, res) => {
|
|
176
|
-
const title = req.
|
|
177
|
-
const filter = req.
|
|
175
|
+
const title = req.params.title;
|
|
176
|
+
const filter = req.query.filter;
|
|
178
177
|
|
|
179
178
|
console.log(title); // my-title
|
|
180
179
|
console.log(filter); // newest
|
|
@@ -206,11 +205,23 @@ res.redirect("https://whatever.com");
|
|
|
206
205
|
|
|
207
206
|
### Error Handling
|
|
208
207
|
|
|
209
|
-
If anywhere in your route functions or route middleware functions you want to return an error,
|
|
208
|
+
If anywhere in your route functions or route middleware functions you want to return an error, you can just throw the error and let the automatic error handler catch it:
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
server.route("get", "/api/document/:title", (req, res) => {
|
|
212
|
+
const title = req.params.title;
|
|
213
|
+
|
|
214
|
+
if (title.length > 500) throw { status: 400, message: "Title too long." };
|
|
215
|
+
|
|
216
|
+
// The rest of your logic...
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
You can also make use of the `handleErr` callback function like this:
|
|
210
221
|
|
|
211
222
|
```javascript
|
|
212
223
|
server.route("get", "/api/document/:title", (req, res, handleErr) => {
|
|
213
|
-
const title = req.
|
|
224
|
+
const title = req.params.title;
|
|
214
225
|
|
|
215
226
|
if (title.length > 500)
|
|
216
227
|
return handleErr({ status: 400, message: "Title too long." });
|
|
@@ -219,7 +230,7 @@ server.route("get", "/api/document/:title", (req, res, handleErr) => {
|
|
|
219
230
|
});
|
|
220
231
|
```
|
|
221
232
|
|
|
222
|
-
|
|
233
|
+
**Make sure** to call the `server.handleErr` and pass a function like this to have the automatic error handler work properly:
|
|
223
234
|
|
|
224
235
|
```javascript
|
|
225
236
|
server.handleErr((error, req, res) => {
|
|
@@ -229,13 +240,13 @@ server.handleErr((error, req, res) => {
|
|
|
229
240
|
// Log the unexpected errors somewhere so you can keep track of them...
|
|
230
241
|
console.error(error);
|
|
231
242
|
res.status(500).json({
|
|
232
|
-
error: "Sorry, something unexpected happened on our side."
|
|
243
|
+
error: "Sorry, something unexpected happened on our side."
|
|
233
244
|
});
|
|
234
245
|
}
|
|
235
246
|
});
|
|
236
247
|
```
|
|
237
248
|
|
|
238
|
-
|
|
249
|
+
_The error object is the object that you threw or passed to the `handleErr` function earlier in your routes._
|
|
239
250
|
|
|
240
251
|
### Listening
|
|
241
252
|
|
|
@@ -270,7 +281,7 @@ With this middleware function, you can automatically set a folder in your projec
|
|
|
270
281
|
```javascript
|
|
271
282
|
server.beforeEach(
|
|
272
283
|
serveStatic("./public", {
|
|
273
|
-
mp3: "audio/mpeg"
|
|
284
|
+
mp3: "audio/mpeg"
|
|
274
285
|
})
|
|
275
286
|
);
|
|
276
287
|
```
|
|
@@ -298,7 +309,9 @@ If you have file types in your public folder that are not one of the following,
|
|
|
298
309
|
With this middleware function, you can easily read and send JSON in HTTP message bodies in route and middleware functions. Fire it up like this:
|
|
299
310
|
|
|
300
311
|
```javascript
|
|
301
|
-
|
|
312
|
+
// You can pass an optional limit option to indicate the maximum
|
|
313
|
+
// JSON body size that your server will accept.
|
|
314
|
+
server.beforeEach(parseJSON({ limit: 1024 * 1024 })); // default value is 1024 * 1024 (1MB)
|
|
302
315
|
```
|
|
303
316
|
|
|
304
317
|
Read and send JSON from HTTP messages like this:
|
|
@@ -333,7 +346,7 @@ server.route("get", "/", (req, res, next) => {
|
|
|
333
346
|
"./public/index.html",
|
|
334
347
|
{
|
|
335
348
|
title: "Page title",
|
|
336
|
-
name: "Allan"
|
|
349
|
+
name: "Allan"
|
|
337
350
|
},
|
|
338
351
|
"text/html"
|
|
339
352
|
);
|
|
@@ -360,18 +373,18 @@ Here you can see all the features that Cpeak offers, in one small piece of code:
|
|
|
360
373
|
```javascript
|
|
361
374
|
import cpeak, { serveStatic, parseJSON, render } from "cpeak";
|
|
362
375
|
|
|
363
|
-
const server =
|
|
376
|
+
const server = cpeak();
|
|
364
377
|
|
|
365
378
|
server.beforeEach(
|
|
366
379
|
serveStatic("./public", {
|
|
367
|
-
mp3: "audio/mpeg"
|
|
380
|
+
mp3: "audio/mpeg"
|
|
368
381
|
})
|
|
369
382
|
);
|
|
370
383
|
|
|
371
384
|
server.beforeEach(render());
|
|
372
385
|
|
|
373
386
|
// For parsing JSON bodies
|
|
374
|
-
server.beforeEach(parseJSON);
|
|
387
|
+
server.beforeEach(parseJSON());
|
|
375
388
|
|
|
376
389
|
// Adding custom middleware functions
|
|
377
390
|
server.beforeEach((req, res, next) => {
|
|
@@ -383,7 +396,7 @@ server.beforeEach((req, res, next) => {
|
|
|
383
396
|
const testRouteMiddleware = (req, res, next, handleErr) => {
|
|
384
397
|
req.whatever = "some calculated value maybe";
|
|
385
398
|
|
|
386
|
-
if (req.
|
|
399
|
+
if (req.params.test !== "something special") {
|
|
387
400
|
return handleErr({ status: 400, message: "an error message" });
|
|
388
401
|
}
|
|
389
402
|
|
|
@@ -396,7 +409,7 @@ server.route("get", "/", (req, res, next) => {
|
|
|
396
409
|
"<path-to-file-relative-to-cwd>",
|
|
397
410
|
{
|
|
398
411
|
test: "some testing value",
|
|
399
|
-
number: "2343242"
|
|
412
|
+
number: "2343242"
|
|
400
413
|
},
|
|
401
414
|
"<mime-type>"
|
|
402
415
|
);
|
|
@@ -406,28 +419,23 @@ server.route("get", "/old-url", testRouteMiddleware, (req, res, next) => {
|
|
|
406
419
|
return res.redirect("/new-url");
|
|
407
420
|
});
|
|
408
421
|
|
|
409
|
-
server.route(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
testRouteMiddleware,
|
|
413
|
-
(req, res, handleErr) => {
|
|
414
|
-
// Reading URL variables
|
|
415
|
-
const title = req.vars.title;
|
|
422
|
+
server.route("get", "/api/document/:title", testRouteMiddleware, (req, res) => {
|
|
423
|
+
// Reading URL variables (route parameters)
|
|
424
|
+
const title = req.params.title;
|
|
416
425
|
|
|
417
|
-
|
|
418
|
-
|
|
426
|
+
// Reading URL parameters (query strings) (like /users?filter=active)
|
|
427
|
+
const filter = req.query.filter;
|
|
419
428
|
|
|
420
|
-
|
|
421
|
-
|
|
429
|
+
// Reading JSON request body
|
|
430
|
+
const anything = req.body.anything;
|
|
422
431
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
432
|
+
// Handling errors
|
|
433
|
+
if (anything === "not-expected-thing")
|
|
434
|
+
throw { status: 400, message: "Invalid property." };
|
|
426
435
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
);
|
|
436
|
+
// Sending a JSON response
|
|
437
|
+
res.status(200).json({ message: "This is a test response" });
|
|
438
|
+
});
|
|
431
439
|
|
|
432
440
|
// Sending a file response
|
|
433
441
|
server.route("get", "/file", (req, res) => {
|
|
@@ -442,7 +450,7 @@ server.handleErr((error, req, res) => {
|
|
|
442
450
|
} else {
|
|
443
451
|
console.error(error);
|
|
444
452
|
res.status(500).json({
|
|
445
|
-
error: "Sorry, something unexpected happened from our side."
|
|
453
|
+
error: "Sorry, something unexpected happened from our side."
|
|
446
454
|
});
|
|
447
455
|
}
|
|
448
456
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,11 @@ import http, { IncomingMessage, ServerResponse } from 'node:http';
|
|
|
2
2
|
|
|
3
3
|
type Cpeak$1 = ReturnType<typeof cpeak>;
|
|
4
4
|
type StringMap = Record<string, string>;
|
|
5
|
-
interface CpeakRequest<ReqBody = any,
|
|
6
|
-
params:
|
|
7
|
-
|
|
5
|
+
interface CpeakRequest<ReqBody = any, ReqQueries = any> extends IncomingMessage {
|
|
6
|
+
params: StringMap;
|
|
7
|
+
query: ReqQueries;
|
|
8
8
|
body?: ReqBody;
|
|
9
9
|
[key: string]: any;
|
|
10
|
-
query: ReqParams;
|
|
11
10
|
}
|
|
12
11
|
interface CpeakResponse extends ServerResponse {
|
|
13
12
|
sendFile: (path: string, mime: string) => Promise<void>;
|
|
@@ -33,18 +32,35 @@ interface RoutesMap {
|
|
|
33
32
|
|
|
34
33
|
declare const serveStatic: (folderPath: string, newMimeTypes?: StringMap) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void | Promise<void>;
|
|
35
34
|
|
|
36
|
-
declare const parseJSON: (
|
|
35
|
+
declare const parseJSON: (options?: {
|
|
36
|
+
limit?: number;
|
|
37
|
+
}) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
37
38
|
|
|
38
39
|
declare const render: () => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
39
40
|
|
|
40
|
-
declare function frameworkError(message: string, skipFn: Function, code?: string): Error & {
|
|
41
|
+
declare function frameworkError(message: string, skipFn: Function, code?: string, status?: number): Error & {
|
|
41
42
|
code?: string;
|
|
43
|
+
cpeak_err?: boolean;
|
|
42
44
|
};
|
|
43
45
|
declare enum ErrorCode {
|
|
44
46
|
MISSING_MIME = "CPEAK_ERR_MISSING_MIME",
|
|
45
47
|
FILE_NOT_FOUND = "CPEAK_ERR_FILE_NOT_FOUND",
|
|
46
48
|
NOT_A_FILE = "CPEAK_ERR_NOT_A_FILE",
|
|
47
|
-
SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL"
|
|
49
|
+
SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL",
|
|
50
|
+
INVALID_JSON = "CPEAK_ERR_INVALID_JSON",
|
|
51
|
+
PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE"
|
|
52
|
+
}
|
|
53
|
+
declare class CpeakIncomingMessage extends http.IncomingMessage {
|
|
54
|
+
body: any;
|
|
55
|
+
params: StringMap;
|
|
56
|
+
private _query?;
|
|
57
|
+
get query(): StringMap;
|
|
58
|
+
}
|
|
59
|
+
declare class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
|
|
60
|
+
sendFile(path: string, mime: string): Promise<void>;
|
|
61
|
+
status(code: number): this;
|
|
62
|
+
redirect(location: string): this;
|
|
63
|
+
json(data: any): void;
|
|
48
64
|
}
|
|
49
65
|
declare class Cpeak {
|
|
50
66
|
#private;
|
|
@@ -56,7 +72,7 @@ declare class Cpeak {
|
|
|
56
72
|
route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]): void;
|
|
57
73
|
beforeEach(cb: Middleware): void;
|
|
58
74
|
handleErr(cb: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void): void;
|
|
59
|
-
listen(port: number, cb?: () => void): http.Server<typeof
|
|
75
|
+
listen(port: number, cb?: () => void): http.Server<typeof CpeakIncomingMessage, typeof CpeakServerResponse>;
|
|
60
76
|
close(cb?: (err?: Error) => void): void;
|
|
61
77
|
}
|
|
62
78
|
|
package/dist/index.js
CHANGED
|
@@ -65,22 +65,58 @@ var serveStatic = (folderPath, newMimeTypes) => {
|
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
// lib/utils/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
};
|
|
84
120
|
};
|
|
85
121
|
|
|
86
122
|
// lib/utils/render.ts
|
|
@@ -108,7 +144,6 @@ function renderTemplate(templateStr, data) {
|
|
|
108
144
|
return result.join("");
|
|
109
145
|
}
|
|
110
146
|
var render = () => {
|
|
111
|
-
console.log("render.ts loaded");
|
|
112
147
|
return function(req, res, next) {
|
|
113
148
|
res.render = async (path2, data, mime) => {
|
|
114
149
|
if (!mime) {
|
|
@@ -127,10 +162,12 @@ var render = () => {
|
|
|
127
162
|
};
|
|
128
163
|
|
|
129
164
|
// lib/index.ts
|
|
130
|
-
function frameworkError(message, skipFn, code) {
|
|
165
|
+
function frameworkError(message, skipFn, code, status) {
|
|
131
166
|
const err = new Error(message);
|
|
132
167
|
Error.captureStackTrace(err, skipFn);
|
|
168
|
+
err.cpeak_err = true;
|
|
133
169
|
if (code) err.code = code;
|
|
170
|
+
if (status) err.status = status;
|
|
134
171
|
return err;
|
|
135
172
|
}
|
|
136
173
|
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
@@ -138,101 +175,123 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
138
175
|
ErrorCode2["FILE_NOT_FOUND"] = "CPEAK_ERR_FILE_NOT_FOUND";
|
|
139
176
|
ErrorCode2["NOT_A_FILE"] = "CPEAK_ERR_NOT_A_FILE";
|
|
140
177
|
ErrorCode2["SEND_FILE_FAIL"] = "CPEAK_ERR_SEND_FILE_FAIL";
|
|
178
|
+
ErrorCode2["INVALID_JSON"] = "CPEAK_ERR_INVALID_JSON";
|
|
179
|
+
ErrorCode2["PAYLOAD_TOO_LARGE"] = "CPEAK_ERR_PAYLOAD_TOO_LARGE";
|
|
141
180
|
return ErrorCode2;
|
|
142
181
|
})(ErrorCode || {});
|
|
182
|
+
var CpeakIncomingMessage = class extends http.IncomingMessage {
|
|
183
|
+
// We define body and params here for better V8 optimization (not changing the shape of the object at runtime)
|
|
184
|
+
body = void 0;
|
|
185
|
+
params = {};
|
|
186
|
+
_query;
|
|
187
|
+
// Parse the URL parameters (like /users?key1=value1&key2=value2)
|
|
188
|
+
// We will call this query to be more familiar with other node.js frameworks.
|
|
189
|
+
// This is a getter method (accessed like a property)
|
|
190
|
+
get query() {
|
|
191
|
+
if (this._query) return this._query;
|
|
192
|
+
const url = this.url || "";
|
|
193
|
+
const qIndex = url.indexOf("?");
|
|
194
|
+
if (qIndex === -1) {
|
|
195
|
+
this._query = {};
|
|
196
|
+
} else {
|
|
197
|
+
const searchParams = new URLSearchParams(url.substring(qIndex + 1));
|
|
198
|
+
this._query = Object.fromEntries(searchParams.entries());
|
|
199
|
+
}
|
|
200
|
+
return this._query;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var CpeakServerResponse = class extends http.ServerResponse {
|
|
204
|
+
// Send a file back to the client
|
|
205
|
+
async sendFile(path2, mime) {
|
|
206
|
+
if (!mime) {
|
|
207
|
+
throw frameworkError(
|
|
208
|
+
'MIME type is missing. Use res.sendFile(path, "mime-type").',
|
|
209
|
+
this.sendFile,
|
|
210
|
+
"CPEAK_ERR_MISSING_MIME" /* MISSING_MIME */
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const stat = await fs3.stat(path2);
|
|
215
|
+
if (!stat.isFile()) {
|
|
216
|
+
throw frameworkError(
|
|
217
|
+
`Not a file: ${path2}`,
|
|
218
|
+
this.sendFile,
|
|
219
|
+
"CPEAK_ERR_NOT_A_FILE" /* NOT_A_FILE */
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
this.setHeader("Content-Type", mime);
|
|
223
|
+
this.setHeader("Content-Length", String(stat.size));
|
|
224
|
+
await pipeline(createReadStream(path2), this);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
if (err?.code === "ENOENT") {
|
|
227
|
+
throw frameworkError(
|
|
228
|
+
`File not found: ${path2}`,
|
|
229
|
+
this.sendFile,
|
|
230
|
+
"CPEAK_ERR_FILE_NOT_FOUND" /* FILE_NOT_FOUND */
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
throw frameworkError(
|
|
234
|
+
`Failed to send file: ${path2}`,
|
|
235
|
+
this.sendFile,
|
|
236
|
+
"CPEAK_ERR_SEND_FILE_FAIL" /* SEND_FILE_FAIL */
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Set the status code of the response
|
|
241
|
+
status(code) {
|
|
242
|
+
this.statusCode = code;
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
// Redirects to a new URL
|
|
246
|
+
redirect(location) {
|
|
247
|
+
this.writeHead(302, { Location: location });
|
|
248
|
+
this.end();
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
// Send a json data back to the client (for small json data, less than the highWaterMark)
|
|
252
|
+
json(data) {
|
|
253
|
+
this.setHeader("Content-Type", "application/json");
|
|
254
|
+
this.end(JSON.stringify(data));
|
|
255
|
+
}
|
|
256
|
+
};
|
|
143
257
|
var Cpeak = class {
|
|
144
258
|
server;
|
|
145
259
|
routes;
|
|
146
260
|
middleware;
|
|
147
261
|
_handleErr;
|
|
148
262
|
constructor() {
|
|
149
|
-
this.server = http.createServer(
|
|
263
|
+
this.server = http.createServer({
|
|
264
|
+
IncomingMessage: CpeakIncomingMessage,
|
|
265
|
+
ServerResponse: CpeakServerResponse
|
|
266
|
+
});
|
|
150
267
|
this.routes = {};
|
|
151
268
|
this.middleware = [];
|
|
152
|
-
this.server.on("request", (req, res) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
);
|
|
180
|
-
}
|
|
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) => {
|
|
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) => {
|
|
207
273
|
if (index === middleware.length) {
|
|
208
274
|
try {
|
|
209
|
-
|
|
275
|
+
await cb(req2, res2, (error) => {
|
|
210
276
|
res2.setHeader("Connection", "close");
|
|
211
277
|
this._handleErr?.(error, req2, res2);
|
|
212
278
|
});
|
|
213
|
-
if (handlerResult && typeof handlerResult.then === "function") {
|
|
214
|
-
handlerResult.catch((error) => {
|
|
215
|
-
res2.setHeader("Connection", "close");
|
|
216
|
-
this._handleErr?.(error, req2, res2);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
return handlerResult;
|
|
220
279
|
} catch (error) {
|
|
221
280
|
res2.setHeader("Connection", "close");
|
|
222
281
|
this._handleErr?.(error, req2, res2);
|
|
223
282
|
}
|
|
224
283
|
} else {
|
|
225
284
|
try {
|
|
226
|
-
|
|
285
|
+
await middleware[index](
|
|
227
286
|
req2,
|
|
228
287
|
res2,
|
|
229
288
|
// The next function
|
|
230
|
-
(error) => {
|
|
289
|
+
async (error) => {
|
|
231
290
|
if (error) {
|
|
232
291
|
res2.setHeader("Connection", "close");
|
|
233
292
|
return this._handleErr?.(error, req2, res2);
|
|
234
293
|
}
|
|
235
|
-
runHandler(req2, res2, middleware, cb, index + 1);
|
|
294
|
+
await runHandler(req2, res2, middleware, cb, index + 1);
|
|
236
295
|
},
|
|
237
296
|
// Error handler for a route middleware
|
|
238
297
|
(error) => {
|
|
@@ -240,38 +299,50 @@ var Cpeak = class {
|
|
|
240
299
|
this._handleErr?.(error, req2, res2);
|
|
241
300
|
}
|
|
242
301
|
);
|
|
243
|
-
if (middlewareResult && typeof middlewareResult.then === "function") {
|
|
244
|
-
middlewareResult.catch((error) => {
|
|
245
|
-
res2.setHeader("Connection", "close");
|
|
246
|
-
this._handleErr?.(error, req2, res2);
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
302
|
} catch (error) {
|
|
250
303
|
res2.setHeader("Connection", "close");
|
|
251
304
|
this._handleErr?.(error, req2, res2);
|
|
252
305
|
}
|
|
253
306
|
}
|
|
254
307
|
};
|
|
255
|
-
const runMiddleware = (req2, res2, middleware, index) => {
|
|
308
|
+
const runMiddleware = async (req2, res2, middleware, index) => {
|
|
256
309
|
if (index === middleware.length) {
|
|
257
310
|
const routes = this.routes[req2.method?.toLowerCase() || ""];
|
|
258
311
|
if (routes && typeof routes[Symbol.iterator] === "function")
|
|
259
312
|
for (const route of routes) {
|
|
260
|
-
const match =
|
|
313
|
+
const match = urlWithoutQueries?.match(route.regex);
|
|
261
314
|
if (match) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
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
|
+
);
|
|
265
327
|
}
|
|
266
328
|
}
|
|
267
|
-
return res2.status(404).json({ error: `Cannot ${req2.method} ${
|
|
329
|
+
return res2.status(404).json({ error: `Cannot ${req2.method} ${urlWithoutQueries}` });
|
|
268
330
|
} else {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
}
|
|
272
343
|
}
|
|
273
344
|
};
|
|
274
|
-
runMiddleware(req, res, this.middleware, 0);
|
|
345
|
+
await runMiddleware(req, res, this.middleware, 0);
|
|
275
346
|
});
|
|
276
347
|
}
|
|
277
348
|
route(method, path2, ...args) {
|
|
@@ -300,23 +371,23 @@ var Cpeak = class {
|
|
|
300
371
|
// PRIVATE METHODS:
|
|
301
372
|
// ------------------------------
|
|
302
373
|
#pathToRegex(path2) {
|
|
303
|
-
const
|
|
374
|
+
const paramNames = [];
|
|
304
375
|
const regexString = "^" + path2.replace(/:\w+/g, (match, offset) => {
|
|
305
|
-
|
|
376
|
+
paramNames.push(match.slice(1));
|
|
306
377
|
return "([^/]+)";
|
|
307
378
|
}) + "$";
|
|
308
379
|
const regex = new RegExp(regexString);
|
|
309
380
|
return regex;
|
|
310
381
|
}
|
|
311
|
-
#
|
|
312
|
-
const
|
|
313
|
-
(
|
|
382
|
+
#extractPathVariables(path2, match) {
|
|
383
|
+
const paramNames = (path2.match(/:\w+/g) || []).map(
|
|
384
|
+
(param) => param.slice(1)
|
|
314
385
|
);
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
386
|
+
const params = {};
|
|
387
|
+
paramNames.forEach((name, index) => {
|
|
388
|
+
params[name] = match[index + 1];
|
|
318
389
|
});
|
|
319
|
-
return
|
|
390
|
+
return params;
|
|
320
391
|
}
|
|
321
392
|
};
|
|
322
393
|
function cpeak() {
|