@ndiinginc/router 4.2.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndiinginc/router",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
package/src/app.js CHANGED
@@ -1,76 +1,76 @@
1
- const http = require("node:http");
2
- const { Router } = require("./router.js");
3
- const { Request } = require("./request.js");
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
- };
12
-
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
-
23
- /**
24
- * @param {import('http').IncomingMessage} req
25
- * @param {import('http').ServerResponse} res
26
- */
27
- async handle(req, res) {
28
- const { params, handlers, errorHandlers } = this.find(req.method, req.path);
29
- req.params = params;
30
-
31
- let err;
32
- try {
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;
42
- } catch (error) {
43
- err = error;
44
-
45
- try {
46
- for (const handler of errorHandlers) {
47
- if (res.writableEnded) break;
48
- await handler(err, req, res);
49
- }
50
- } catch (error) {
51
- err = new Error(error.message, { cause: err });
52
- }
53
- }
54
-
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;
60
- }
61
-
62
- res.json({ error: { message: err.message, stack: err.stack } });
63
- }
64
- }
65
-
66
- listen(...params) {
67
- const requestListener = (req, res) => this.handle(req, res);
68
- const server = http.createServer(SERVER_OPTIONS, requestListener);
69
- server.listen(...params);
70
- return server;
71
- }
72
- }
73
-
74
- App.SERVER_OPTIONS = SERVER_OPTIONS;
75
-
76
- exports.App = App;
1
+ const http = require("node:http");
2
+ const { Router } = require("./router.js");
3
+ const { Request } = require("./request.js");
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
+ };
12
+
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
+
23
+ /**
24
+ * @param {import('http').IncomingMessage} req
25
+ * @param {import('http').ServerResponse} res
26
+ */
27
+ async handle(req, res) {
28
+ const { params, handlers, errorHandlers } = this.find(req.method, req.path);
29
+ req.params = params;
30
+
31
+ let err;
32
+ try {
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;
42
+ } catch (error) {
43
+ err = error;
44
+
45
+ try {
46
+ for (const handler of errorHandlers) {
47
+ if (res.writableEnded) break;
48
+ await handler(err, req, res);
49
+ }
50
+ } catch (error) {
51
+ err = new Error(error.message, { cause: err });
52
+ }
53
+ }
54
+
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;
60
+ }
61
+
62
+ res.json({ error: { message: err.message, stack: err.stack } });
63
+ }
64
+ }
65
+
66
+ listen(...params) {
67
+ const requestListener = (req, res) => this.handle(req, res);
68
+ const server = http.createServer(SERVER_OPTIONS, requestListener);
69
+ server.listen(...params);
70
+ return server;
71
+ }
72
+ }
73
+
74
+ App.SERVER_OPTIONS = SERVER_OPTIONS;
75
+
76
+ exports.App = App;
package/src/cache.js CHANGED
@@ -1,24 +1,24 @@
1
- class Cache {
2
- constructor(size) {
3
- this.size = size;
4
- this.map = new Map();
5
- }
6
-
7
- get(key) {
8
- if (!this.map.has(key)) return undefined;
9
- const value = this.map.get(key);
10
- this.map.delete(key);
11
- this.map.set(key, value);
12
- return value;
13
- }
14
-
15
- set(key, value) {
16
- if (this.map.has(key)) this.map.delete(key);
17
- else if (this.map.size >= this.size) {
18
- this.map.delete(this.map.keys().next().value);
19
- }
20
- this.map.set(key, value);
21
- }
22
- }
23
-
24
- exports.Cache = Cache;
1
+ class Cache {
2
+ constructor(size) {
3
+ this.size = size;
4
+ this.map = new Map();
5
+ }
6
+
7
+ get(key) {
8
+ if (!this.map.has(key)) return undefined;
9
+ const value = this.map.get(key);
10
+ this.map.delete(key);
11
+ this.map.set(key, value);
12
+ return value;
13
+ }
14
+
15
+ set(key, value) {
16
+ if (this.map.has(key)) this.map.delete(key);
17
+ else if (this.map.size >= this.size) {
18
+ this.map.delete(this.map.keys().next().value);
19
+ }
20
+ this.map.set(key, value);
21
+ }
22
+ }
23
+
24
+ exports.Cache = Cache;
package/src/error.js CHANGED
@@ -1,26 +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;
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 CHANGED
@@ -1,9 +1,11 @@
1
- const { inspect } = require("node:util");
2
- const { json } = require("./json.js");
3
- const { App } = require("./app.js");
4
- const { Router } = require("./router.js");
5
-
6
- App.Router=Router
7
- App.json=json
8
-
9
- module.exports=App
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,55 +1,55 @@
1
- const http = require("node:http");
2
-
3
- // middleware
4
- function json({ limit = 100 * 1024 } = {}) {
5
- const reviver = (key, value) => {
6
- if (key === "__proto__" || key === "constructor" || key === "prototype") {
7
- return undefined;
8
- }
9
- return value;
10
- };
11
-
12
- return async (req, res) => {
13
- if (!["POST", "PUT", "PATCH", "QUERY"].includes(req.method)) {
14
- return;
15
- }
16
-
17
- const contentType = req.headers["content-type"];
18
- if (!contentType || !contentType.includes("application/json")) {
19
- return;
20
- }
21
-
22
- const contentLength = parseInt(req.headers["content-length"], 10);
23
- if (contentLength && contentLength > limit) {
24
- res.statusCode = 413;
25
- throw new Error(http.STATUS_CODES[res.statusCode]);
26
- }
27
-
28
- let chunkLength = 0;
29
-
30
- const chunks = [];
31
-
32
- for await (const chunk of req) {
33
- chunkLength += chunk.length;
34
- if (chunkLength > limit) {
35
- res.statusCode = 413;
36
- throw new Error(http.STATUS_CODES[res.statusCode]);
37
- }
38
- chunks.push(chunk);
39
- }
40
-
41
- if (chunks.length === 0) {
42
- req.body = null;
43
- return;
44
- }
45
-
46
- try {
47
- req.body = JSON.parse(Buffer.concat(chunks), reviver);
48
- } catch (error) {
49
- res.statusCode = 400;
50
- throw new Error("Invalid JSON body");
51
- }
52
- };
53
- }
54
-
55
- exports.json = json;
1
+ const http = require("node:http");
2
+
3
+ // middleware
4
+ function json({ limit = 100 * 1024 } = {}) {
5
+ const reviver = (key, value) => {
6
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
7
+ return undefined;
8
+ }
9
+ return value;
10
+ };
11
+
12
+ return async (req, res) => {
13
+ if (!["POST", "PUT", "PATCH", "QUERY"].includes(req.method)) {
14
+ return;
15
+ }
16
+
17
+ const contentType = req.headers["content-type"];
18
+ if (!contentType || !contentType.includes("application/json")) {
19
+ return;
20
+ }
21
+
22
+ const contentLength = parseInt(req.headers["content-length"], 10);
23
+ if (contentLength && contentLength > limit) {
24
+ res.statusCode = 413;
25
+ throw new Error(http.STATUS_CODES[res.statusCode]);
26
+ }
27
+
28
+ let chunkLength = 0;
29
+
30
+ const chunks = [];
31
+
32
+ for await (const chunk of req) {
33
+ chunkLength += chunk.length;
34
+ if (chunkLength > limit) {
35
+ res.statusCode = 413;
36
+ throw new Error(http.STATUS_CODES[res.statusCode]);
37
+ }
38
+ chunks.push(chunk);
39
+ }
40
+
41
+ if (chunks.length === 0) {
42
+ req.body = null;
43
+ return;
44
+ }
45
+
46
+ try {
47
+ req.body = JSON.parse(Buffer.concat(chunks), reviver);
48
+ } catch (error) {
49
+ res.statusCode = 400;
50
+ throw new Error("Invalid JSON body");
51
+ }
52
+ };
53
+ }
54
+
55
+ exports.json = json;
package/src/node.js CHANGED
@@ -1,81 +1,81 @@
1
- const { Pattern } = require("./pattern.js");
2
-
3
- class Node {
4
- constructor(segment = "") {
5
- this.segment = segment;
6
- this.pattern = new Pattern(segment);
7
- /**@type {Map<string,Node>}*/
8
- this.children = new Map();
9
- this.handlers = new Map([
10
- ["ALL", []],
11
- ["GET", []],
12
- ["QUERY", []],
13
- ["HEAD", []],
14
- ["OPTIONS", []],
15
- ["TRACE", []],
16
- ["PUT", []],
17
- ["DELETE", []],
18
- ["POST", []],
19
- ["PATCH", []],
20
- ["CONNECT", []],
21
- ]);
22
- }
23
-
24
- add(segment) {
25
- if (!this.children.has(segment)) {
26
- this.children.set(segment, new Node(segment));
27
- }
28
- return this.children.get(segment);
29
- }
30
-
31
- /**
32
- * @param {Node} node
33
- */
34
- merge(node, offset) {
35
- for (const [method, handlers] of node.handlers) {
36
- for (const handler of handlers) {
37
- handler.seq += offset;
38
- this.handlers.get(method).push(handler);
39
- }
40
- }
41
-
42
- for (const [segment, child] of node.children) {
43
- if (!this.children.has(segment)) {
44
- child.setSeqOffset(offset);
45
- this.children.set(segment, child);
46
- } else {
47
- this.children.get(segment).merge(child, offset);
48
- }
49
- }
50
- }
51
-
52
- setSeqOffset(offset) {
53
- for (const [, handlers] of this.handlers) {
54
- for (const handler of handlers) {
55
- handler.seq += offset;
56
- }
57
- }
58
-
59
- for (const [, child] of this.children) {
60
- child.setSeqOffset(offset);
61
- }
62
- }
63
-
64
- getMaxSeq() {
65
- let max = -1;
66
-
67
- for (const [, handlers] of this.handlers) {
68
- for (const handler of handlers) {
69
- max = Math.max(max, handler.seq);
70
- }
71
- }
72
-
73
- for (const [, child] of this.children) {
74
- max = Math.max(max, child.getMaxSeq());
75
- }
76
-
77
- return max;
78
- }
79
- }
80
-
81
- exports.Node = Node;
1
+ const { Pattern } = require("./pattern.js");
2
+
3
+ class Node {
4
+ constructor(segment = "") {
5
+ this.segment = segment;
6
+ this.pattern = new Pattern(segment);
7
+ /**@type {Map<string,Node>}*/
8
+ this.children = new Map();
9
+ this.handlers = new Map([
10
+ ["ALL", []],
11
+ ["GET", []],
12
+ ["QUERY", []],
13
+ ["HEAD", []],
14
+ ["OPTIONS", []],
15
+ ["TRACE", []],
16
+ ["PUT", []],
17
+ ["DELETE", []],
18
+ ["POST", []],
19
+ ["PATCH", []],
20
+ ["CONNECT", []],
21
+ ]);
22
+ }
23
+
24
+ add(segment) {
25
+ if (!this.children.has(segment)) {
26
+ this.children.set(segment, new Node(segment));
27
+ }
28
+ return this.children.get(segment);
29
+ }
30
+
31
+ /**
32
+ * @param {Node} node
33
+ */
34
+ merge(node, offset) {
35
+ for (const [method, handlers] of node.handlers) {
36
+ for (const handler of handlers) {
37
+ handler.seq += offset;
38
+ this.handlers.get(method).push(handler);
39
+ }
40
+ }
41
+
42
+ for (const [segment, child] of node.children) {
43
+ if (!this.children.has(segment)) {
44
+ child.setSeqOffset(offset);
45
+ this.children.set(segment, child);
46
+ } else {
47
+ this.children.get(segment).merge(child, offset);
48
+ }
49
+ }
50
+ }
51
+
52
+ setSeqOffset(offset) {
53
+ for (const [, handlers] of this.handlers) {
54
+ for (const handler of handlers) {
55
+ handler.seq += offset;
56
+ }
57
+ }
58
+
59
+ for (const [, child] of this.children) {
60
+ child.setSeqOffset(offset);
61
+ }
62
+ }
63
+
64
+ getMaxSeq() {
65
+ let max = -1;
66
+
67
+ for (const [, handlers] of this.handlers) {
68
+ for (const handler of handlers) {
69
+ max = Math.max(max, handler.seq);
70
+ }
71
+ }
72
+
73
+ for (const [, child] of this.children) {
74
+ max = Math.max(max, child.getMaxSeq());
75
+ }
76
+
77
+ return max;
78
+ }
79
+ }
80
+
81
+ exports.Node = Node;
package/src/pattern.js CHANGED
@@ -1,34 +1,34 @@
1
- /**
2
- * @typedef PatternExecResult
3
- * @property {number} index
4
- * @property {string} input
5
- * @property {Record<string, string>} [groups]
6
- * @property {number} length
7
- */
8
- class Pattern {
9
- constructor(input) {
10
- const pattern = `^${input
11
- .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
12
- .replace(/:(\w+)/g, "(?<$1>[^/]+)")
13
- .replace(/\*/, ".*")}$`;
14
- Object.defineProperty(this, "regex", {
15
- value: new RegExp(pattern),
16
- configurable: true,
17
- enumerable: false,
18
- });
19
- }
20
-
21
- /**
22
- * @param {string} input
23
- * @returns {PatternExecResult | null}
24
- */
25
- exec(input) {
26
- return this.regex.exec(input);
27
- }
28
-
29
- test(input) {
30
- return this.regex.test(input);
31
- }
32
- }
33
-
34
- exports.Pattern = Pattern;
1
+ /**
2
+ * @typedef PatternExecResult
3
+ * @property {number} index
4
+ * @property {string} input
5
+ * @property {Record<string, string>} [groups]
6
+ * @property {number} length
7
+ */
8
+ class Pattern {
9
+ constructor(input) {
10
+ const pattern = `^${input
11
+ .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
12
+ .replace(/:(\w+)/g, "(?<$1>[^/]+)")
13
+ .replace(/\\\*/g, ".*")}$`;
14
+ Object.defineProperty(this, "regex", {
15
+ value: new RegExp(pattern),
16
+ configurable: true,
17
+ enumerable: false,
18
+ });
19
+ }
20
+
21
+ /**
22
+ * @param {string} input
23
+ * @returns {PatternExecResult | null}
24
+ */
25
+ exec(input) {
26
+ return this.regex.exec(input);
27
+ }
28
+
29
+ test(input) {
30
+ return this.regex.test(input);
31
+ }
32
+ }
33
+
34
+ exports.Pattern = Pattern;
package/src/request.js CHANGED
@@ -1,37 +1,37 @@
1
- const http = require("node:http");
2
- const { parseUrl, parseQueryString } = require("./util.js");
3
-
4
- class Request extends http.IncomingMessage {
5
- _path;
6
- _parsed;
7
- _query;
8
- params={};
9
- timeout=0;
10
-
11
- get path() {
12
- if (this._path) return this._path;
13
-
14
- if (this.url.indexOf("?") === -1) {
15
- this._path = this.url;
16
- return this._path;
17
- }
18
-
19
- this._parsed = parseUrl(this.url)
20
- this._path = this._parsed.path;
21
- return this._path;
22
- }
23
-
24
- get query() {
25
- if (this._query) return this._query;
26
-
27
- if (!this._parsed) {
28
- this._query = {};
29
- return this._query;
30
- }
31
-
32
- this._query = parseQueryString(this._parsed.queryString);
33
- return this._query;
34
- }
35
- }
36
-
37
- exports.Request = Request;
1
+ const http = require("node:http");
2
+ const { parseUrl, parseQueryString } = require("./util.js");
3
+
4
+ class Request extends http.IncomingMessage {
5
+ _path;
6
+ _parsed;
7
+ _query;
8
+ params = {};
9
+ timeout = 0;
10
+
11
+ get path() {
12
+ if (this._path) return this._path;
13
+
14
+ if (this.url.indexOf("?") === -1) {
15
+ this._path = this.url;
16
+ return this._path;
17
+ }
18
+
19
+ this._parsed = parseUrl(this.url);
20
+ this._path = this._parsed.path;
21
+ return this._path;
22
+ }
23
+
24
+ get query() {
25
+ if (this._query) return this._query;
26
+
27
+ if (!this._parsed) {
28
+ this._query = {};
29
+ return this._query;
30
+ }
31
+
32
+ this._query = parseQueryString(this._parsed.queryString);
33
+ return this._query;
34
+ }
35
+ }
36
+
37
+ exports.Request = Request;
package/src/response.js CHANGED
@@ -1,13 +1,13 @@
1
- const http = require("node:http");
2
-
3
- class Response extends http.ServerResponse {
4
- send(chunk) {
5
- this.end(chunk);
6
- }
7
-
8
- json(value) {
9
- this.setHeader("Content-Type","application/json")
10
- this.end(JSON.stringify(value));
11
- }
12
- }
13
- exports.Response = Response;
1
+ const http = require("node:http");
2
+
3
+ class Response extends http.ServerResponse {
4
+ send(chunk) {
5
+ this.end(chunk);
6
+ }
7
+
8
+ json(value) {
9
+ this.setHeader("Content-Type", "application/json");
10
+ this.end(JSON.stringify(value));
11
+ }
12
+ }
13
+ exports.Response = Response;
package/src/router.js CHANGED
@@ -1,164 +1,164 @@
1
- const { Node } = require("./node.js");
2
- const { Cache } = require("./cache.js");
3
-
4
- class Router {
5
- constructor(options = {}) {
6
- this.cacheSize = options.size ?? 1000;
7
-
8
- this.root = new Node();
9
- this.seq = 0;
10
- this.cache = new Cache(this.cacheSize);
11
- }
12
-
13
- split(path) {
14
- return path.split("/").filter(Boolean);
15
- }
16
-
17
- add(method, path, ...handlers) {
18
- if (typeof path === "function") {
19
- handlers = [path, ...handlers];
20
- path = null;
21
- }
22
-
23
- let node = this.root;
24
-
25
- if (path !== null) {
26
- for (const segment of this.split(path)) {
27
- node = node.add(segment);
28
- }
29
- }
30
-
31
- for (const handler of handlers) {
32
- if (handler.root) {
33
- node.merge(handler.root, this.seq);
34
- this.seq = node.getMaxSeq() + 1;
35
- } else {
36
- handler.seq = this.seq++;
37
- node.handlers.get(method).push(handler);
38
- }
39
- }
40
- }
41
-
42
- use(path, ...handlers) {
43
- this.add("ALL", path, ...handlers);
44
- return this;
45
- }
46
-
47
- get(path, ...handlers) {
48
- this.add("GET", path, ...handlers);
49
- return this;
50
- }
51
-
52
- query(path, ...handlers) {
53
- this.add("QUERY", path, ...handlers);
54
- return this;
55
- }
56
-
57
- head(path, ...handlers) {
58
- this.add("HEAD", path, ...handlers);
59
- return this;
60
- }
61
-
62
- options(path, ...handlers) {
63
- this.add("OPTIONS", path, ...handlers);
64
- return this;
65
- }
66
-
67
- trace(path, ...handlers) {
68
- this.add("TRACE", path, ...handlers);
69
- return this;
70
- }
71
-
72
- put(path, ...handlers) {
73
- this.add("PUT", path, ...handlers);
74
- return this;
75
- }
76
-
77
- delete(path, ...handlers) {
78
- this.add("DELETE", path, ...handlers);
79
- return this;
80
- }
81
-
82
- post(path, ...handlers) {
83
- this.add("POST", path, ...handlers);
84
- return this;
85
- }
86
-
87
- patch(path, ...handlers) {
88
- this.add("PATCH", path, ...handlers);
89
- return this;
90
- }
91
-
92
- connect(path, ...handlers) {
93
- this.add("CONNECT", path, ...handlers);
94
- return this;
95
- }
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
- */
102
- find(method, path) {
103
- const key = `${method} ${path}`;
104
-
105
- const cached = this.cache.get(key);
106
- if (cached) return cached;
107
-
108
- let node = this.root;
109
-
110
- let params = {};
111
- const handlers = {};
112
- const errorHandlers = {};
113
-
114
- for (const input of this.split(path)) {
115
- let matched = node.children.get(input);
116
-
117
- if (!matched) {
118
- for (const [, child] of node.children) {
119
- const match = child.pattern.exec(input);
120
- if (match) {
121
- matched = child;
122
- Object.assign(params, match.groups);
123
- break;
124
- }
125
- }
126
- }
127
-
128
- if (!matched) {
129
- node = null;
130
- params = {};
131
- break;
132
- }
133
-
134
- node = matched;
135
-
136
- for (const handler of node.handlers.get("ALL")) {
137
- if (handler.length === 3) errorHandlers[handler.seq] = handler;
138
- else handlers[handler.seq] = handler;
139
- }
140
- }
141
-
142
- for (const handler of this.root.handlers.get("ALL")) {
143
- if (handler.length === 3) errorHandlers[handler.seq] = handler;
144
- else handlers[handler.seq] = handler;
145
- }
146
-
147
- if (node) {
148
- for (const handler of node.handlers.get(method)) {
149
- handlers[handler.seq] = handler
150
- }
151
- }
152
-
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;
162
- }
163
- }
164
- exports.Router = Router;
1
+ const { Node } = require("./node.js");
2
+ const { Cache } = require("./cache.js");
3
+
4
+ class Router {
5
+ constructor(options = {}) {
6
+ this.cacheSize = options.size ?? 1000;
7
+
8
+ this.root = new Node();
9
+ this.seq = 0;
10
+ this.cache = new Cache(this.cacheSize);
11
+ }
12
+
13
+ split(path) {
14
+ return path.split("/").filter(Boolean);
15
+ }
16
+
17
+ add(method, path, ...handlers) {
18
+ if (typeof path === "function") {
19
+ handlers = [path, ...handlers];
20
+ path = null;
21
+ }
22
+
23
+ let node = this.root;
24
+
25
+ if (path !== null) {
26
+ for (const segment of this.split(path)) {
27
+ node = node.add(segment);
28
+ }
29
+ }
30
+
31
+ for (const handler of handlers) {
32
+ if (handler.root) {
33
+ node.merge(handler.root, this.seq);
34
+ this.seq = node.getMaxSeq() + 1;
35
+ } else {
36
+ handler.seq = this.seq++;
37
+ node.handlers.get(method).push(handler);
38
+ }
39
+ }
40
+ }
41
+
42
+ use(path, ...handlers) {
43
+ this.add("ALL", path, ...handlers);
44
+ return this;
45
+ }
46
+
47
+ get(path, ...handlers) {
48
+ this.add("GET", path, ...handlers);
49
+ return this;
50
+ }
51
+
52
+ query(path, ...handlers) {
53
+ this.add("QUERY", path, ...handlers);
54
+ return this;
55
+ }
56
+
57
+ head(path, ...handlers) {
58
+ this.add("HEAD", path, ...handlers);
59
+ return this;
60
+ }
61
+
62
+ options(path, ...handlers) {
63
+ this.add("OPTIONS", path, ...handlers);
64
+ return this;
65
+ }
66
+
67
+ trace(path, ...handlers) {
68
+ this.add("TRACE", path, ...handlers);
69
+ return this;
70
+ }
71
+
72
+ put(path, ...handlers) {
73
+ this.add("PUT", path, ...handlers);
74
+ return this;
75
+ }
76
+
77
+ delete(path, ...handlers) {
78
+ this.add("DELETE", path, ...handlers);
79
+ return this;
80
+ }
81
+
82
+ post(path, ...handlers) {
83
+ this.add("POST", path, ...handlers);
84
+ return this;
85
+ }
86
+
87
+ patch(path, ...handlers) {
88
+ this.add("PATCH", path, ...handlers);
89
+ return this;
90
+ }
91
+
92
+ connect(path, ...handlers) {
93
+ this.add("CONNECT", path, ...handlers);
94
+ return this;
95
+ }
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
+ */
102
+ find(method, path) {
103
+ const key = `${method} ${path}`;
104
+
105
+ const cached = this.cache.get(key);
106
+ if (cached) return cached;
107
+
108
+ let node = this.root;
109
+
110
+ let params = {};
111
+ const handlers = {};
112
+ const errorHandlers = {};
113
+
114
+ for (const input of this.split(path)) {
115
+ let matched = node.children.get(input);
116
+
117
+ if (!matched) {
118
+ for (const [, child] of node.children) {
119
+ const match = child.pattern.exec(input);
120
+ if (match) {
121
+ matched = child;
122
+ Object.assign(params, match.groups);
123
+ break;
124
+ }
125
+ }
126
+ }
127
+
128
+ if (!matched) {
129
+ node = null;
130
+ params = {};
131
+ break;
132
+ }
133
+
134
+ node = matched;
135
+
136
+ for (const handler of node.handlers.get("ALL")) {
137
+ if (handler.length === 3) errorHandlers[handler.seq] = handler;
138
+ else handlers[handler.seq] = handler;
139
+ }
140
+ }
141
+
142
+ for (const handler of this.root.handlers.get("ALL")) {
143
+ if (handler.length === 3) errorHandlers[handler.seq] = handler;
144
+ else handlers[handler.seq] = handler;
145
+ }
146
+
147
+ if (node) {
148
+ for (const handler of node.handlers.get(method)) {
149
+ handlers[handler.seq] = handler;
150
+ }
151
+ }
152
+
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;
162
+ }
163
+ }
164
+ exports.Router = Router;
package/src/util.js CHANGED
@@ -1,27 +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;
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;