@ndiinginc/router 4.1.3 → 4.3.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/package.json +3 -3
- package/src/app.js +44 -35
- package/src/cache.js +1 -5
- package/src/error.js +26 -0
- package/src/index.js +11 -0
- package/src/middleware/cors.js +139 -0
- package/src/{middleware.js → middleware/json.js} +5 -2
- package/src/node.js +3 -8
- package/src/pattern.js +19 -19
- package/src/request.js +12 -18
- package/src/response.js +5 -6
- package/src/router.js +19 -34
- package/src/util.js +27 -0
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ndiinginc/router",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "./src/
|
|
5
|
+
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "nodemon --exec \"node -e \\\"require('@ndiinginc/test')(require('path').resolve('tests'),{verbose:true})\\\"\"",
|
|
8
|
-
"dev": "nodemon src/
|
|
8
|
+
"dev": "nodemon src/index.js",
|
|
9
9
|
"prettier": "npx prettier . --write",
|
|
10
10
|
"commit": "node -e \"require('@ndiinginc/commit')()\"",
|
|
11
11
|
"npm:dry-run": "npm publish --dry-run",
|
package/src/app.js
CHANGED
|
@@ -1,67 +1,76 @@
|
|
|
1
1
|
const http = require("node:http");
|
|
2
|
-
const {
|
|
2
|
+
const { Router } = require("./router.js");
|
|
3
3
|
const { Request } = require("./request.js");
|
|
4
|
-
const {
|
|
4
|
+
const { Response } = require("./response.js");
|
|
5
|
+
const { ErrorResponse } = require("./error.js");
|
|
6
|
+
|
|
7
|
+
/**@type {import('http').ServerOptions}*/
|
|
8
|
+
const SERVER_OPTIONS = {
|
|
9
|
+
IncomingMessage: Request,
|
|
10
|
+
ServerResponse: Response,
|
|
11
|
+
};
|
|
5
12
|
|
|
6
13
|
class App extends Router {
|
|
14
|
+
async withTimeout(handlerPromise, delay) {
|
|
15
|
+
let timeout;
|
|
16
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
17
|
+
timeout = setTimeout(() => reject(ErrorResponse.status(504)), delay);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await Promise.race([handlerPromise, timeoutPromise]).finally(() => clearTimeout(timeout));
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
/**
|
|
8
24
|
* @param {import('http').IncomingMessage} req
|
|
9
25
|
* @param {import('http').ServerResponse} res
|
|
10
26
|
*/
|
|
11
|
-
async
|
|
12
|
-
let err;
|
|
13
|
-
|
|
27
|
+
async handle(req, res) {
|
|
14
28
|
const { params, handlers, errorHandlers } = this.find(req.method, req.path);
|
|
15
29
|
req.params = params;
|
|
16
30
|
|
|
31
|
+
let err;
|
|
17
32
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
const handlerPromise = (async () => {
|
|
34
|
+
for (const handler of handlers) {
|
|
35
|
+
if (res.writableEnded) break;
|
|
36
|
+
await handler(req, res);
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
|
|
40
|
+
if (req.timeout) await this.withTimeout(handlerPromise, req.timeout);
|
|
41
|
+
else await handlerPromise;
|
|
22
42
|
} catch (error) {
|
|
23
43
|
err = error;
|
|
24
44
|
|
|
25
45
|
try {
|
|
26
46
|
for (const handler of errorHandlers) {
|
|
27
|
-
if (res.
|
|
47
|
+
if (res.writableEnded) break;
|
|
28
48
|
await handler(err, req, res);
|
|
29
49
|
}
|
|
30
50
|
} catch (error) {
|
|
31
|
-
err = error;
|
|
51
|
+
err = new Error(error.message, { cause: err });
|
|
32
52
|
}
|
|
33
53
|
}
|
|
34
54
|
|
|
35
|
-
if (!res.
|
|
36
|
-
if (!err)
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
if (!res.writableEnded) {
|
|
56
|
+
if (!err) err = ErrorResponse.status(404);
|
|
57
|
+
|
|
58
|
+
if (!res.statusCode || res.statusCode < 400) {
|
|
59
|
+
res.statusCode = err.statusCode || 500;
|
|
39
60
|
}
|
|
40
61
|
|
|
41
|
-
res.
|
|
42
|
-
const error = { message: err.message };
|
|
43
|
-
if (process.env.NODE_ENV === "development") error.stack = err.stack;
|
|
44
|
-
res.json({ error });
|
|
62
|
+
res.json({ error: { message: err.message, stack: err.stack } });
|
|
45
63
|
}
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
listen(...args) {
|
|
53
|
-
const serverOptions = {
|
|
54
|
-
IncomingMessage: Request,
|
|
55
|
-
ServerResponse: Response,
|
|
56
|
-
};
|
|
57
|
-
const requestListener = (req, res) => this.handleRequest(req, res);
|
|
58
|
-
|
|
59
|
-
const server = http.createServer(serverOptions, requestListener);
|
|
60
|
-
|
|
61
|
-
server.listen(...args);
|
|
62
|
-
|
|
66
|
+
listen(...params) {
|
|
67
|
+
const requestListener = (req, res) => this.handle(req, res);
|
|
68
|
+
const server = http.createServer(SERVER_OPTIONS, requestListener);
|
|
69
|
+
server.listen(...params);
|
|
63
70
|
return server;
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
App.SERVER_OPTIONS = SERVER_OPTIONS;
|
|
75
|
+
|
|
76
|
+
exports.App = App;
|
package/src/cache.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
class Cache {
|
|
2
2
|
constructor(size) {
|
|
3
3
|
this.size = size;
|
|
4
|
-
|
|
5
4
|
this.map = new Map();
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
get(key) {
|
|
9
8
|
if (!this.map.has(key)) return undefined;
|
|
10
|
-
|
|
11
9
|
const value = this.map.get(key);
|
|
12
|
-
|
|
13
10
|
this.map.delete(key);
|
|
14
11
|
this.map.set(key, value);
|
|
15
|
-
|
|
16
12
|
return value;
|
|
17
13
|
}
|
|
18
14
|
|
|
@@ -21,8 +17,8 @@ class Cache {
|
|
|
21
17
|
else if (this.map.size >= this.size) {
|
|
22
18
|
this.map.delete(this.map.keys().next().value);
|
|
23
19
|
}
|
|
24
|
-
|
|
25
20
|
this.map.set(key, value);
|
|
26
21
|
}
|
|
27
22
|
}
|
|
23
|
+
|
|
28
24
|
exports.Cache = Cache;
|
package/src/error.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
|
|
3
|
+
// Client error responses (400 – 499)
|
|
4
|
+
// Server error responses (500 – 599)
|
|
5
|
+
class ErrorResponse extends Error {
|
|
6
|
+
constructor({ statusCode, message } = {}, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
|
+
|
|
11
|
+
if (Error.captureStackTrace) {
|
|
12
|
+
Error.captureStackTrace(this, this.constructor);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static status(statusCode) {
|
|
19
|
+
return new ErrorResponse({
|
|
20
|
+
statusCode,
|
|
21
|
+
message: http.STATUS_CODES[statusCode],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
exports.ErrorResponse = ErrorResponse;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { inspect } = require("node:util");
|
|
2
|
+
const { json } = require("./middleware/json.js");
|
|
3
|
+
const { cors } = require("./middleware/cors.js");
|
|
4
|
+
const { App } = require("./app.js");
|
|
5
|
+
const { Router } = require("./router.js");
|
|
6
|
+
|
|
7
|
+
App.Router = Router;
|
|
8
|
+
App.json = json;
|
|
9
|
+
App.cors = cors;
|
|
10
|
+
|
|
11
|
+
module.exports = App;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
function pattern(string) {
|
|
2
|
+
return new RegExp(
|
|
3
|
+
`(${String(string)
|
|
4
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
5
|
+
.replace(/ /g, "")
|
|
6
|
+
.replace(/,/g, "|")
|
|
7
|
+
.replace(/\\\*/g, ".*")})`,
|
|
8
|
+
"i",
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef CORSOptions
|
|
14
|
+
* @property {String} [origin="*"]
|
|
15
|
+
* @property {Boolean} [credentials=false]
|
|
16
|
+
* @property {String|Array} headers
|
|
17
|
+
* @property {Number} [maxAge=0]
|
|
18
|
+
* @property {String|Array} methods
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {CORSOptions} options
|
|
23
|
+
*/
|
|
24
|
+
function cors({ origin = "*", credentials = false, headers, maxAge = 0, methods } = {}) {
|
|
25
|
+
const HEADERS_PATTERN = headers && pattern(headers);
|
|
26
|
+
const METHODS_PATTERN = methods && pattern(methods);
|
|
27
|
+
const ORIGIN_PATTERN = pattern(origin);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('http').ServerResponse} res
|
|
31
|
+
*/
|
|
32
|
+
function setAllowCredentials(res) {
|
|
33
|
+
if (credentials) {
|
|
34
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {import('http').IncomingMessage} req
|
|
40
|
+
* @param {import('http').ServerResponse} res
|
|
41
|
+
*/
|
|
42
|
+
function setAllowHeaders(req, res) {
|
|
43
|
+
const reqHeaders = req.headers["access-control-request-headers"];
|
|
44
|
+
if (!reqHeaders) return;
|
|
45
|
+
|
|
46
|
+
let value;
|
|
47
|
+
if (headers === "*" && !credentials) {
|
|
48
|
+
value = "*";
|
|
49
|
+
} else if (Array.isArray(headers) && HEADERS_PATTERN && HEADERS_PATTERN.test(reqHeaders)) {
|
|
50
|
+
value = headers;
|
|
51
|
+
}
|
|
52
|
+
if (value) res.setHeader("Access-Control-Allow-Headers", value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {import('http').IncomingMessage} req
|
|
57
|
+
* @param {import('http').ServerResponse} res
|
|
58
|
+
*/
|
|
59
|
+
function setAllowMethods(req, res) {
|
|
60
|
+
const reqMethod = req.headers["access-control-request-method"];
|
|
61
|
+
if (!reqMethod) return;
|
|
62
|
+
|
|
63
|
+
let value;
|
|
64
|
+
if (methods === "*" && !credentials) {
|
|
65
|
+
value = "*";
|
|
66
|
+
} else if (Array.isArray(methods) && METHODS_PATTERN && METHODS_PATTERN.test(reqMethod)) {
|
|
67
|
+
value = methods;
|
|
68
|
+
}
|
|
69
|
+
if (value) res.setHeader("Access-Control-Allow-Methods", value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {import('http').IncomingMessage} req
|
|
74
|
+
* @param {import('http').ServerResponse} res
|
|
75
|
+
*/
|
|
76
|
+
function setAllowOrigin(req, res) {
|
|
77
|
+
const reqOrigin = req.headers["origin"];
|
|
78
|
+
|
|
79
|
+
let value;
|
|
80
|
+
let explicit;
|
|
81
|
+
if (origin === "*") {
|
|
82
|
+
if (!credentials) value = "*";
|
|
83
|
+
else {
|
|
84
|
+
value = reqOrigin;
|
|
85
|
+
explicit = true;
|
|
86
|
+
}
|
|
87
|
+
} else if (origin === null) {
|
|
88
|
+
value = "null";
|
|
89
|
+
} else if (typeof origin === "string" && ORIGIN_PATTERN.test(reqOrigin)) {
|
|
90
|
+
value = origin;
|
|
91
|
+
explicit = true;
|
|
92
|
+
}
|
|
93
|
+
if (value) res.setHeader("Access-Control-Allow-Origin", value);
|
|
94
|
+
if (explicit) res.appendHeader("Vary", "Origin");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {import('http').ServerResponse} res
|
|
99
|
+
*/
|
|
100
|
+
function setExposeHeaders(res) {
|
|
101
|
+
let value;
|
|
102
|
+
if (headers === "*" && !credentials) {
|
|
103
|
+
value = "*";
|
|
104
|
+
} else if (Array.isArray(headers)) {
|
|
105
|
+
value = headers;
|
|
106
|
+
}
|
|
107
|
+
if (value) res.setHeader("Access-Control-Expose-Headers", value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {import('http').ServerResponse} res
|
|
112
|
+
*/
|
|
113
|
+
function setMaxAge(res) {
|
|
114
|
+
if (maxAge > 0) {
|
|
115
|
+
res.setHeader("Access-Control-Max-Age", maxAge);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {import('http').IncomingMessage} req
|
|
121
|
+
* @param {import('http').ServerResponse} res
|
|
122
|
+
*/
|
|
123
|
+
return (req, res) => {
|
|
124
|
+
setAllowCredentials(res);
|
|
125
|
+
setAllowOrigin(req, res);
|
|
126
|
+
if (req.method === "OPTIONS") {
|
|
127
|
+
setAllowHeaders(req, res);
|
|
128
|
+
setAllowMethods(req, res);
|
|
129
|
+
setMaxAge(res);
|
|
130
|
+
|
|
131
|
+
res.statusCode = 204;
|
|
132
|
+
res.setHeader("Content-Length", "0");
|
|
133
|
+
res.end();
|
|
134
|
+
} else {
|
|
135
|
+
setExposeHeaders(res);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
exports.cors = cors;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const http = require("node:http");
|
|
2
2
|
|
|
3
|
+
// middleware
|
|
3
4
|
function json({ limit = 100 * 1024 } = {}) {
|
|
4
5
|
const reviver = (key, value) => {
|
|
5
6
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
@@ -25,14 +26,15 @@ function json({ limit = 100 * 1024 } = {}) {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
let chunkLength = 0;
|
|
29
|
+
|
|
28
30
|
const chunks = [];
|
|
31
|
+
|
|
29
32
|
for await (const chunk of req) {
|
|
30
33
|
chunkLength += chunk.length;
|
|
31
34
|
if (chunkLength > limit) {
|
|
32
35
|
res.statusCode = 413;
|
|
33
36
|
throw new Error(http.STATUS_CODES[res.statusCode]);
|
|
34
37
|
}
|
|
35
|
-
|
|
36
38
|
chunks.push(chunk);
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -49,4 +51,5 @@ function json({ limit = 100 * 1024 } = {}) {
|
|
|
49
51
|
}
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
|
-
|
|
54
|
+
|
|
55
|
+
exports.json = json;
|
package/src/node.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
const { Pattern } = require("./pattern.js");
|
|
2
2
|
|
|
3
3
|
class Node {
|
|
4
|
-
constructor(segment) {
|
|
4
|
+
constructor(segment = "") {
|
|
5
5
|
this.segment = segment;
|
|
6
6
|
this.pattern = new Pattern(segment);
|
|
7
|
-
/**@type {Map<string,
|
|
7
|
+
/**@type {Map<string,Node>}*/
|
|
8
8
|
this.children = new Map();
|
|
9
|
-
/**@type {Map<string, FunctionHandler[]>}*/
|
|
10
9
|
this.handlers = new Map([
|
|
11
10
|
["ALL", []],
|
|
12
11
|
["GET", []],
|
|
@@ -26,13 +25,11 @@ class Node {
|
|
|
26
25
|
if (!this.children.has(segment)) {
|
|
27
26
|
this.children.set(segment, new Node(segment));
|
|
28
27
|
}
|
|
29
|
-
|
|
30
28
|
return this.children.get(segment);
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
32
|
* @param {Node} node
|
|
35
|
-
* @param {Number} offset
|
|
36
33
|
*/
|
|
37
34
|
merge(node, offset) {
|
|
38
35
|
for (const [method, handlers] of node.handlers) {
|
|
@@ -52,9 +49,6 @@ class Node {
|
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
/**
|
|
56
|
-
* @param {Number} offset
|
|
57
|
-
*/
|
|
58
52
|
setSeqOffset(offset) {
|
|
59
53
|
for (const [, handlers] of this.handlers) {
|
|
60
54
|
for (const handler of handlers) {
|
|
@@ -83,4 +77,5 @@ class Node {
|
|
|
83
77
|
return max;
|
|
84
78
|
}
|
|
85
79
|
}
|
|
80
|
+
|
|
86
81
|
exports.Node = Node;
|
package/src/pattern.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef
|
|
3
|
-
* @property {number} index
|
|
4
|
-
* @property {string} input
|
|
5
|
-
* @property {
|
|
2
|
+
* @typedef PatternExecResult
|
|
3
|
+
* @property {number} index
|
|
4
|
+
* @property {string} input
|
|
5
|
+
* @property {Record<string, string>} [groups]
|
|
6
|
+
* @property {number} length
|
|
6
7
|
*/
|
|
7
8
|
class Pattern {
|
|
8
9
|
constructor(input) {
|
|
9
|
-
const pattern = input
|
|
10
|
-
.replace(/[
|
|
10
|
+
const pattern = `^${input
|
|
11
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
11
12
|
.replace(/:(\w+)/g, "(?<$1>[^/]+)")
|
|
12
|
-
.replace(/\\\*/g, ".*")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
value: new RegExp(`^${pattern}$`),
|
|
16
|
-
writable: true,
|
|
17
|
-
enumerable: false,
|
|
13
|
+
.replace(/\\\*/g, ".*")}$`;
|
|
14
|
+
Object.defineProperty(this, "regex", {
|
|
15
|
+
value: new RegExp(pattern),
|
|
18
16
|
configurable: true,
|
|
17
|
+
enumerable: false,
|
|
19
18
|
});
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
test(input) {
|
|
23
|
-
return this.re.test(input);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
21
|
/**
|
|
27
|
-
* @param {
|
|
28
|
-
* @returns {
|
|
22
|
+
* @param {string} input
|
|
23
|
+
* @returns {PatternExecResult | null}
|
|
29
24
|
*/
|
|
30
25
|
exec(input) {
|
|
31
|
-
return this.
|
|
26
|
+
return this.regex.exec(input);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test(input) {
|
|
30
|
+
return this.regex.test(input);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
33
|
+
|
|
34
34
|
exports.Pattern = Pattern;
|
package/src/request.js
CHANGED
|
@@ -1,43 +1,37 @@
|
|
|
1
1
|
const http = require("node:http");
|
|
2
|
+
const { parseUrl, parseQueryString } = require("./util.js");
|
|
2
3
|
|
|
3
4
|
class Request extends http.IncomingMessage {
|
|
4
|
-
|
|
5
|
+
_path;
|
|
6
|
+
_parsed;
|
|
7
|
+
_query;
|
|
8
|
+
params = {};
|
|
9
|
+
timeout = 0;
|
|
5
10
|
|
|
6
11
|
get path() {
|
|
7
12
|
if (this._path) return this._path;
|
|
8
13
|
|
|
9
|
-
if (this.url.indexOf(
|
|
14
|
+
if (this.url.indexOf("?") === -1) {
|
|
10
15
|
this._path = this.url;
|
|
11
16
|
return this._path;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
this.
|
|
15
|
-
this._path = this.
|
|
19
|
+
this._parsed = parseUrl(this.url);
|
|
20
|
+
this._path = this._parsed.path;
|
|
16
21
|
return this._path;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
get query() {
|
|
20
25
|
if (this._query) return this._query;
|
|
21
26
|
|
|
22
|
-
if (!this.
|
|
27
|
+
if (!this._parsed) {
|
|
23
28
|
this._query = {};
|
|
24
29
|
return this._query;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
for (const [name, value] of this.parsed.searchParams) {
|
|
29
|
-
if (query[name]) {
|
|
30
|
-
if (Array.isArray(query[name])) {
|
|
31
|
-
query[name].push(value);
|
|
32
|
-
} else {
|
|
33
|
-
query[name] = [query[name], value];
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
query[name] = value;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
this._query = query;
|
|
32
|
+
this._query = parseQueryString(this._parsed.queryString);
|
|
40
33
|
return this._query;
|
|
41
34
|
}
|
|
42
35
|
}
|
|
36
|
+
|
|
43
37
|
exports.Request = Request;
|
package/src/response.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
const http = require("node:http");
|
|
2
2
|
|
|
3
3
|
class Response extends http.ServerResponse {
|
|
4
|
-
send(
|
|
5
|
-
this.end(
|
|
6
|
-
this.done = true;
|
|
4
|
+
send(chunk) {
|
|
5
|
+
this.end(chunk);
|
|
7
6
|
}
|
|
8
7
|
|
|
9
|
-
json(
|
|
10
|
-
this.setHeader("
|
|
11
|
-
this.
|
|
8
|
+
json(value) {
|
|
9
|
+
this.setHeader("Content-Type", "application/json");
|
|
10
|
+
this.end(JSON.stringify(value));
|
|
12
11
|
}
|
|
13
12
|
}
|
|
14
13
|
exports.Response = Response;
|
package/src/router.js
CHANGED
|
@@ -1,45 +1,19 @@
|
|
|
1
|
-
const { Node } = require("./node");
|
|
2
|
-
const { Cache } = require("./cache");
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @callback Handler
|
|
6
|
-
* @param {*} req
|
|
7
|
-
* @param {*} res
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* @callback Middleware
|
|
11
|
-
* @param {*} req
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* @callback ErrorHandler
|
|
15
|
-
* @param {*} err
|
|
16
|
-
* @param {*} req
|
|
17
|
-
* @param {*} res
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* @typedef {Handler|Middleware|ErrorHandler} FunctionHandler
|
|
21
|
-
*/
|
|
1
|
+
const { Node } = require("./node.js");
|
|
2
|
+
const { Cache } = require("./cache.js");
|
|
22
3
|
|
|
23
4
|
class Router {
|
|
24
5
|
constructor(options = {}) {
|
|
25
|
-
this.cacheSize = options.
|
|
6
|
+
this.cacheSize = options.size ?? 1000;
|
|
26
7
|
|
|
27
|
-
this.root = new Node(
|
|
8
|
+
this.root = new Node();
|
|
28
9
|
this.seq = 0;
|
|
29
10
|
this.cache = new Cache(this.cacheSize);
|
|
30
11
|
}
|
|
31
12
|
|
|
32
13
|
split(path) {
|
|
33
|
-
if (path === "/") return [];
|
|
34
|
-
|
|
35
14
|
return path.split("/").filter(Boolean);
|
|
36
15
|
}
|
|
37
16
|
|
|
38
|
-
/**
|
|
39
|
-
* @param {*} method
|
|
40
|
-
* @param {*} path
|
|
41
|
-
* @param {FunctionHandler[]} handler
|
|
42
|
-
*/
|
|
43
17
|
add(method, path, ...handlers) {
|
|
44
18
|
if (typeof path === "function") {
|
|
45
19
|
handlers = [path, ...handlers];
|
|
@@ -120,6 +94,11 @@ class Router {
|
|
|
120
94
|
return this;
|
|
121
95
|
}
|
|
122
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @param {'GET'|'QUERY'|'HEAD'|'OPTIONS'|'TRACE'|'PUT'|'DELETE'|'POST'|'PATCH'|'CONNECT'} method
|
|
99
|
+
* @param {String} path
|
|
100
|
+
* @returns {{params: {};handlers: any[];errorHandlers: any[];}}
|
|
101
|
+
*/
|
|
123
102
|
find(method, path) {
|
|
124
103
|
const key = `${method} ${path}`;
|
|
125
104
|
|
|
@@ -171,9 +150,15 @@ class Router {
|
|
|
171
150
|
}
|
|
172
151
|
}
|
|
173
152
|
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
153
|
+
const result = {
|
|
154
|
+
params,
|
|
155
|
+
handlers: Object.values(handlers),
|
|
156
|
+
errorHandlers: Object.values(errorHandlers),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
this.cache.set(key, result);
|
|
160
|
+
|
|
161
|
+
return result;
|
|
177
162
|
}
|
|
178
163
|
}
|
|
179
|
-
|
|
164
|
+
exports.Router = Router;
|
package/src/util.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const URL_REGEX = /^([^?#]*)(\?([^#]*))?(#.*)?$/;
|
|
2
|
+
const QUERY_STRING_REGEX = /([^=&]*)?=([^&]*)?/g;
|
|
3
|
+
|
|
4
|
+
function parseQueryString(queryString) {
|
|
5
|
+
const result = {};
|
|
6
|
+
|
|
7
|
+
let match;
|
|
8
|
+
while ((match = QUERY_STRING_REGEX.exec(queryString)) !== null) {
|
|
9
|
+
const [, name = "", raw = ""] = match;
|
|
10
|
+
|
|
11
|
+
const value = decodeURIComponent(raw);
|
|
12
|
+
|
|
13
|
+
if (result[name]) {
|
|
14
|
+
if (Array.isArray(result[name])) result[name].push(value);
|
|
15
|
+
else result[name] = [result[name], value];
|
|
16
|
+
} else result[name] = value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
exports.parseQueryString = parseQueryString;
|
|
22
|
+
|
|
23
|
+
function parseUrl(url) {
|
|
24
|
+
const [, path = "/", , queryString = ""] = URL_REGEX.exec(url);
|
|
25
|
+
return { path, queryString };
|
|
26
|
+
}
|
|
27
|
+
exports.parseUrl = parseUrl;
|