@ndiinginc/router 0.0.0-stage → 4.0.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/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ridho Prasetya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,237 @@
1
- # Temporary Holding Version
1
+ # @ndiinginc/router
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Router HTTP kecil, cepat, dan **zero-dependency** untuk Node.js. Dibangun langsung di atas `node:http`, dengan routing berbasis tree, nested router, middleware async, error handler, dan JSON body parser bawaan.
4
+
5
+ ## Fitur
6
+
7
+ - Zero dependency, murni `node:http`
8
+ - Routing berbasis tree per segment (static match diprioritaskan, lalu dynamic)
9
+ - Parameter dinamis `:id` dan wildcard `*`
10
+ - Nested router dengan `use(path, router)`
11
+ - Middleware global dan per-path, mendukung `async`
12
+ - Error handler dengan signature `(err, req, res)`
13
+ - `req.path`, `req.query`, `req.params`, `req.body`
14
+ - Helper `res.send()` dan `res.json()`
15
+ - JSON body parser dengan limit ukuran dan proteksi prototype pollution
16
+ - Semua HTTP method, termasuk `QUERY`
17
+
18
+ ## Instalasi
19
+
20
+ ```bash
21
+ npm install @ndiinginc/router
22
+ ```
23
+
24
+ Butuh **Node.js 22.1+** (memakai `URL.parse`).
25
+
26
+ ## Quick Start
27
+
28
+ ```js
29
+ const Router = require("@ndiinginc/router");
30
+
31
+ const app = new Router();
32
+
33
+ app.get("/", (req, res) => {
34
+ res.json({ hello: "world" });
35
+ });
36
+
37
+ app.get("/users/:id", (req, res) => {
38
+ res.json({ id: req.params.id });
39
+ });
40
+
41
+ app.listen(3000);
42
+ ```
43
+
44
+ ## Routing
45
+
46
+ Semua method mengembalikan `this`, jadi bisa di-chain.
47
+
48
+ ```js
49
+ app.get("/users", handler)
50
+ .post("/users", handler)
51
+ .put("/users/:id", handler)
52
+ .patch("/users/:id", handler)
53
+ .delete("/users/:id", handler);
54
+ ```
55
+
56
+ Method yang tersedia: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`, `trace`, `connect`, `query`, dan `use` (semua method).
57
+
58
+ ### Parameter dinamis
59
+
60
+ ```js
61
+ app.get("/posts/:postId/comments/:commentId", (req, res) => {
62
+ const { postId, commentId } = req.params;
63
+ res.json({ postId, commentId });
64
+ });
65
+ ```
66
+
67
+ ### Wildcard
68
+
69
+ `*` cocok dengan karakter apa pun di dalam satu segmen.
70
+
71
+ ```js
72
+ app.get("/files/report-*", (req, res) => {
73
+ res.json({ path: req.path });
74
+ });
75
+ ```
76
+
77
+ ### Prioritas match
78
+
79
+ Untuk setiap segmen, router mencoba **static match dulu**, baru pattern dinamis. Jadi `/users/me` menang atas `/users/:id` bila keduanya terdaftar.
80
+
81
+ ## Middleware
82
+
83
+ Middleware didaftarkan dengan `use()` dan dijalankan **berurutan sesuai urutan pendaftaran**.
84
+
85
+ ```js
86
+ // Global
87
+ app.use((req, res) => {
88
+ console.log(req.method, req.path);
89
+ });
90
+
91
+ // Scoped: jalan untuk /admin dan semua sub-path-nya
92
+ app.use("/admin", async (req, res) => {
93
+ if (req.headers.authorization !== "secret") {
94
+ res.statusCode = 401;
95
+ res.json({ error: "Unauthorized" });
96
+ }
97
+ });
98
+ ```
99
+
100
+ Chain berhenti otomatis begitu response dikirim (`res.send()` / `res.json()` menandai `res.done = true`), jadi handler berikutnya tidak dipanggil.
101
+
102
+ > Middleware global tetap dijalankan walaupun tidak ada route yang cocok (sebelum 404 dikirim).
103
+
104
+ ## Nested Router
105
+
106
+ Pecah aplikasi jadi beberapa router lalu pasang dengan `use(path, router)`. Nesting bisa berlapis.
107
+
108
+ ```js
109
+ const users = new Router();
110
+ users.get("/", (req, res) => res.json({ list: [] }));
111
+ users.get("/:id", (req, res) => res.json({ id: req.params.id }));
112
+
113
+ const admin = new Router();
114
+ admin.use("/users", users);
115
+
116
+ const app = new Router();
117
+ app.use("/admin", admin);
118
+
119
+ // GET /admin/users -> list
120
+ // GET /admin/users/42 -> { id: "42" }
121
+ ```
122
+
123
+ ## Error Handling
124
+
125
+ Handler dengan **tiga argumen** `(err, req, res)` dikenali sebagai error handler. Error handler dipanggil saat handler atau middleware melempar error (sync maupun async).
126
+
127
+ ```js
128
+ app.get("/boom", () => {
129
+ throw new Error("Something broke");
130
+ });
131
+
132
+ app.use((err, req, res) => {
133
+ res.statusCode = err.statusCode || 500;
134
+ res.json({ error: err.message });
135
+ });
136
+ ```
137
+
138
+ ### Respons default
139
+
140
+ Jika tidak ada handler yang mengirim response, router mengirim JSON otomatis:
141
+
142
+ ```json
143
+ { "error": { "code": undefined, "message": "Not Found" } }
144
+ ```
145
+
146
+ - Tidak ada route cocok: status `404`
147
+ - Error dengan status 2xx: dinaikkan menjadi `500`
148
+ - Saat `NODE_ENV=development`, field `stack` ikut disertakan
149
+
150
+ ## Request
151
+
152
+ | Properti | Keterangan |
153
+ | --- | --- |
154
+ | `req.path` | Pathname tanpa query string |
155
+ | `req.query` | Object query string. Key yang berulang menjadi array |
156
+ | `req.params` | Parameter dari route dinamis |
157
+ | `req.body` | Hasil parse body (butuh middleware `json()`) |
158
+
159
+ ```js
160
+ // GET /search?tag=a&tag=b&page=2
161
+ app.get("/search", (req, res) => {
162
+ res.json(req.query); // { tag: ["a", "b"], page: "2" }
163
+ });
164
+ ```
165
+
166
+ ## Response
167
+
168
+ | Method | Keterangan |
169
+ | --- | --- |
170
+ | `res.send(string)` | Kirim response dan akhiri chain |
171
+ | `res.json(object)` | Set `content-type: application/json`, serialize, lalu kirim |
172
+
173
+ Keduanya juga turunan dari `http.ServerResponse`, jadi `res.statusCode`, `res.setHeader()`, dan lainnya tetap tersedia.
174
+
175
+ ## JSON Body Parser
176
+
177
+ ```js
178
+ const Router = require("@ndiinginc/router");
179
+ const { json } = require("@ndiinginc/router/middleware");
180
+
181
+ const app = new Router();
182
+
183
+ app.use(json({ limit: 100 * 1024 })); // default 100 KB
184
+
185
+ app.post("/users", (req, res) => {
186
+ res.json({ received: req.body });
187
+ });
188
+ ```
189
+
190
+ Perilaku:
191
+
192
+ - Hanya memproses method `POST`, `PUT`, `PATCH`, dan `QUERY` dengan `content-type: application/json`
193
+ - Body melebihi `limit` memicu error `413 Payload Too Large` (dicek lewat `content-length` dan saat streaming)
194
+ - JSON tidak valid memicu error `400 Invalid JSON body`
195
+ - Body kosong menghasilkan `req.body = null`
196
+ - Key `__proto__`, `constructor`, dan `prototype` dibuang untuk mencegah prototype pollution
197
+
198
+ ## API
199
+
200
+ ### `new Router()`
201
+
202
+ Membuat instance router baru.
203
+
204
+ ### `router.use([path], handler | router)`
205
+
206
+ Mendaftarkan middleware, error handler, atau nested router untuk semua method.
207
+
208
+ ### `router.<method>([path], handler)`
209
+
210
+ Mendaftarkan handler untuk method tertentu. `path` opsional; jika dihilangkan, handler dipasang di root router.
211
+
212
+ ### `router.find(method, path)`
213
+
214
+ Mencari route tanpa menjalankannya. Berguna untuk testing.
215
+
216
+ ```js
217
+ const { params, handlers, errorHandlers } = app.find("GET", "/users/1");
218
+ ```
219
+
220
+ ### `router.listen(...args)`
221
+
222
+ Membuat `http.Server` dengan `Request` dan `Response` milik router, lalu memanggil `server.listen(...args)`. Mengembalikan instance `http.Server`.
223
+
224
+ ```js
225
+ const server = app.listen(0); // port acak
226
+ console.log(server.address().port);
227
+ ```
228
+
229
+ ## Testing
230
+
231
+ ```bash
232
+ npm test
233
+ ```
234
+
235
+ ## Lisensi
236
+
237
+ MIT
package/package.json CHANGED
@@ -1,6 +1,26 @@
1
1
  {
2
2
  "name": "@ndiinginc/router",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "4.0.0",
4
+ "description": "",
5
+ "main": "./src/router.js",
6
+ "scripts": {
7
+ "test": "nodemon --exec \"node -e \\\"require('@ndiinginc/test')(require('path').resolve('tests'),{verbose:true})\\\"\"",
8
+ "dev": "nodemon src/router.js",
9
+ "prettier": "npx prettier . --write",
10
+ "commit": "node -e \"require('@ndiinginc/commit')()\"",
11
+ "npm:dry-run": "npm publish --dry-run",
12
+ "npm:login": "npm login",
13
+ "npm:publish": "npm publish --access public",
14
+ "tsc": "npx tsc"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "type": "commonjs",
20
+ "devDependencies": {
21
+ "@ndiinginc/commit": "^1.0.1",
22
+ "@ndiinginc/test": "^1.5.1",
23
+ "@types/jest": "^30.0.0",
24
+ "@types/node": "^26.6.4"
25
+ }
6
26
  }
@@ -0,0 +1,18 @@
1
+ ###
2
+ GET http://localhost:3000
3
+ ###
4
+ POST http://localhost:3000
5
+ Content-Type: application/json; charset=utf-8; asdfadsf
6
+
7
+ {
8
+ "user":"name",
9
+ "pass":"word"
10
+ }
11
+ ###
12
+ GET http://localhost:4000
13
+ ###
14
+ GET http://localhost:5000
15
+
16
+ autocannon -c 100 -d 10 -p 10 localhost:3000
17
+ autocannon -c 100 -d 10 -p 10 localhost:4000
18
+ autocannon -c 100 -d 10 -p 10 localhost:5000
@@ -0,0 +1,52 @@
1
+ const http = require("node:http");
2
+
3
+ function json({ limit = 100 * 1024 } = {}) {
4
+ const reviver = (key, value) => {
5
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
6
+ return undefined;
7
+ }
8
+ return value;
9
+ };
10
+
11
+ return async (req, res) => {
12
+ if (!["POST", "PUT", "PATCH", "QUERY"].includes(req.method)) {
13
+ return;
14
+ }
15
+
16
+ const contentType = req.headers["content-type"];
17
+ if (!contentType || !contentType.includes("application/json")) {
18
+ return;
19
+ }
20
+
21
+ const contentLength = parseInt(req.headers["content-length"], 10);
22
+ if (contentLength && contentLength > limit) {
23
+ res.statusCode = 413;
24
+ throw new Error(http.STATUS_CODES[res.statusCode]);
25
+ }
26
+
27
+ let bodySize = 0;
28
+ const chunks = [];
29
+ for await (const chunk of req) {
30
+ bodySize += chunk.length;
31
+ if (bodySize > limit) {
32
+ res.statusCode = 413;
33
+ throw new Error(http.STATUS_CODES[res.statusCode]);
34
+ }
35
+ chunks.push(chunk);
36
+ }
37
+
38
+ if (chunks.length === 0) {
39
+ req.body = null;
40
+ return;
41
+ }
42
+
43
+ const rawBody = Buffer.concat(chunks).toString("utf8");
44
+ try {
45
+ req.body = JSON.parse(rawBody, reviver);
46
+ } catch (error) {
47
+ res.statusCode = 400;
48
+ throw new Error("Invalid JSON body");
49
+ }
50
+ };
51
+ }
52
+ module.exports.json = json;
package/src/node.js ADDED
@@ -0,0 +1,70 @@
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
+ /**@type {Map<string, Function[]>}*/
10
+ this.handlers = new Map();
11
+ }
12
+
13
+ add(segment) {
14
+ if (!this.children.has(segment)) {
15
+ this.children.set(segment, new Node(segment));
16
+ }
17
+ return this.children.get(segment);
18
+ }
19
+
20
+ /**
21
+ * @param {Node} node
22
+ * @param {Number} offset
23
+ */
24
+ merge(node, offset) {
25
+ for (const [method, handlers] of node.handlers) {
26
+ for (const handler of handlers) {
27
+ handler.seq += offset;
28
+ this.handlers.getOrInsert(method, []).push(handler);
29
+ }
30
+ }
31
+
32
+ for (const [segment, child] of node.children) {
33
+ if (!this.children.has(segment)) {
34
+ child.setSeqOffset(offset);
35
+ this.children.set(segment, child);
36
+ } else {
37
+ this.children.get(segment).merge(child, offset);
38
+ }
39
+ }
40
+ }
41
+
42
+ setSeqOffset(offset) {
43
+ for (const [, handlers] of this.handlers) {
44
+ for (const handler of handlers) {
45
+ handler.seq += offset;
46
+ }
47
+ }
48
+
49
+ for (const [, child] of this.children) {
50
+ child.setSeqOffset(offset);
51
+ }
52
+ }
53
+
54
+ getMaxSeq() {
55
+ let max = -1;
56
+
57
+ for (const [, handlers] of this.handlers) {
58
+ for (const handler of handlers) {
59
+ max = Math.max(max, handler.seq);
60
+ }
61
+ }
62
+
63
+ for (const [, child] of this.children) {
64
+ max = Math.max(max, child.getMaxSeq());
65
+ }
66
+
67
+ return max;
68
+ }
69
+ }
70
+ exports.Node = Node;
package/src/pattern.js ADDED
@@ -0,0 +1,24 @@
1
+ class Pattern {
2
+ static baseURL = "http://localhost";
3
+
4
+ constructor(input) {
5
+ const regexStr = input
6
+ .replace(/[.+*?^${}()|[\]\\]/g, "\\$&")
7
+ .replace(/:(\w+)/g, "(?<$1>[^/]+)")
8
+ .replace(/\\\*/g, ".*");
9
+ this.regex = new RegExp(`^${regexStr}$`);
10
+ }
11
+
12
+ exec(input) {
13
+ const match = this.regex.exec(input);
14
+ if (!match) return null;
15
+ return {
16
+ pathname: { groups: match.groups || {} },
17
+ };
18
+ }
19
+
20
+ test(input) {
21
+ return this.regex.test(input);
22
+ }
23
+ }
24
+ exports.Pattern = Pattern;
@@ -0,0 +1,9 @@
1
+ // Polyfill for Map.prototype.getOrInsert
2
+ if (!Map.prototype.getOrInsert) {
3
+ Map.prototype.getOrInsert = function (key, defaultValue) {
4
+ if (!this.has(key)) {
5
+ this.set(key, defaultValue);
6
+ }
7
+ return this.get(key);
8
+ };
9
+ }
package/src/request.js ADDED
@@ -0,0 +1,29 @@
1
+ const { IncomingMessage } = require("node:http");
2
+
3
+ class Request extends IncomingMessage {
4
+ get path() {
5
+ if (this.url.indexOf("?") !== -1) {
6
+ this.parsed = URL.parse(this.url, "http://localhost");
7
+ return this.parsed.pathname;
8
+ }
9
+ return this.url;
10
+ }
11
+
12
+ get query() {
13
+ const query = {};
14
+ if (!this.parsed) return query;
15
+ for (const [name, value] of this.parsed.searchParams) {
16
+ if (query[name]) {
17
+ if (Array.isArray(query[name])) {
18
+ query[name].push(value);
19
+ } else {
20
+ query[name] = [query[name], value];
21
+ }
22
+ } else {
23
+ query[name] = value;
24
+ }
25
+ }
26
+ return query;
27
+ }
28
+ }
29
+ exports.Request = Request;
@@ -0,0 +1,14 @@
1
+ const { ServerResponse } = require("node:http");
2
+
3
+ class Response extends ServerResponse {
4
+ send(string) {
5
+ this.done = true;
6
+ this.end(string);
7
+ }
8
+
9
+ json(object) {
10
+ this.setHeader("content-type", "application/json");
11
+ this.send(JSON.stringify(object));
12
+ }
13
+ }
14
+ exports.Response = Response;
package/src/router.js ADDED
@@ -0,0 +1,212 @@
1
+ require("./polyfill.js");
2
+ const http = require("node:http");
3
+ const { Response } = require("./response.js");
4
+ const { Request } = require("./request.js");
5
+ const { Node } = require("./node.js");
6
+
7
+ class Router {
8
+ /**@type {Node}*/
9
+ root;
10
+
11
+ constructor() {
12
+ this.root = new Node("");
13
+ this.seq = 0;
14
+ }
15
+
16
+ split(path) {
17
+ return path.split("/").filter(Boolean);
18
+ }
19
+
20
+ add(method, path, handler) {
21
+ if (typeof path === "function") {
22
+ handler = path;
23
+ path = null;
24
+ }
25
+
26
+ let node = this.root;
27
+
28
+ if (path !== null) {
29
+ for (const segment of this.split(path)) {
30
+ node = node.add(segment);
31
+ }
32
+ }
33
+
34
+ if (handler.root) {
35
+ node.merge(handler.root, this.seq);
36
+ this.seq = node.getMaxSeq() + 1;
37
+ } else {
38
+ handler.seq = this.seq++;
39
+ node.handlers.getOrInsert(method, []).push(handler);
40
+ }
41
+ }
42
+
43
+ use(path, handler) {
44
+ this.add("ALL", path, handler);
45
+ return this;
46
+ }
47
+
48
+ get(path, handler) {
49
+ this.add("GET", path, handler);
50
+ return this;
51
+ }
52
+ query(path, handler) {
53
+ this.add("QUERY", path, handler);
54
+ return this;
55
+ }
56
+ head(path, handler) {
57
+ this.add("HEAD", path, handler);
58
+ return this;
59
+ }
60
+ options(path, handler) {
61
+ this.add("OPTIONS", path, handler);
62
+ return this;
63
+ }
64
+ trace(path, handler) {
65
+ this.add("TRACE", path, handler);
66
+ return this;
67
+ }
68
+ put(path, handler) {
69
+ this.add("PUT", path, handler);
70
+ return this;
71
+ }
72
+ delete(path, handler) {
73
+ this.add("DELETE", path, handler);
74
+ return this;
75
+ }
76
+ post(path, handler) {
77
+ this.add("POST", path, handler);
78
+ return this;
79
+ }
80
+ patch(path, handler) {
81
+ this.add("PATCH", path, handler);
82
+ return this;
83
+ }
84
+ connect(path, handler) {
85
+ this.add("CONNECT", path, handler);
86
+ return this;
87
+ }
88
+
89
+ find(method, path) {
90
+ let params = {};
91
+ let handlers = {};
92
+ let errorHandlers = {};
93
+
94
+ let node = this.root;
95
+
96
+ const inputs = this.split(path);
97
+ if (inputs.length) {
98
+ for (const input of inputs) {
99
+ let matched = node.children.get(input);
100
+
101
+ if (!matched) {
102
+ for (const [, child] of node.children) {
103
+ const match = child.pattern.exec(input);
104
+ if (match) {
105
+ matched = child;
106
+ Object.assign(params, match.pathname.groups);
107
+ break;
108
+ }
109
+ }
110
+ }
111
+
112
+ if (!matched) {
113
+ node = null;
114
+ params = {};
115
+ break;
116
+ }
117
+
118
+ node = matched;
119
+
120
+ if (node.handlers.has("ALL")) {
121
+ for (const handler of node.handlers.get("ALL")) {
122
+ if (handler.length === 3) errorHandlers[handler.seq] = handler;
123
+ else handlers[handler.seq] = handler;
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ if (this.root.handlers.has("ALL")) {
130
+ for (const handler of this.root.handlers.get("ALL")) {
131
+ if (handler.length === 3) errorHandlers[handler.seq] = handler;
132
+ else handlers[handler.seq] = handler;
133
+ }
134
+ }
135
+
136
+ if (node && node.handlers.has(method)) {
137
+ for (const handler of node.handlers.get(method)) {
138
+ if (handler.length === 3) errorHandlers[handler.seq] = handler;
139
+ else handlers[handler.seq] = handler;
140
+ }
141
+ }
142
+
143
+ return {
144
+ params,
145
+ handlers: Object.values(handlers),
146
+ errorHandlers: Object.values(errorHandlers),
147
+ };
148
+ }
149
+
150
+ /**
151
+ * @param {import('http').RequestOptions} req
152
+ * @param {import('http').ServerResponse} res
153
+ */
154
+ async handleRequest(req, res) {
155
+ const result = this.find(req.method, req.path);
156
+ req.params = result.params;
157
+
158
+ try {
159
+ for (const handler of result.handlers) {
160
+ if (res.done) break;
161
+ await handler(req, res);
162
+ }
163
+ } catch (error) {
164
+ res.error = error;
165
+
166
+ try {
167
+ for (const handler of result.errorHandlers) {
168
+ if (res.done) break;
169
+ await handler(res.error, req, res);
170
+ }
171
+ } catch (error) {
172
+ res.error = error;
173
+ }
174
+ }
175
+
176
+ if (!res.done) {
177
+ if (!res.error) {
178
+ res.statusCode = 404;
179
+ res.error = new Error(http.STATUS_CODES[res.statusCode]);
180
+ }
181
+
182
+ res.statusCode = res.statusCode >= 200 && res.statusCode < 300 ? 500 : res.statusCode;
183
+
184
+ const error = {
185
+ code: res.error.code,
186
+ message: res.error.message,
187
+ };
188
+ if (process.env.NODE_ENV === "development") error.stack = res.error.stack;
189
+
190
+ res.json({ error });
191
+ }
192
+ }
193
+
194
+ /**
195
+ * @returns {import('http').Server}
196
+ */
197
+ listen(...params) {
198
+ const server = http.createServer(
199
+ {
200
+ IncomingMessage: Request,
201
+ ServerResponse: Response,
202
+ },
203
+ (req, res) => this.handleRequest(req, res),
204
+ );
205
+
206
+ server.listen(...params);
207
+
208
+ return server;
209
+ }
210
+ }
211
+
212
+ module.exports = Router;