@ndiinginc/router 4.14.1 → 4.16.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndiinginc/router",
3
- "version": "4.14.1",
3
+ "version": "4.16.0",
4
4
  "description": "",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
package/src/app.js CHANGED
@@ -2,12 +2,9 @@ const http = require("http");
2
2
  const { Router } = require("./router");
3
3
  const { Request } = require("./request");
4
4
  const { Response } = require("./response");
5
-
6
- /**@type {import('http').ServerOptions}*/
7
- const SERVER_OPTIONS = {
8
- IncomingMessage: Request,
9
- ServerResponse: Response,
10
- };
5
+ const { json } = require("./middleware/json");
6
+ const { cors } = require("./middleware/cors");
7
+ const { urlencoded } = require("./middleware/urlencoded");
11
8
 
12
9
  class App extends Router {
13
10
  /**
@@ -28,7 +25,6 @@ class App extends Router {
28
25
  const promise = handlerPromise().catch((error) => {
29
26
  if (!req.controller.signal.aborted) {
30
27
  req.controller.abort(error);
31
- } else {
32
28
  }
33
29
  });
34
30
  try {
@@ -40,10 +36,10 @@ class App extends Router {
40
36
  }
41
37
 
42
38
  /**
43
- * @param {Response} res
44
39
  * @param {Error} err
40
+ * @param {Response} res
45
41
  */
46
- fallback(res, err) {
42
+ fallback(err, res) {
47
43
  if (!res.writableEnded) {
48
44
  if (!err) {
49
45
  res.statusCode = 404;
@@ -65,12 +61,12 @@ class App extends Router {
65
61
  * @param {Response} res
66
62
  */
67
63
  async handle(req, res) {
68
- const { params, paramsLength, handlers, errorHandlers } = this.find(req.method, req.path);
69
- req.params = paramsLength ? Object.assign(Object.create(null), params) : Object.create(null);
64
+ const result = this.find(req.method, req.path);
65
+ req.params = result.params;
70
66
  let err;
71
67
  try {
72
68
  const handlerPromise = async () => {
73
- for (const handler of handlers) {
69
+ for (const handler of result.handlers) {
74
70
  if (res.writableEnded) break;
75
71
  await handler(req, res);
76
72
  }
@@ -83,7 +79,7 @@ class App extends Router {
83
79
  } catch (error) {
84
80
  err = error;
85
81
  try {
86
- for (const handler of errorHandlers) {
82
+ for (const handler of result.errorHandlers) {
87
83
  if (res.writableEnded) break;
88
84
  await handler(err, req, res);
89
85
  }
@@ -91,7 +87,7 @@ class App extends Router {
91
87
  err = new Error(error.message, { cause: err });
92
88
  }
93
89
  }
94
- this.fallback(res, err);
90
+ this.fallback(err, res);
95
91
  }
96
92
 
97
93
  /**
@@ -99,13 +95,21 @@ class App extends Router {
99
95
  * @returns {import('http').Server}
100
96
  */
101
97
  listen(...args) {
102
- const requestListener = (req, res) => this.handle(req, res);
103
- const server = http.createServer(SERVER_OPTIONS, requestListener);
98
+ const server = http.createServer(
99
+ {
100
+ IncomingMessage: Request,
101
+ ServerResponse: Response,
102
+ },
103
+ (req, res) => this.handle(req, res),
104
+ );
104
105
  server.listen(...args);
105
106
  return server;
106
107
  }
107
108
  }
108
109
 
109
- App.SERVER_OPTIONS = SERVER_OPTIONS;
110
+ App.Router = Router;
111
+ App.json = json;
112
+ App.cors = cors;
113
+ App.urlencoded = urlencoded;
110
114
 
111
115
  exports.App = App;
package/src/index.js CHANGED
@@ -1,13 +1,4 @@
1
1
  require("./polyfill");
2
2
  const { App } = require("./app");
3
- const { Router } = require("./router");
4
- const { json } = require("./middleware/json");
5
- const { cors } = require("./middleware/cors");
6
- const { urlencoded } = require("./middleware/urlencoded");
7
-
8
- App.Router = Router;
9
- App.json = json;
10
- App.cors = cors;
11
- App.urlencoded = urlencoded;
12
3
 
13
4
  module.exports = App;
@@ -21,6 +21,7 @@ function cors({ origin = "*", credentials = false, headers, maxAge = 0, methods
21
21
  if (Array.isArray(headers)) {
22
22
  headers = headers.map((h) => h.toLowerCase());
23
23
  }
24
+
24
25
  if (Array.isArray(methods)) {
25
26
  methods = methods.map((m) => m.toUpperCase());
26
27
  }
package/src/node.js CHANGED
@@ -1,20 +1,17 @@
1
1
  class Node {
2
2
  /**
3
- * @param {String} segment
3
+ * @param {String} prefix
4
4
  */
5
- constructor(segment = "") {
6
- this.segment = segment;
7
- if (segment.startsWith(":")) {
5
+ constructor(prefix = "") {
6
+ this.prefix = prefix;
7
+ if (prefix.startsWith(":")) {
8
8
  /**@type {String}*/
9
- this.name = segment.slice(1);
9
+ this.name = prefix.slice(1);
10
10
  }
11
-
12
11
  /**@type {Map<string,Node>}*/
13
12
  this.children = new Map();
14
-
15
13
  /**@type {Map<string,Node>}*/
16
14
  this.params = new Map();
17
-
18
15
  /**@type {Node}*/
19
16
  this.wildcard = null;
20
17
  /**@type {Map<string,Function[]>}*/
@@ -22,29 +19,25 @@ class Node {
22
19
  }
23
20
 
24
21
  /**
25
- * @param {String} segment
22
+ * @param {String} prefix
26
23
  * @returns {Node}
27
24
  */
28
- add(segment) {
29
- if (segment === "*") {
25
+ add(prefix) {
26
+ if (prefix === "*") {
30
27
  if (!this.wildcard) this.wildcard = new Node("*");
31
28
  return this.wildcard;
32
29
  }
33
- if (segment.startsWith(":")) {
34
- return this.params.getOrInsertComputed(segment, (segment) => {
35
- return new Node(segment);
36
- });
30
+ if (prefix.startsWith(":")) {
31
+ return this.params.getOrInsertComputed(prefix, (prefix) => new Node(prefix));
37
32
  }
38
- return this.children.getOrInsertComputed(segment, (segment) => {
39
- return new Node(segment);
40
- });
33
+ return this.children.getOrInsertComputed(prefix, (prefix) => new Node(prefix));
41
34
  }
42
35
 
43
36
  /**
44
- * @param {String} segment
37
+ * @param {String} prefix
45
38
  */
46
- remove(segment) {
47
- this.children.delete(segment);
39
+ remove(prefix) {
40
+ this.children.delete(prefix);
48
41
  }
49
42
 
50
43
  /**
@@ -63,11 +56,11 @@ class Node {
63
56
  if (node.wildcard) {
64
57
  this.add("*").merge(node.wildcard, offset);
65
58
  }
66
- for (const [segment, child] of node.params) {
67
- this.add(segment).merge(child, offset);
59
+ for (const [prefix, child] of node.params) {
60
+ this.add(prefix).merge(child, offset);
68
61
  }
69
- for (const [segment, child] of node.children) {
70
- this.add(segment).merge(child, offset);
62
+ for (const [prefix, child] of node.children) {
63
+ this.add(prefix).merge(child, offset);
71
64
  }
72
65
  }
73
66
 
package/src/response.js CHANGED
@@ -1,31 +1,41 @@
1
1
  const http = require("http");
2
+ const fs = require("fs");
3
+ const path = require("path");
2
4
 
3
5
  class Response extends http.ServerResponse {
4
- /**
5
- * @param {Number} code
6
- * @returns {Response}
7
- */
8
6
  status(code) {
9
7
  this.statusCode = code;
10
8
  return this;
11
9
  }
12
-
13
- /**
14
- * @param {String} chunk
15
- */
16
10
  send(chunk) {
17
- if (this.writableEnded) return;
18
- this.end(chunk, null, null);
11
+ this.setHeader("Content-Type", "text/plain; charset=utf-8");
12
+ this.end(chunk);
19
13
  }
20
14
 
21
- /**
22
- * @param {Object} value
23
- */
24
15
  json(value) {
25
- if (!this.headersSent) {
26
- this.setHeader("Content-Type", "application/json; charset=utf-8");
27
- }
28
- this.send(JSON.stringify(value));
16
+ this.setHeader("Content-Type", "application/json; charset=utf-8");
17
+ this.end(JSON.stringify(value));
18
+ }
19
+
20
+ download(filepath, filename) {
21
+ fs.stat(filepath, (err, stats) => {
22
+ if (err || !stats.isFile()) {
23
+ this.statusCode = 404;
24
+ return this.end();
25
+ }
26
+ filename=filename||path.basename(filepath)
27
+ this.setHeader("Content-Type", "application/octet-stream");
28
+ this.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
29
+ this.setHeader("Content-Length", stats.size);
30
+ const stream = fs.createReadStream(filepath);
31
+ stream.on("error", (err) => {
32
+ if (!this.headersSent) {
33
+ this.statusCode = 500;
34
+ this.end();
35
+ }
36
+ });
37
+ stream.pipe(this);
38
+ });
29
39
  }
30
40
  }
31
41
  exports.Response = Response;
package/src/router.js CHANGED
@@ -1,6 +1,12 @@
1
1
  const { decodeParam } = require("./util");
2
2
  const { Node } = require("./node");
3
3
 
4
+ /**
5
+ * @callback Callback
6
+ * @param {import('./request').Request} req
7
+ * @param {import('./response').Response} res
8
+ */
9
+
4
10
  /**
5
11
  * @typedef {Object} Options
6
12
  * @property {Number} [requestTimeout=0]
@@ -12,7 +18,6 @@ class Router {
12
18
  */
13
19
  constructor(options = {}) {
14
20
  this.requestTimeout = options.requestTimeout ?? 0;
15
-
16
21
  this.root = new Node();
17
22
  this.seq = 0;
18
23
  }
@@ -36,45 +41,42 @@ class Router {
36
41
  * @returns
37
42
  */
38
43
  walk(path, node) {
39
- for (const [segment, child] of node.children) {
40
- const lcp = this.lcp(segment, path);
44
+ for (const [prefix, child] of node.children) {
45
+ const lcp = this.lcp(prefix, path);
41
46
  if (lcp === 0) continue;
42
- if (lcp === segment.length) {
47
+ if (lcp === prefix.length) {
43
48
  if (lcp === path.length) return child;
44
49
  return this.walk(path.slice(lcp), child);
45
50
  }
46
- const newNode = new Node(segment.slice(0, lcp));
47
- newNode.add(segment.slice(lcp)).merge(child);
51
+ const newNode = new Node(prefix.slice(0, lcp));
52
+ newNode.add(prefix.slice(lcp)).merge(child);
48
53
  const rest = path.slice(lcp);
49
54
  if (rest.length) newNode.add(rest);
50
- node.add(newNode.segment).merge(newNode);
51
- node.remove(segment);
55
+ node.add(newNode.prefix).merge(newNode);
56
+ node.remove(prefix);
52
57
  }
53
58
  return node.add(path);
54
59
  }
55
60
 
56
61
  /**
57
62
  * @param {String} method
58
- * @param {String|Function} path
59
- * @param {Function[]} handlers
63
+ * @param {String|Callback} path
64
+ * @param {Callback[]} handlers
60
65
  */
61
66
  add(method, path, ...handlers) {
62
67
  if (typeof path === "function") {
63
68
  handlers = [path, ...handlers];
64
69
  path = null;
65
70
  }
66
-
67
71
  let node = this.root;
68
- let params = [];
69
72
  if (path) {
70
73
  let i = 0;
71
74
  while (i < path.length) {
72
75
  if (path[i] === ":") {
73
76
  let j = i + 1;
74
77
  while (j < path.length && path[j] !== "/") j++;
75
- let segment = path.slice(i, j);
76
- node = node.add(segment);
77
- params.push(segment.slice(1));
78
+ let prefix = path.slice(i, j);
79
+ node = node.add(prefix);
78
80
  i = j;
79
81
  } else if (path[i] === "*") {
80
82
  let j = i + 1;
@@ -101,8 +103,8 @@ class Router {
101
103
  }
102
104
 
103
105
  /**
104
- * @param {String|Function} path
105
- * @param {Function[]} handlers
106
+ * @param {String|Callback} path
107
+ * @param {Callback[]} handlers
106
108
  * @returns {this}
107
109
  */
108
110
  use(path, ...handlers) {
@@ -111,8 +113,8 @@ class Router {
111
113
  }
112
114
 
113
115
  /**
114
- * @param {String|Function} path
115
- * @param {Function[]} handlers
116
+ * @param {String|Callback} path
117
+ * @param {Callback[]} handlers
116
118
  * @returns {this}
117
119
  */
118
120
  get(path, ...handlers) {
@@ -121,8 +123,8 @@ class Router {
121
123
  }
122
124
 
123
125
  /**
124
- * @param {String|Function} path
125
- * @param {Function[]} handlers
126
+ * @param {String|Callback} path
127
+ * @param {Callback[]} handlers
126
128
  * @returns {this}
127
129
  */
128
130
  query(path, ...handlers) {
@@ -131,8 +133,8 @@ class Router {
131
133
  }
132
134
 
133
135
  /**
134
- * @param {String|Function} path
135
- * @param {Function[]} handlers
136
+ * @param {String|Callback} path
137
+ * @param {Callback[]} handlers
136
138
  * @returns {this}
137
139
  */
138
140
  head(path, ...handlers) {
@@ -141,8 +143,8 @@ class Router {
141
143
  }
142
144
 
143
145
  /**
144
- * @param {String|Function} path
145
- * @param {Function[]} handlers
146
+ * @param {String|Callback} path
147
+ * @param {Callback[]} handlers
146
148
  * @returns {this}
147
149
  */
148
150
  options(path, ...handlers) {
@@ -151,8 +153,8 @@ class Router {
151
153
  }
152
154
 
153
155
  /**
154
- * @param {String|Function} path
155
- * @param {Function[]} handlers
156
+ * @param {String|Callback} path
157
+ * @param {Callback[]} handlers
156
158
  * @returns {this}
157
159
  */
158
160
  trace(path, ...handlers) {
@@ -161,8 +163,8 @@ class Router {
161
163
  }
162
164
 
163
165
  /**
164
- * @param {String|Function} path
165
- * @param {Function[]} handlers
166
+ * @param {String|Callback} path
167
+ * @param {Callback[]} handlers
166
168
  * @returns {this}
167
169
  */
168
170
  put(path, ...handlers) {
@@ -171,8 +173,8 @@ class Router {
171
173
  }
172
174
 
173
175
  /**
174
- * @param {String|Function} path
175
- * @param {Function[]} handlers
176
+ * @param {String|Callback} path
177
+ * @param {Callback[]} handlers
176
178
  * @returns {this}
177
179
  */
178
180
  delete(path, ...handlers) {
@@ -181,8 +183,8 @@ class Router {
181
183
  }
182
184
 
183
185
  /**
184
- * @param {String|Function} path
185
- * @param {Function[]} handlers
186
+ * @param {String|Callback} path
187
+ * @param {Callback[]} handlers
186
188
  * @returns {this}
187
189
  */
188
190
  post(path, ...handlers) {
@@ -191,8 +193,8 @@ class Router {
191
193
  }
192
194
 
193
195
  /**
194
- * @param {String|Function} path
195
- * @param {Function[]} handlers
196
+ * @param {String|Callback} path
197
+ * @param {Callback[]} handlers
196
198
  * @returns {this}
197
199
  */
198
200
  patch(path, ...handlers) {
@@ -201,8 +203,8 @@ class Router {
201
203
  }
202
204
 
203
205
  /**
204
- * @param {String|Function} path
205
- * @param {Function[]} handlers
206
+ * @param {String|Callback} path
207
+ * @param {Callback[]} handlers
206
208
  * @returns {this}
207
209
  */
208
210
  connect(path, ...handlers) {
@@ -226,95 +228,102 @@ class Router {
226
228
  return { nodes, params, matched: true };
227
229
  }
228
230
  if (nodes.length > state.nodes.length) {
229
- state.nodes=nodes.slice()
230
- params={...params}
231
+ state.nodes = nodes.slice();
232
+ state.params = { ...params };
231
233
  }
232
234
  nodes.pop();
233
235
  return { nodes: state.nodes, params: state.params, matched: false };
234
236
  }
235
- for (const [segment, child] of node.children) {
236
- if (path.startsWith(segment)) {
237
- const res = this.search(method, path.slice(segment.length), child, nodes, params, state);
238
- if (res.matched) return res;
237
+ if (node.children.size) {
238
+ for (const [prefix, child] of node.children) {
239
+ if (path.startsWith(prefix)) {
240
+ const res = this.search(method, path.slice(prefix.length), child, nodes, params, state);
241
+ if (res.matched) return res;
242
+ }
239
243
  }
240
244
  }
241
- for (const [segment, child] of node.params) {
242
- const slashIdx = path.indexOf("/");
243
- const value = slashIdx === -1 ? path : path.slice(0, slashIdx);
244
- const rest = slashIdx === -1 ? "" : path.slice(slashIdx);
245
- if (value.length > 0) {
246
- const next = { ...params, [child.name]: decodeParam(value) };
247
- const result = this.search(method, rest, child, nodes, next, state);
248
- if (result.matched) return result;
245
+ if (node.params.size) {
246
+ for (const [, child] of node.params) {
247
+ const index = path.indexOf("/");
248
+ const value = index === -1 ? path : path.slice(0, index);
249
+ if (value.length) {
250
+ const rest = index === -1 ? "" : path.slice(index);
251
+ const next = { ...params, [child.name]: value };
252
+ const result = this.search(method, rest, child, nodes, next, state);
253
+ if (result.matched) return result;
254
+ }
249
255
  }
250
256
  }
251
257
  if (node.wildcard && (node.wildcard.handlers.get(method) || node.wildcard.handlers.get("ALL"))) {
252
258
  nodes.push(node.wildcard);
253
- return {
254
- nodes,
255
- params: { ...params, wildcard: path },
256
- matched: true,
257
- };
259
+ return { nodes, params: { ...params, wildcard: path }, matched: true };
258
260
  }
259
261
  if (nodes.length > state.nodes.length) {
260
- state.nodes=nodes.slice()
261
- params={...params}
262
+ state.nodes = nodes.slice();
263
+ state.params = { ...params };
262
264
  }
263
265
  nodes.pop();
264
266
  return { nodes: state.nodes, params: state.params, matched: false };
265
267
  }
266
268
 
267
269
  /**
268
- * @param {Node} r
270
+ * @param {Node} node
269
271
  * @param {String} method
270
272
  * @param {Object} handlers
271
273
  */
272
- collectHandler(r, method, handlers) {
273
- if (r.handlers.has(method)) {
274
- for (const entry of r.handlers.get(method)) {
274
+ collectHandler(node, method, handlers) {
275
+ if (node.handlers.has(method)) {
276
+ for (const entry of node.handlers.get(method)) {
275
277
  handlers[entry.seq] = entry.handler;
276
278
  }
277
279
  }
278
280
  }
279
281
 
280
282
  /**
281
- * @param {Node} r
283
+ * @param {Node} node
282
284
  * @param {Object} handlers
283
285
  * @param {Object} errorHandlers
284
286
  */
285
- collectMiddleware(r, handlers, errorHandlers) {
286
- this.collectHandler(r, "ALL", handlers);
287
- this.collectHandler(r, "CATCH", errorHandlers);
287
+ collectMiddleware(node, handlers, errorHandlers) {
288
+ this.collectHandler(node, "ALL", handlers);
289
+ this.collectHandler(node, "CATCH", errorHandlers);
290
+ }
291
+
292
+ decodeParams(obj) {
293
+ const params = Object.create(null);
294
+ const keys = Object.keys(obj);
295
+ if (keys.length) {
296
+ for (const key of keys) {
297
+ params[key] = decodeParam(obj[key]);
298
+ }
299
+ }
300
+ return params;
288
301
  }
289
302
 
290
303
  /**
291
304
  * @param {String} method
292
305
  * @param {String} path
293
- * @returns {{matched: boolean;params: Object;paramsLength: number;handlers: any[];errorHandlers: any[];}}
306
+ * @returns {{params: Object;handlers: any[];errorHandlers: any[];}}
294
307
  */
295
308
  find(method, path) {
296
309
  let res = this.search(method, path);
297
-
298
310
  if (!res.matched && path.length > 1) {
299
311
  const alt = path.endsWith("/") ? path.slice(0, -1) : path + "/";
300
312
  res = this.search(method, alt);
301
313
  }
302
314
  const handlers = {};
303
315
  const errorHandlers = {};
304
- for (const r of res.nodes) {
305
- this.collectMiddleware(r, handlers, errorHandlers);
306
- }
307
316
  if (res.matched) {
308
317
  this.collectHandler(res.nodes.at(-1), method, handlers);
309
318
  }
319
+ for (const node of res.nodes) {
320
+ this.collectMiddleware(node, handlers, errorHandlers);
321
+ }
310
322
  return {
311
- matched: res.matched,
312
- params: res.params,
313
- paramsLength: Object.keys(res.params).length,
323
+ params: this.decodeParams(res.params),
314
324
  handlers: Object.values(handlers),
315
325
  errorHandlers: Object.values(errorHandlers),
316
326
  };
317
327
  }
318
-
319
328
  }
320
329
  exports.Router = Router;