@e22m4u/js-trie-router 0.0.1
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/.c8rc +9 -0
- package/.commitlintrc +5 -0
- package/.editorconfig +13 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +5 -0
- package/.mocharc.cjs +4 -0
- package/.prettierrc +7 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/eslint.config.js +34 -0
- package/examples/cookie-parsing-example.js +28 -0
- package/examples/params-parsing-example.js +28 -0
- package/examples/query-parsing-example.js +28 -0
- package/examples/uptime-example.js +40 -0
- package/package.json +57 -0
- package/src/chai.js +7 -0
- package/src/hooks/hook-invoker.d.ts +25 -0
- package/src/hooks/hook-invoker.js +104 -0
- package/src/hooks/hook-invoker.spec.js +462 -0
- package/src/hooks/hook-registry.d.ts +43 -0
- package/src/hooks/hook-registry.js +88 -0
- package/src/hooks/hook-registry.spec.js +165 -0
- package/src/hooks/index.d.ts +2 -0
- package/src/hooks/index.js +2 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +9 -0
- package/src/parsers/body-parser.d.ts +52 -0
- package/src/parsers/body-parser.js +161 -0
- package/src/parsers/body-parser.spec.js +297 -0
- package/src/parsers/cookie-parser.d.ts +21 -0
- package/src/parsers/cookie-parser.js +32 -0
- package/src/parsers/cookie-parser.spec.js +26 -0
- package/src/parsers/index.d.ts +4 -0
- package/src/parsers/index.js +4 -0
- package/src/parsers/query-parser.d.ts +21 -0
- package/src/parsers/query-parser.js +32 -0
- package/src/parsers/query-parser.spec.js +25 -0
- package/src/parsers/request-parser.d.ts +34 -0
- package/src/parsers/request-parser.js +65 -0
- package/src/parsers/request-parser.spec.js +137 -0
- package/src/request-context.d.ts +54 -0
- package/src/request-context.js +108 -0
- package/src/request-context.spec.js +89 -0
- package/src/route-registry.d.ts +39 -0
- package/src/route-registry.js +95 -0
- package/src/route-registry.spec.js +77 -0
- package/src/route.d.ts +82 -0
- package/src/route.js +184 -0
- package/src/route.spec.js +299 -0
- package/src/router-options.d.ts +18 -0
- package/src/router-options.js +41 -0
- package/src/router-options.spec.js +52 -0
- package/src/senders/data-sender.d.ts +15 -0
- package/src/senders/data-sender.js +72 -0
- package/src/senders/data-sender.spec.js +193 -0
- package/src/senders/error-sender.d.ts +25 -0
- package/src/senders/error-sender.js +85 -0
- package/src/senders/error-sender.spec.js +90 -0
- package/src/senders/index.d.ts +2 -0
- package/src/senders/index.js +2 -0
- package/src/service.d.ts +14 -0
- package/src/service.js +28 -0
- package/src/service.spec.js +11 -0
- package/src/trie-router.d.ts +66 -0
- package/src/trie-router.js +189 -0
- package/src/trie-router.spec.js +471 -0
- package/src/types.d.ts +19 -0
- package/src/utils/create-cookie-string.d.ts +6 -0
- package/src/utils/create-cookie-string.js +24 -0
- package/src/utils/create-cookie-string.spec.js +36 -0
- package/src/utils/create-debugger.d.ts +11 -0
- package/src/utils/create-debugger.js +22 -0
- package/src/utils/create-debugger.spec.js +30 -0
- package/src/utils/create-error.d.ts +14 -0
- package/src/utils/create-error.js +28 -0
- package/src/utils/create-error.spec.js +50 -0
- package/src/utils/create-request-mock.d.ts +28 -0
- package/src/utils/create-request-mock.js +345 -0
- package/src/utils/create-request-mock.spec.js +482 -0
- package/src/utils/create-response-mock.d.ts +16 -0
- package/src/utils/create-response-mock.js +119 -0
- package/src/utils/create-response-mock.spec.js +130 -0
- package/src/utils/fetch-request-body.d.ts +17 -0
- package/src/utils/fetch-request-body.js +133 -0
- package/src/utils/fetch-request-body.spec.js +211 -0
- package/src/utils/get-request-path.d.ts +8 -0
- package/src/utils/get-request-path.js +23 -0
- package/src/utils/get-request-path.spec.js +31 -0
- package/src/utils/index.d.ts +11 -0
- package/src/utils/index.js +11 -0
- package/src/utils/is-promise.d.ts +10 -0
- package/src/utils/is-promise.js +13 -0
- package/src/utils/is-promise.spec.js +20 -0
- package/src/utils/is-readable-stream.d.ts +9 -0
- package/src/utils/is-readable-stream.js +11 -0
- package/src/utils/is-readable-stream.spec.js +23 -0
- package/src/utils/is-response-sent.d.ts +8 -0
- package/src/utils/is-response-sent.js +23 -0
- package/src/utils/is-response-sent.spec.js +35 -0
- package/src/utils/is-writable-stream.d.ts +9 -0
- package/src/utils/is-writable-stream.js +11 -0
- package/src/utils/is-writable-stream.spec.js +23 -0
- package/src/utils/parse-content-type.d.ts +15 -0
- package/src/utils/parse-content-type.js +30 -0
- package/src/utils/parse-content-type.spec.js +62 -0
- package/src/utils/parse-cookie.d.ts +19 -0
- package/src/utils/parse-cookie.js +30 -0
- package/src/utils/parse-cookie.spec.js +37 -0
- package/src/utils/to-camel-case.d.ts +6 -0
- package/src/utils/to-camel-case.js +20 -0
- package/src/utils/to-camel-case.spec.js +32 -0
- package/tsconfig.json +11 -0
package/.c8rc
ADDED
package/.commitlintrc
ADDED
package/.editorconfig
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# EditorConfig is awesome: https://EditorConfig.org
|
|
2
|
+
|
|
3
|
+
# top-most EditorConfig file
|
|
4
|
+
root = true
|
|
5
|
+
|
|
6
|
+
# Unix-style newlines with a newline ending every file
|
|
7
|
+
[*]
|
|
8
|
+
end_of_line = lf
|
|
9
|
+
insert_final_newline = true
|
|
10
|
+
charset = utf-8
|
|
11
|
+
indent_style = space
|
|
12
|
+
indent_size = 2
|
|
13
|
+
max_line_length = 80
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx --no -- commitlint --edit $1
|
package/.mocharc.cjs
ADDED
package/.prettierrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 e22m4u@yandex.ru
|
|
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
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
## @e22m4u/js-trie-router
|
|
2
|
+
|
|
3
|
+
A pure ES-module of the Node.js HTTP router that uses the
|
|
4
|
+
[Trie](https://en.wikipedia.org/wiki/Trie) for routing.
|
|
5
|
+
|
|
6
|
+
- Uses [path-to-regexp](https://github.com/pillarjs/path-to-regexp) syntax.
|
|
7
|
+
- Supports path parameters.
|
|
8
|
+
- Parses JSON-body automatically.
|
|
9
|
+
- Parses a query string and the Cookie header.
|
|
10
|
+
- Supports `preHandler` and `postHandler` hooks.
|
|
11
|
+
- Asynchronous request handler.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @e22m4u/js-trie-router
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
A basic "Hello world." example.
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import http from 'http';
|
|
25
|
+
import {TrieRouter} from '../src/index.js';
|
|
26
|
+
import {HTTP_METHOD} from '../src/route.js';
|
|
27
|
+
|
|
28
|
+
const server = new http.Server(); // A Node.js HTTP server.
|
|
29
|
+
const router = new TrieRouter(); // A TrieRouter instance.
|
|
30
|
+
|
|
31
|
+
router.defineRoute({
|
|
32
|
+
method: HTTP_METHOD.GET, // Request method.
|
|
33
|
+
path: '/', // Path template like "/user/:id".
|
|
34
|
+
handler(ctx) { // Request handler.
|
|
35
|
+
return 'Hello world!';
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
server.on('request', router.requestHandler);
|
|
40
|
+
server.listen(3000, 'localhost');
|
|
41
|
+
|
|
42
|
+
// Open in browser http://localhost:3000
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### RequestContext
|
|
46
|
+
|
|
47
|
+
The first parameter of the `Router` handler is the `RequestContext` instance.
|
|
48
|
+
|
|
49
|
+
- `container: ServiceContainer`
|
|
50
|
+
- `req: IncomingMessage`
|
|
51
|
+
- `res: ServerResponse`
|
|
52
|
+
- `query: ParsedQuery`
|
|
53
|
+
- `headers: ParsedHeaders`
|
|
54
|
+
- `cookie: ParsedCookie`
|
|
55
|
+
|
|
56
|
+
The `RequestContext` can be destructured.
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
router.defineRoute({
|
|
60
|
+
// ...
|
|
61
|
+
handler({req, res, query, headers, cookie}) {
|
|
62
|
+
console.log(req); // IncomingMessage
|
|
63
|
+
console.log(res); // ServerResponse
|
|
64
|
+
console.log(query); // {id: '10', ...}
|
|
65
|
+
console.log(headers); // {'cookie': 'foo=bar', ...}
|
|
66
|
+
console.log(cookie); // {foo: 'bar', ...}
|
|
67
|
+
// ...
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Sending response
|
|
73
|
+
|
|
74
|
+
Return values of the `Route` handler will be sent as described below.
|
|
75
|
+
|
|
76
|
+
| type | content-type |
|
|
77
|
+
|---------|--------------------------|
|
|
78
|
+
| `string` | text/plain |
|
|
79
|
+
| `number` | application/json |
|
|
80
|
+
| `boolean` | application/json |
|
|
81
|
+
| `object` | application/json |
|
|
82
|
+
| `Buffer` | application/octet-stream |
|
|
83
|
+
| `Stream` | application/octet-stream |
|
|
84
|
+
|
|
85
|
+
Here is an example of a JSON response.
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
router.defineRoute({
|
|
89
|
+
// ...
|
|
90
|
+
handler(ctx) {
|
|
91
|
+
// sends "application/json"
|
|
92
|
+
return {foo: 'bar'};
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If the `ServerResponse` has been sent manually, then the return
|
|
98
|
+
value will be ignored.
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
router.defineRoute({
|
|
102
|
+
// ...
|
|
103
|
+
handler(ctx) {
|
|
104
|
+
res.statusCode = 404;
|
|
105
|
+
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
106
|
+
res.end('404 Not Found', 'utf-8');
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Debug
|
|
112
|
+
|
|
113
|
+
Set environment variable `DEBUG=jsTrieRouter*` before start.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
DEBUG=jsPathTrie* npm run test
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Testing
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm run test
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import globals from 'globals';
|
|
2
|
+
import eslintJs from '@eslint/js';
|
|
3
|
+
import eslintJsdocPlugin from 'eslint-plugin-jsdoc';
|
|
4
|
+
import eslintMochaPlugin from 'eslint-plugin-mocha';
|
|
5
|
+
import eslintPrettierConfig from 'eslint-config-prettier';
|
|
6
|
+
import eslintChaiExpectPlugin from 'eslint-plugin-chai-expect';
|
|
7
|
+
|
|
8
|
+
export default [{
|
|
9
|
+
languageOptions: {
|
|
10
|
+
globals: {
|
|
11
|
+
...globals.node,
|
|
12
|
+
...globals.es2021,
|
|
13
|
+
...globals.mocha,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
plugins: {
|
|
17
|
+
'jsdoc': eslintJsdocPlugin,
|
|
18
|
+
'mocha': eslintMochaPlugin,
|
|
19
|
+
'chai-expect': eslintChaiExpectPlugin,
|
|
20
|
+
},
|
|
21
|
+
rules: {
|
|
22
|
+
...eslintJs.configs.recommended.rules,
|
|
23
|
+
...eslintPrettierConfig.rules,
|
|
24
|
+
...eslintJsdocPlugin.configs['flat/recommended-error'].rules,
|
|
25
|
+
...eslintMochaPlugin.configs.flat.recommended.rules,
|
|
26
|
+
...eslintChaiExpectPlugin.configs['recommended-flat'].rules,
|
|
27
|
+
'no-unused-vars': ['error', {'caughtErrors': 'none'}],
|
|
28
|
+
'jsdoc/require-param-description': 0,
|
|
29
|
+
'jsdoc/require-returns-description': 0,
|
|
30
|
+
'jsdoc/require-property-description': 0,
|
|
31
|
+
'jsdoc/tag-lines': ['error', 'any', {startLines: 1}],
|
|
32
|
+
},
|
|
33
|
+
files: ['src/**/*.js'],
|
|
34
|
+
}];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import {TrieRouter} from '../src/index.js';
|
|
3
|
+
import {HTTP_METHOD} from '../src/route.js';
|
|
4
|
+
|
|
5
|
+
const router = new TrieRouter();
|
|
6
|
+
|
|
7
|
+
// регистрация роута для вывода
|
|
8
|
+
// переданных Cookie
|
|
9
|
+
router.defineRoute({
|
|
10
|
+
method: HTTP_METHOD.GET,
|
|
11
|
+
path: '/showCookie',
|
|
12
|
+
handler: ({cookie}) => cookie,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// создаем экземпляр HTTP сервера
|
|
16
|
+
// и подключаем обработчик запросов
|
|
17
|
+
const server = new http.Server();
|
|
18
|
+
server.on('request', router.requestHandler);
|
|
19
|
+
|
|
20
|
+
// слушаем входящие запросы
|
|
21
|
+
// на указанный адрес и порт
|
|
22
|
+
const port = 3000;
|
|
23
|
+
const host = '0.0.0.0';
|
|
24
|
+
server.listen(port, host, function () {
|
|
25
|
+
const cyan = '\x1b[36m%s\x1b[0m';
|
|
26
|
+
console.log(cyan, 'Server listening on port:', port);
|
|
27
|
+
console.log(cyan, 'Open in browser:', `http://${host}:${port}/showCookie`);
|
|
28
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import {TrieRouter} from '../src/index.js';
|
|
3
|
+
import {HTTP_METHOD} from '../src/route.js';
|
|
4
|
+
|
|
5
|
+
const router = new TrieRouter();
|
|
6
|
+
|
|
7
|
+
// регистрация роута для вывода
|
|
8
|
+
// переданных параметров пути
|
|
9
|
+
router.defineRoute({
|
|
10
|
+
method: HTTP_METHOD.GET,
|
|
11
|
+
path: '/showParams/:p1/:p2',
|
|
12
|
+
handler: ({params}) => params,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// создаем экземпляр HTTP сервера
|
|
16
|
+
// и подключаем обработчик запросов
|
|
17
|
+
const server = new http.Server();
|
|
18
|
+
server.on('request', router.requestHandler);
|
|
19
|
+
|
|
20
|
+
// слушаем входящие запросы
|
|
21
|
+
// на указанный адрес и порт
|
|
22
|
+
const port = 3000;
|
|
23
|
+
const host = '0.0.0.0';
|
|
24
|
+
server.listen(port, host, function () {
|
|
25
|
+
const cyan = '\x1b[36m%s\x1b[0m';
|
|
26
|
+
console.log(cyan, 'Server listening on port:', port);
|
|
27
|
+
console.log(cyan, 'Open in browser:', `http://${host}:${port}/showParams/foo/bar`);
|
|
28
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import {TrieRouter} from '../src/index.js';
|
|
3
|
+
import {HTTP_METHOD} from '../src/route.js';
|
|
4
|
+
|
|
5
|
+
const router = new TrieRouter();
|
|
6
|
+
|
|
7
|
+
// регистрация роута для вывода
|
|
8
|
+
// переданных "query" параметров
|
|
9
|
+
router.defineRoute({
|
|
10
|
+
method: HTTP_METHOD.GET,
|
|
11
|
+
path: '/showQuery',
|
|
12
|
+
handler: ({query}) => query,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// создаем экземпляр HTTP сервера
|
|
16
|
+
// и подключаем обработчик запросов
|
|
17
|
+
const server = new http.Server();
|
|
18
|
+
server.on('request', router.requestHandler);
|
|
19
|
+
|
|
20
|
+
// слушаем входящие запросы
|
|
21
|
+
// на указанный адрес и порт
|
|
22
|
+
const port = 3000;
|
|
23
|
+
const host = '0.0.0.0';
|
|
24
|
+
server.listen(port, host, function () {
|
|
25
|
+
const cyan = '\x1b[36m%s\x1b[0m';
|
|
26
|
+
console.log(cyan, 'Server listening on port:', port);
|
|
27
|
+
console.log(cyan, 'Open in browser:', `http://${host}:${port}/showQuery?foo=bar&baz=qux`);
|
|
28
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import {TrieRouter} from '../src/index.js';
|
|
3
|
+
import {HTTP_METHOD} from '../src/route.js';
|
|
4
|
+
|
|
5
|
+
const router = new TrieRouter();
|
|
6
|
+
|
|
7
|
+
// регистрация роута для вывода
|
|
8
|
+
// времени работы сервера
|
|
9
|
+
router.defineRoute({
|
|
10
|
+
method: HTTP_METHOD.GET,
|
|
11
|
+
path: '/',
|
|
12
|
+
handler() {
|
|
13
|
+
const uptimeSec = process.uptime();
|
|
14
|
+
const days = Math.floor(uptimeSec / (60 * 60 * 24));
|
|
15
|
+
const hours = Math.floor((uptimeSec / (60 * 60)) % 24);
|
|
16
|
+
const mins = Math.floor((uptimeSec / 60) % 60);
|
|
17
|
+
const secs = Math.floor(uptimeSec % 60);
|
|
18
|
+
let res = 'Uptime';
|
|
19
|
+
if (days) res += ` ${days}d`;
|
|
20
|
+
if (days || hours) res += ` ${hours}h`;
|
|
21
|
+
if (days || hours || mins) res += ` ${mins}m`;
|
|
22
|
+
res += ` ${secs}s`;
|
|
23
|
+
return res;
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// создаем экземпляр HTTP сервера
|
|
28
|
+
// и подключаем обработчик запросов
|
|
29
|
+
const server = new http.Server();
|
|
30
|
+
server.on('request', router.requestHandler);
|
|
31
|
+
|
|
32
|
+
// слушаем входящие запросы
|
|
33
|
+
// на указанный адрес и порт
|
|
34
|
+
const port = 3000;
|
|
35
|
+
const host = '0.0.0.0';
|
|
36
|
+
server.listen(port, host, function () {
|
|
37
|
+
const cyan = '\x1b[36m%s\x1b[0m';
|
|
38
|
+
console.log(cyan, 'Server listening on port:', port);
|
|
39
|
+
console.log(cyan, 'Open in browser:', `http://${host}:${port}`);
|
|
40
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@e22m4u/js-trie-router",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Trie-based router for Node.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lint": "tsc && eslint .",
|
|
9
|
+
"lint:fix": "tsc && eslint . --fix",
|
|
10
|
+
"format": "prettier --write \"./src/**/*.{js,ts}\"",
|
|
11
|
+
"test": "npm run lint && c8 --reporter=text-summary mocha --bail",
|
|
12
|
+
"test:coverage": "npm run lint && c8 --reporter=text mocha",
|
|
13
|
+
"prepare": "husky"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/e22m4u/js-trie-router.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"router",
|
|
21
|
+
"trie",
|
|
22
|
+
"http",
|
|
23
|
+
"server",
|
|
24
|
+
"nodejs"
|
|
25
|
+
],
|
|
26
|
+
"author": "e22m4u <e22m4u@yandex.ru>",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"homepage": "https://github.com/e22m4u/js-trie-router",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@commitlint/cli": "~19.4.0",
|
|
31
|
+
"@commitlint/config-conventional": "~19.2.2",
|
|
32
|
+
"@eslint/js": "~9.9.0",
|
|
33
|
+
"@types/chai-as-promised": "^8.0.0",
|
|
34
|
+
"c8": "~10.1.2",
|
|
35
|
+
"chai": "~5.1.1",
|
|
36
|
+
"chai-as-promised": "^8.0.0",
|
|
37
|
+
"eslint": "~9.9.0",
|
|
38
|
+
"eslint-config-prettier": "~9.1.0",
|
|
39
|
+
"eslint-plugin-chai-expect": "~3.1.0",
|
|
40
|
+
"eslint-plugin-jsdoc": "^50.2.2",
|
|
41
|
+
"eslint-plugin-mocha": "~10.5.0",
|
|
42
|
+
"globals": "~15.9.0",
|
|
43
|
+
"husky": "~9.1.4",
|
|
44
|
+
"mocha": "~10.7.3",
|
|
45
|
+
"prettier": "~3.3.3",
|
|
46
|
+
"typescript": "~5.5.4"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@e22m4u/js-format": "^0.1.0",
|
|
50
|
+
"@e22m4u/js-path-trie": "^0.0.1",
|
|
51
|
+
"@e22m4u/js-service": "^0.0.12",
|
|
52
|
+
"debug": "^4.3.6",
|
|
53
|
+
"http-errors": "^2.0.0",
|
|
54
|
+
"path-to-regexp": "^7.1.0",
|
|
55
|
+
"statuses": "^2.0.1"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/chai.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {Route} from '../route.js';
|
|
2
|
+
import {ServerResponse} from 'http';
|
|
3
|
+
import {Service} from '../service.js';
|
|
4
|
+
import {ValueOrPromise} from '../types.js';
|
|
5
|
+
import {HOOK_NAME} from './hook-registry.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook invoker.
|
|
9
|
+
*/
|
|
10
|
+
export declare class HookInvoker extends Service {
|
|
11
|
+
/**
|
|
12
|
+
* Invoke and continue until value received.
|
|
13
|
+
*
|
|
14
|
+
* @param route
|
|
15
|
+
* @param hookName
|
|
16
|
+
* @param response
|
|
17
|
+
* @param args
|
|
18
|
+
*/
|
|
19
|
+
invokeAndContinueUntilValueReceived(
|
|
20
|
+
route: Route,
|
|
21
|
+
hookName: HOOK_NAME,
|
|
22
|
+
response: ServerResponse,
|
|
23
|
+
...args: unknown[]
|
|
24
|
+
): ValueOrPromise<unknown>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {Route} from '../route.js';
|
|
2
|
+
import {Service} from '../service.js';
|
|
3
|
+
import {Errorf} from '@e22m4u/js-format';
|
|
4
|
+
import {isPromise} from '../utils/index.js';
|
|
5
|
+
import {HOOK_NAME} from './hook-registry.js';
|
|
6
|
+
import {HookRegistry} from './hook-registry.js';
|
|
7
|
+
import {isResponseSent} from '../utils/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook invoker.
|
|
11
|
+
*/
|
|
12
|
+
export class HookInvoker extends Service {
|
|
13
|
+
/**
|
|
14
|
+
* Invoke and continue until value received.
|
|
15
|
+
*
|
|
16
|
+
* @param {Route} route
|
|
17
|
+
* @param {string} hookName
|
|
18
|
+
* @param {import('http').ServerResponse} response
|
|
19
|
+
* @param {*[]} args
|
|
20
|
+
* @returns {Promise<*>|*}
|
|
21
|
+
*/
|
|
22
|
+
invokeAndContinueUntilValueReceived(route, hookName, response, ...args) {
|
|
23
|
+
if (!route || !(route instanceof Route))
|
|
24
|
+
throw new Errorf(
|
|
25
|
+
'The parameter "route" of ' +
|
|
26
|
+
'the HookInvoker.invokeAndContinueUntilValueReceived ' +
|
|
27
|
+
'should be a Route instance, but %v given.',
|
|
28
|
+
route,
|
|
29
|
+
);
|
|
30
|
+
if (!hookName || typeof hookName !== 'string')
|
|
31
|
+
throw new Errorf(
|
|
32
|
+
'The parameter "hookName" of ' +
|
|
33
|
+
'the HookInvoker.invokeAndContinueUntilValueReceived ' +
|
|
34
|
+
'should be a non-empty String, but %v given.',
|
|
35
|
+
hookName,
|
|
36
|
+
);
|
|
37
|
+
if (!Object.values(HOOK_NAME).includes(hookName))
|
|
38
|
+
throw new Errorf('The hook name %v is not supported.', hookName);
|
|
39
|
+
if (
|
|
40
|
+
!response ||
|
|
41
|
+
typeof response !== 'object' ||
|
|
42
|
+
Array.isArray(response) ||
|
|
43
|
+
typeof response.headersSent !== 'boolean'
|
|
44
|
+
) {
|
|
45
|
+
throw new Errorf(
|
|
46
|
+
'The parameter "response" of ' +
|
|
47
|
+
'the HookInvoker.invokeAndContinueUntilValueReceived ' +
|
|
48
|
+
'should be a ServerResponse instance, but %v given.',
|
|
49
|
+
response,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
// так как хуки роута выполняются
|
|
53
|
+
// после глобальных, то объединяем
|
|
54
|
+
// их в данной последовательности
|
|
55
|
+
const hooks = [
|
|
56
|
+
...this.getService(HookRegistry).getHooks(hookName),
|
|
57
|
+
...route.hookRegistry.getHooks(hookName),
|
|
58
|
+
];
|
|
59
|
+
// последовательный вызов хуков будет прерван,
|
|
60
|
+
// если один из них вернет значение (или Promise)
|
|
61
|
+
// отличное от "undefined" и "null"
|
|
62
|
+
let result = undefined;
|
|
63
|
+
for (const hook of hooks) {
|
|
64
|
+
// если ответ уже был отправлен,
|
|
65
|
+
// то завершаем обход
|
|
66
|
+
if (isResponseSent(response)) {
|
|
67
|
+
result = response;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
// если выполняется первый хук, или предыдущий
|
|
71
|
+
// хук вернул пустое значение, то выполняем
|
|
72
|
+
// следующий, записывая возвращаемое
|
|
73
|
+
// значение в результат
|
|
74
|
+
if (result == null) {
|
|
75
|
+
result = hook(...args);
|
|
76
|
+
}
|
|
77
|
+
// если какой-то из предыдущих хуков вернул
|
|
78
|
+
// Promise, то последующие значения будут
|
|
79
|
+
// оборачиваться именно им
|
|
80
|
+
else if (isPromise(result)) {
|
|
81
|
+
result = result.then(prevVal => {
|
|
82
|
+
// если ответ уже был отправлен,
|
|
83
|
+
// то останавливаем выполнение
|
|
84
|
+
if (isResponseSent(response)) {
|
|
85
|
+
result = response;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// если предыдущий Promise вернул значение
|
|
89
|
+
// отличное от "undefined" и "null",
|
|
90
|
+
// то завершаем обход
|
|
91
|
+
if (prevVal != null) return prevVal;
|
|
92
|
+
return hook(...args);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// если предыдущий хук вернул значение
|
|
96
|
+
// отличное от "undefined" и "null",
|
|
97
|
+
// то завершаем обход
|
|
98
|
+
else {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
}
|