@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.
Files changed (112) hide show
  1. package/.c8rc +9 -0
  2. package/.commitlintrc +5 -0
  3. package/.editorconfig +13 -0
  4. package/.husky/commit-msg +1 -0
  5. package/.husky/pre-commit +5 -0
  6. package/.mocharc.cjs +4 -0
  7. package/.prettierrc +7 -0
  8. package/LICENSE +21 -0
  9. package/README.md +127 -0
  10. package/eslint.config.js +34 -0
  11. package/examples/cookie-parsing-example.js +28 -0
  12. package/examples/params-parsing-example.js +28 -0
  13. package/examples/query-parsing-example.js +28 -0
  14. package/examples/uptime-example.js +40 -0
  15. package/package.json +57 -0
  16. package/src/chai.js +7 -0
  17. package/src/hooks/hook-invoker.d.ts +25 -0
  18. package/src/hooks/hook-invoker.js +104 -0
  19. package/src/hooks/hook-invoker.spec.js +462 -0
  20. package/src/hooks/hook-registry.d.ts +43 -0
  21. package/src/hooks/hook-registry.js +88 -0
  22. package/src/hooks/hook-registry.spec.js +165 -0
  23. package/src/hooks/index.d.ts +2 -0
  24. package/src/hooks/index.js +2 -0
  25. package/src/index.d.ts +1 -0
  26. package/src/index.js +9 -0
  27. package/src/parsers/body-parser.d.ts +52 -0
  28. package/src/parsers/body-parser.js +161 -0
  29. package/src/parsers/body-parser.spec.js +297 -0
  30. package/src/parsers/cookie-parser.d.ts +21 -0
  31. package/src/parsers/cookie-parser.js +32 -0
  32. package/src/parsers/cookie-parser.spec.js +26 -0
  33. package/src/parsers/index.d.ts +4 -0
  34. package/src/parsers/index.js +4 -0
  35. package/src/parsers/query-parser.d.ts +21 -0
  36. package/src/parsers/query-parser.js +32 -0
  37. package/src/parsers/query-parser.spec.js +25 -0
  38. package/src/parsers/request-parser.d.ts +34 -0
  39. package/src/parsers/request-parser.js +65 -0
  40. package/src/parsers/request-parser.spec.js +137 -0
  41. package/src/request-context.d.ts +54 -0
  42. package/src/request-context.js +108 -0
  43. package/src/request-context.spec.js +89 -0
  44. package/src/route-registry.d.ts +39 -0
  45. package/src/route-registry.js +95 -0
  46. package/src/route-registry.spec.js +77 -0
  47. package/src/route.d.ts +82 -0
  48. package/src/route.js +184 -0
  49. package/src/route.spec.js +299 -0
  50. package/src/router-options.d.ts +18 -0
  51. package/src/router-options.js +41 -0
  52. package/src/router-options.spec.js +52 -0
  53. package/src/senders/data-sender.d.ts +15 -0
  54. package/src/senders/data-sender.js +72 -0
  55. package/src/senders/data-sender.spec.js +193 -0
  56. package/src/senders/error-sender.d.ts +25 -0
  57. package/src/senders/error-sender.js +85 -0
  58. package/src/senders/error-sender.spec.js +90 -0
  59. package/src/senders/index.d.ts +2 -0
  60. package/src/senders/index.js +2 -0
  61. package/src/service.d.ts +14 -0
  62. package/src/service.js +28 -0
  63. package/src/service.spec.js +11 -0
  64. package/src/trie-router.d.ts +66 -0
  65. package/src/trie-router.js +189 -0
  66. package/src/trie-router.spec.js +471 -0
  67. package/src/types.d.ts +19 -0
  68. package/src/utils/create-cookie-string.d.ts +6 -0
  69. package/src/utils/create-cookie-string.js +24 -0
  70. package/src/utils/create-cookie-string.spec.js +36 -0
  71. package/src/utils/create-debugger.d.ts +11 -0
  72. package/src/utils/create-debugger.js +22 -0
  73. package/src/utils/create-debugger.spec.js +30 -0
  74. package/src/utils/create-error.d.ts +14 -0
  75. package/src/utils/create-error.js +28 -0
  76. package/src/utils/create-error.spec.js +50 -0
  77. package/src/utils/create-request-mock.d.ts +28 -0
  78. package/src/utils/create-request-mock.js +345 -0
  79. package/src/utils/create-request-mock.spec.js +482 -0
  80. package/src/utils/create-response-mock.d.ts +16 -0
  81. package/src/utils/create-response-mock.js +119 -0
  82. package/src/utils/create-response-mock.spec.js +130 -0
  83. package/src/utils/fetch-request-body.d.ts +17 -0
  84. package/src/utils/fetch-request-body.js +133 -0
  85. package/src/utils/fetch-request-body.spec.js +211 -0
  86. package/src/utils/get-request-path.d.ts +8 -0
  87. package/src/utils/get-request-path.js +23 -0
  88. package/src/utils/get-request-path.spec.js +31 -0
  89. package/src/utils/index.d.ts +11 -0
  90. package/src/utils/index.js +11 -0
  91. package/src/utils/is-promise.d.ts +10 -0
  92. package/src/utils/is-promise.js +13 -0
  93. package/src/utils/is-promise.spec.js +20 -0
  94. package/src/utils/is-readable-stream.d.ts +9 -0
  95. package/src/utils/is-readable-stream.js +11 -0
  96. package/src/utils/is-readable-stream.spec.js +23 -0
  97. package/src/utils/is-response-sent.d.ts +8 -0
  98. package/src/utils/is-response-sent.js +23 -0
  99. package/src/utils/is-response-sent.spec.js +35 -0
  100. package/src/utils/is-writable-stream.d.ts +9 -0
  101. package/src/utils/is-writable-stream.js +11 -0
  102. package/src/utils/is-writable-stream.spec.js +23 -0
  103. package/src/utils/parse-content-type.d.ts +15 -0
  104. package/src/utils/parse-content-type.js +30 -0
  105. package/src/utils/parse-content-type.spec.js +62 -0
  106. package/src/utils/parse-cookie.d.ts +19 -0
  107. package/src/utils/parse-cookie.js +30 -0
  108. package/src/utils/parse-cookie.spec.js +37 -0
  109. package/src/utils/to-camel-case.d.ts +6 -0
  110. package/src/utils/to-camel-case.js +20 -0
  111. package/src/utils/to-camel-case.spec.js +32 -0
  112. package/tsconfig.json +11 -0
package/.c8rc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "all": true,
3
+ "include": [
4
+ "src/**/*.js"
5
+ ],
6
+ "exclude": [
7
+ "src/**/*.spec.js"
8
+ ]
9
+ }
package/.commitlintrc ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": [
3
+ "@commitlint/config-conventional"
4
+ ]
5
+ }
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
@@ -0,0 +1,5 @@
1
+ npm run lint:fix
2
+ npm run format
3
+ npm run test
4
+
5
+ git add -A
package/.mocharc.cjs ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ extension: ['js'],
3
+ spec: 'src/**/*.spec.js',
4
+ }
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "bracketSpacing": false,
3
+ "singleQuote": true,
4
+ "printWidth": 80,
5
+ "trailingComma": "all",
6
+ "arrowParens": "avoid"
7
+ }
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
@@ -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,7 @@
1
+ import * as chaiModule from 'chai';
2
+ import chaiAsPromised from 'chai-as-promised';
3
+ const chai = {...chaiModule};
4
+
5
+ chaiAsPromised(chai, chai.util);
6
+
7
+ export const expect = chai.expect;
@@ -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
+ }