@ndiinginc/router 4.11.1 → 4.12.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 +1 -1
- package/src/app.js +16 -4
- package/src/middleware/cors.js +14 -7
- package/src/middleware/json.js +15 -1
- package/src/node.js +22 -3
- package/src/request.js +0 -3
- package/src/response.js +10 -0
- package/src/router.js +94 -6
- package/src/util.js +36 -31
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
|
-
const { EMPTY_PARAMS } = require("./util");
|
|
3
2
|
const { Router } = require("./router");
|
|
4
3
|
const { Request } = require("./request");
|
|
5
4
|
const { Response } = require("./response");
|
|
@@ -11,6 +10,11 @@ const SERVER_OPTIONS = {
|
|
|
11
10
|
};
|
|
12
11
|
|
|
13
12
|
class App extends Router {
|
|
13
|
+
/**
|
|
14
|
+
* @param {Request} req
|
|
15
|
+
* @param {Response} res
|
|
16
|
+
* @param {Promise} handlerPromise
|
|
17
|
+
*/
|
|
14
18
|
async withTimeout(req, res, handlerPromise) {
|
|
15
19
|
let handleAbort;
|
|
16
20
|
const timeoutPromise = new Promise((_, reject) => {
|
|
@@ -35,6 +39,10 @@ class App extends Router {
|
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {Response} res
|
|
44
|
+
* @param {Error} err
|
|
45
|
+
*/
|
|
38
46
|
fallback(res, err) {
|
|
39
47
|
if (!res.writableEnded) {
|
|
40
48
|
if (!err) {
|
|
@@ -53,12 +61,12 @@ class App extends Router {
|
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
/**
|
|
56
|
-
* @param {
|
|
57
|
-
* @param {
|
|
64
|
+
* @param {Request} req
|
|
65
|
+
* @param {Response} res
|
|
58
66
|
*/
|
|
59
67
|
async handle(req, res) {
|
|
60
68
|
const { params, paramsLength, handlers, errorHandlers } = this.find(req.method, req.path);
|
|
61
|
-
req.params = paramsLength ? Object.assign(Object.create(null), params) :
|
|
69
|
+
req.params = paramsLength ? Object.assign(Object.create(null), params) : Object.create(null);
|
|
62
70
|
let err;
|
|
63
71
|
try {
|
|
64
72
|
const handlerPromise = async () => {
|
|
@@ -86,6 +94,10 @@ class App extends Router {
|
|
|
86
94
|
this.fallback(res, err);
|
|
87
95
|
}
|
|
88
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @param {import('net').ListenOptions} args
|
|
99
|
+
* @returns {import('http').Server}
|
|
100
|
+
*/
|
|
89
101
|
listen(...args) {
|
|
90
102
|
const requestListener = (req, res) => this.handle(req, res);
|
|
91
103
|
const server = http.createServer(SERVER_OPTIONS, requestListener);
|
package/src/middleware/cors.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef
|
|
3
|
-
* @property {
|
|
4
|
-
* @property {
|
|
5
|
-
* @property {
|
|
6
|
-
* @property {Number}
|
|
7
|
-
* @property {
|
|
2
|
+
* @typedef {Object} Options
|
|
3
|
+
* @property {string|array} origin
|
|
4
|
+
* @property {boolean} credentials
|
|
5
|
+
* @property {string|array} headers
|
|
6
|
+
* @property {Number} maxAge
|
|
7
|
+
* @property {string|array} methods
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @
|
|
11
|
+
* @callback Callback
|
|
12
|
+
* @param {import('../request')} req
|
|
13
|
+
* @param {import('../response')} res
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Options} Options
|
|
18
|
+
* @returns {Callback}
|
|
12
19
|
*/
|
|
13
20
|
function cors({ origin = "*", credentials = false, headers, maxAge = 0, methods } = {}) {
|
|
14
21
|
if (Array.isArray(headers)) {
|
package/src/middleware/json.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {Object} Options
|
|
4
|
+
* @property {Number} [limit=1.024e+5]
|
|
5
|
+
*/
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @callback Callback
|
|
9
|
+
* @param {import('../request')} req
|
|
10
|
+
* @param {import('../response')} res
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Options} Options
|
|
15
|
+
* @returns {Callback}
|
|
16
|
+
*/
|
|
17
|
+
function json({ limit = 1.024e5 } = {}) {
|
|
4
18
|
const reviver = (key, value) => {
|
|
5
19
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
6
20
|
return undefined;
|
package/src/node.js
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
class Node {
|
|
2
|
+
/**
|
|
3
|
+
* @param {String} segment
|
|
4
|
+
*/
|
|
2
5
|
constructor(segment = "") {
|
|
3
6
|
this.segment = segment;
|
|
4
7
|
if (segment.startsWith(":")) {
|
|
8
|
+
/**@type {String}*/
|
|
5
9
|
this.name = segment.slice(1);
|
|
6
10
|
}
|
|
7
|
-
|
|
11
|
+
|
|
12
|
+
/**@type {Map<string,Node>}*/
|
|
8
13
|
this.children = new Map();
|
|
9
|
-
|
|
14
|
+
|
|
15
|
+
/**@type {Map<string,Node>}*/
|
|
10
16
|
this.params = new Map();
|
|
11
|
-
|
|
17
|
+
|
|
18
|
+
/**@type {Node}*/
|
|
12
19
|
this.wildcard = null;
|
|
20
|
+
/**@type {Map<string,Function[]>}*/
|
|
13
21
|
this.handlers = new Map();
|
|
14
22
|
}
|
|
15
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @param {String} segment
|
|
26
|
+
* @returns {Node}
|
|
27
|
+
*/
|
|
16
28
|
add(segment) {
|
|
17
29
|
if (segment === "*") {
|
|
18
30
|
if (!this.wildcard) this.wildcard = new Node("*");
|
|
@@ -28,6 +40,10 @@ class Node {
|
|
|
28
40
|
});
|
|
29
41
|
}
|
|
30
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @param {Node} node
|
|
45
|
+
* @param {Number} offset
|
|
46
|
+
*/
|
|
31
47
|
merge(node, offset) {
|
|
32
48
|
for (const [method, entries] of node.handlers) {
|
|
33
49
|
for (const entry of entries) {
|
|
@@ -48,6 +64,9 @@ class Node {
|
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @returns {Number}
|
|
69
|
+
*/
|
|
51
70
|
getMaxSeq() {
|
|
52
71
|
let max = -1;
|
|
53
72
|
for (const [, entries] of this.handlers) {
|
package/src/request.js
CHANGED
package/src/response.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
2
|
|
|
3
3
|
class Response extends http.ServerResponse {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Number} code
|
|
6
|
+
* @returns {Response}
|
|
7
|
+
*/
|
|
4
8
|
status(code) {
|
|
5
9
|
this.statusCode = code;
|
|
6
10
|
return this;
|
|
7
11
|
}
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {String} chunk
|
|
15
|
+
*/
|
|
9
16
|
send(chunk) {
|
|
10
17
|
if (this.writableEnded) return;
|
|
11
18
|
this.end(chunk, null, null);
|
|
12
19
|
}
|
|
13
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @param {Object} value
|
|
23
|
+
*/
|
|
14
24
|
json(value) {
|
|
15
25
|
if (!this.headersSent) {
|
|
16
26
|
this.setHeader("Content-Type", "application/json; charset=utf-8");
|
package/src/router.js
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
const { decodeParam } = require("./util");
|
|
2
2
|
const { Node } = require("./node");
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} Options
|
|
6
|
+
* @property {Number} requestTimeout
|
|
7
|
+
*/
|
|
8
|
+
|
|
4
9
|
class Router {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Options} options
|
|
12
|
+
*/
|
|
5
13
|
constructor(options = {}) {
|
|
6
14
|
this.requestTimeout = options.requestTimeout ?? 0;
|
|
7
15
|
this.root = new Node();
|
|
8
16
|
this.seq = 0;
|
|
9
17
|
}
|
|
10
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @param {String} path
|
|
21
|
+
* @returns {String[]}
|
|
22
|
+
*/
|
|
11
23
|
split(path) {
|
|
12
24
|
return path.split("/").filter(Boolean);
|
|
13
25
|
}
|
|
14
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @param {String} method
|
|
29
|
+
* @param {String|Function} path
|
|
30
|
+
* @param {Function[]} handlers
|
|
31
|
+
*/
|
|
15
32
|
add(method, path, ...handlers) {
|
|
16
33
|
if (typeof path === "function") {
|
|
17
34
|
handlers = [path, ...handlers];
|
|
@@ -37,69 +54,125 @@ class Router {
|
|
|
37
54
|
}
|
|
38
55
|
}
|
|
39
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @param {String|Function} path
|
|
59
|
+
* @param {Function[]} handlers
|
|
60
|
+
* @returns {this}
|
|
61
|
+
*/
|
|
40
62
|
use(path, ...handlers) {
|
|
41
63
|
this.add("ALL", path, ...handlers);
|
|
42
64
|
return this;
|
|
43
65
|
}
|
|
44
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @param {String|Function} path
|
|
69
|
+
* @param {Function[]} handlers
|
|
70
|
+
* @returns {this}
|
|
71
|
+
*/
|
|
45
72
|
get(path, ...handlers) {
|
|
46
73
|
this.add("GET", path, ...handlers);
|
|
47
74
|
return this;
|
|
48
75
|
}
|
|
49
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @param {String|Function} path
|
|
79
|
+
* @param {Function[]} handlers
|
|
80
|
+
* @returns {this}
|
|
81
|
+
*/
|
|
50
82
|
query(path, ...handlers) {
|
|
51
83
|
this.add("QUERY", path, ...handlers);
|
|
52
84
|
return this;
|
|
53
85
|
}
|
|
54
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @param {String|Function} path
|
|
89
|
+
* @param {Function[]} handlers
|
|
90
|
+
* @returns {this}
|
|
91
|
+
*/
|
|
55
92
|
head(path, ...handlers) {
|
|
56
93
|
this.add("HEAD", path, ...handlers);
|
|
57
94
|
return this;
|
|
58
95
|
}
|
|
59
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @param {String|Function} path
|
|
99
|
+
* @param {Function[]} handlers
|
|
100
|
+
* @returns {this}
|
|
101
|
+
*/
|
|
60
102
|
options(path, ...handlers) {
|
|
61
103
|
this.add("OPTIONS", path, ...handlers);
|
|
62
104
|
return this;
|
|
63
105
|
}
|
|
64
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @param {String|Function} path
|
|
109
|
+
* @param {Function[]} handlers
|
|
110
|
+
* @returns {this}
|
|
111
|
+
*/
|
|
65
112
|
trace(path, ...handlers) {
|
|
66
113
|
this.add("TRACE", path, ...handlers);
|
|
67
114
|
return this;
|
|
68
115
|
}
|
|
69
116
|
|
|
117
|
+
/**
|
|
118
|
+
* @param {String|Function} path
|
|
119
|
+
* @param {Function[]} handlers
|
|
120
|
+
* @returns {this}
|
|
121
|
+
*/
|
|
70
122
|
put(path, ...handlers) {
|
|
71
123
|
this.add("PUT", path, ...handlers);
|
|
72
124
|
return this;
|
|
73
125
|
}
|
|
74
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @param {String|Function} path
|
|
129
|
+
* @param {Function[]} handlers
|
|
130
|
+
* @returns {this}
|
|
131
|
+
*/
|
|
75
132
|
delete(path, ...handlers) {
|
|
76
133
|
this.add("DELETE", path, ...handlers);
|
|
77
134
|
return this;
|
|
78
135
|
}
|
|
79
136
|
|
|
137
|
+
/**
|
|
138
|
+
* @param {String|Function} path
|
|
139
|
+
* @param {Function[]} handlers
|
|
140
|
+
* @returns {this}
|
|
141
|
+
*/
|
|
80
142
|
post(path, ...handlers) {
|
|
81
143
|
this.add("POST", path, ...handlers);
|
|
82
144
|
return this;
|
|
83
145
|
}
|
|
84
146
|
|
|
147
|
+
/**
|
|
148
|
+
* @param {String|Function} path
|
|
149
|
+
* @param {Function[]} handlers
|
|
150
|
+
* @returns {this}
|
|
151
|
+
*/
|
|
85
152
|
patch(path, ...handlers) {
|
|
86
153
|
this.add("PATCH", path, ...handlers);
|
|
87
154
|
return this;
|
|
88
155
|
}
|
|
89
156
|
|
|
157
|
+
/**
|
|
158
|
+
* @param {String|Function} path
|
|
159
|
+
* @param {Function[]} handlers
|
|
160
|
+
* @returns {this}
|
|
161
|
+
*/
|
|
90
162
|
connect(path, ...handlers) {
|
|
91
163
|
this.add("CONNECT", path, ...handlers);
|
|
92
164
|
return this;
|
|
93
165
|
}
|
|
94
166
|
|
|
95
167
|
/**
|
|
168
|
+
* @param {String} method
|
|
169
|
+
* @param {String[]} segments
|
|
96
170
|
* @param {Node} node
|
|
97
|
-
* @param {
|
|
98
|
-
* @param {
|
|
99
|
-
* @param {
|
|
100
|
-
* @param {
|
|
101
|
-
* @
|
|
102
|
-
* @returns {{ nodes: Node[], params: Record<string, string>, matched: boolean }}
|
|
171
|
+
* @param {Array} path
|
|
172
|
+
* @param {Object} params
|
|
173
|
+
* @param {Object} state
|
|
174
|
+
* @param {Number} index
|
|
175
|
+
* @returns {{path:Array,params:Object,matched:Boolean}}
|
|
103
176
|
*/
|
|
104
177
|
search(method, segments, node, path = [], params = {}, state = { path: [], params: {} }, index = 0) {
|
|
105
178
|
path.push(node);
|
|
@@ -163,6 +236,11 @@ class Router {
|
|
|
163
236
|
return { path: state.path, params: state.params, matched: false };
|
|
164
237
|
}
|
|
165
238
|
|
|
239
|
+
/**
|
|
240
|
+
* @param {Node} r
|
|
241
|
+
* @param {String} method
|
|
242
|
+
* @param {Object} handlers
|
|
243
|
+
*/
|
|
166
244
|
collectHandler(r, method, handlers) {
|
|
167
245
|
if (r.handlers.has(method)) {
|
|
168
246
|
for (const entry of r.handlers.get(method)) {
|
|
@@ -171,11 +249,21 @@ class Router {
|
|
|
171
249
|
}
|
|
172
250
|
}
|
|
173
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @param {Node} r
|
|
254
|
+
* @param {Object} handlers
|
|
255
|
+
* @param {Object} errorHandlers
|
|
256
|
+
*/
|
|
174
257
|
collectMiddleware(r, handlers, errorHandlers) {
|
|
175
258
|
this.collectHandler(r, "ALL", handlers);
|
|
176
259
|
this.collectHandler(r, "CATCH", errorHandlers);
|
|
177
260
|
}
|
|
178
261
|
|
|
262
|
+
/**
|
|
263
|
+
* @param {String} method
|
|
264
|
+
* @param {String} path
|
|
265
|
+
* @returns {{matched: boolean;params: Object;paramsLength: number;handlers: any[];errorHandlers: any[];}}
|
|
266
|
+
*/
|
|
179
267
|
find(method, path) {
|
|
180
268
|
const res = this.search(
|
|
181
269
|
//
|
package/src/util.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @param {String} raw
|
|
3
|
+
* @returns {String}
|
|
4
|
+
*/
|
|
4
5
|
function decodeParam(raw) {
|
|
5
6
|
try {
|
|
6
7
|
return decodeURIComponent(raw);
|
|
@@ -10,40 +11,44 @@ function decodeParam(raw) {
|
|
|
10
11
|
}
|
|
11
12
|
exports.decodeParam = decodeParam;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} catch (error) {
|
|
18
|
-
return normalized;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
14
|
+
/**
|
|
15
|
+
* @param {String} queryString
|
|
16
|
+
* @returns {Object}
|
|
17
|
+
*/
|
|
22
18
|
function parseQueryString(queryString) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (Object.hasOwn(result, name)) {
|
|
32
|
-
if (Array.isArray(result[name])) result[name].push(value);
|
|
33
|
-
else result[name] = [result[name], value];
|
|
34
|
-
} else result[name] = value;
|
|
19
|
+
const query = Object.create(null);
|
|
20
|
+
if (queryString) {
|
|
21
|
+
for (const [name, value] of new URLSearchParams(queryString)) {
|
|
22
|
+
if (Object.hasOwn(query, name)) {
|
|
23
|
+
if (Array.isArray(query[name])) query[name].push(value);
|
|
24
|
+
else query[name] = [query[name], value];
|
|
25
|
+
} else query[name] = value;
|
|
26
|
+
}
|
|
35
27
|
}
|
|
36
|
-
|
|
37
|
-
return result;
|
|
28
|
+
return query;
|
|
38
29
|
}
|
|
39
30
|
exports.parseQueryString = parseQueryString;
|
|
40
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @param {String} url
|
|
34
|
+
* @returns {{path: any;queryString: any;}}
|
|
35
|
+
*/
|
|
41
36
|
function parseUrl(url) {
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
let path = url;
|
|
38
|
+
let queryString = "";
|
|
39
|
+
let hash = "";
|
|
40
|
+
if (url.includes("?") || url.includes("#")) {
|
|
41
|
+
const hashIdx = url.indexOf("#");
|
|
42
|
+
if (hashIdx !== -1) {
|
|
43
|
+
hash = url.slice(hashIdx + 1);
|
|
44
|
+
path = url.slice(0, hashIdx);
|
|
45
|
+
}
|
|
46
|
+
const queryIdx = path.indexOf("?");
|
|
47
|
+
if (queryIdx !== -1) {
|
|
48
|
+
queryString = path.slice(queryIdx + 1);
|
|
49
|
+
path = path.slice(0, queryIdx);
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
|
-
|
|
46
|
-
const [, path = "/", , queryString = ""] = regex.exec(url);
|
|
47
|
-
return { path, queryString };
|
|
52
|
+
return { path, queryString, hash };
|
|
48
53
|
}
|
|
49
54
|
exports.parseUrl = parseUrl;
|