@e22m4u/js-trie-router 0.7.7 → 0.7.9

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/README.md CHANGED
@@ -969,7 +969,7 @@ v1Branch.defineRoute({
969
969
  // GET /api/v1/status
970
970
  ```
971
971
 
972
- ## Обработка ошибок
972
+ ### Обработка ошибок
973
973
 
974
974
  Маршрутизатор автоматически перехватывает любые ошибки, выброшенные из хуков
975
975
  или обработчика маршрута. По умолчанию, любая ошибка приводит к ответу сервера
@@ -1035,6 +1035,42 @@ router.defineRoute({
1035
1035
  }
1036
1036
  ```
1037
1037
 
1038
+ #### Перехват и логирование ошибок
1039
+
1040
+ По умолчанию маршрутизатор не выводит информацию об ошибках в консоль,
1041
+ чтобы не нарушать формат логов приложения и не допускать утечки чувствительных
1042
+ данных (например, токенов из заголовков запроса).
1043
+
1044
+ Если требуется реализовать собственное логирование, то можно переопределить
1045
+ встроенный сервис `RouterErrorSender`. Для этого потребуется унаследовать
1046
+ класс данного сервиса и подменить стандартную реализацию в контейнере
1047
+ маршрутизатора.
1048
+
1049
+ ```js
1050
+ import {TrieRouter, RouterErrorSender} from '@e22m4u/js-trie-router';
1051
+
1052
+ // создание собственного сервиса обработки ошибок
1053
+ class CustomErrorSender extends RouterErrorSender {
1054
+ send(request, response, error) {
1055
+ // логирование ошибки удобным способом
1056
+ console.error(`[Error on ${request.method} ${request.url}]:`, error);
1057
+ // вызов родительского метода для стандартной
1058
+ // отправки JSON-ответа с ошибкой клиенту
1059
+ super.send(request, response, error);
1060
+ }
1061
+ }
1062
+
1063
+ const router = new TrieRouter();
1064
+ // подмена стандартного сервиса новым экземпляром,
1065
+ // передавая текущий контейнер в конструктор (обязательно)
1066
+ router.setService(RouterErrorSender, new CustomErrorSender(router.container));
1067
+ // ... регистрация маршрутов и запуск сервера
1068
+ ```
1069
+
1070
+ *При необходимости можно переопределить метод `send404(request, response)`,
1071
+ чтобы отслеживать запросы к несуществующим маршрутам или изменять формат
1072
+ ответа.*
1073
+
1038
1074
  ## Отладка
1039
1075
 
1040
1076
  Установка переменной `DEBUG` включает вывод логов.
@@ -98,7 +98,7 @@ var DebuggableService = class extends import_js_service.DebuggableService {
98
98
  constructor(container = void 0) {
99
99
  super(container, {
100
100
  namespace: MODULE_DEBUG_NAMESPACE,
101
- noEnvironmentNamespace: true
101
+ noGlobalNamespace: true
102
102
  });
103
103
  }
104
104
  };
@@ -1670,7 +1670,7 @@ var Route = class extends import_js_debug.Debuggable {
1670
1670
  constructor(routeDef) {
1671
1671
  super({
1672
1672
  namespace: MODULE_DEBUG_NAMESPACE,
1673
- noEnvironmentNamespace: true,
1673
+ noGlobalNamespace: true,
1674
1674
  noInstantiationMessage: true
1675
1675
  });
1676
1676
  validateRouteDefinition(routeDef);
@@ -1688,7 +1688,7 @@ var Route = class extends import_js_debug.Debuggable {
1688
1688
  this._hookRegistry.addHook(RouterHookType.POST_HANDLER, hook);
1689
1689
  });
1690
1690
  }
1691
- this.ctorDebug("Created a route %s %v.", this.method, this.path);
1691
+ this.ctorDebug("Route %s %v created.", this.method, this.path);
1692
1692
  }
1693
1693
  /**
1694
1694
  * Handle request.
@@ -1758,7 +1758,7 @@ var RouteRegistry = class extends DebuggableService {
1758
1758
  const route = new Route(routeDef);
1759
1759
  const triePath = `${route.method}/${route.path}`;
1760
1760
  this._trie.add(triePath, route);
1761
- debug("Registered a route %s %v.", route.method.toUpperCase(), route.path);
1761
+ debug("Route %s %v registered.", route.method.toUpperCase(), route.path);
1762
1762
  return route;
1763
1763
  }
1764
1764
  /**
@@ -2460,7 +2460,6 @@ var RouterDataSender = class extends DebuggableService {
2460
2460
  };
2461
2461
 
2462
2462
  // src/senders/router-error-sender.js
2463
- var import_util = require("util");
2464
2463
  var import_statuses = __toESM(require("statuses"), 1);
2465
2464
  var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
2466
2465
  var RouterErrorSender = class extends DebuggableService {
@@ -2497,24 +2496,6 @@ var RouterErrorSender = class extends DebuggableService {
2497
2496
  body.error[name] = safeError[name];
2498
2497
  }
2499
2498
  });
2500
- const requestData = {
2501
- url: request.url,
2502
- method: request.method,
2503
- headers: request.headers
2504
- };
2505
- const inspectOptions = {
2506
- showHidden: false,
2507
- depth: null,
2508
- colors: true,
2509
- compact: false
2510
- };
2511
- console.warn((0, import_util.inspect)(requestData, inspectOptions));
2512
- console.warn((0, import_util.inspect)(body, inspectOptions));
2513
- if (error.stack) {
2514
- console.log(error.stack);
2515
- } else {
2516
- console.error(error);
2517
- }
2518
2499
  response.statusCode = statusCode;
2519
2500
  response.setHeader("Content-Type", "application/json; charset=utf-8");
2520
2501
  response.end(JSON.stringify(body, null, 2), "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-trie-router",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
5
5
  "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
6
6
  "license": "MIT",
@@ -38,17 +38,17 @@
38
38
  "prepare": "husky"
39
39
  },
40
40
  "dependencies": {
41
- "@e22m4u/js-debug": "~0.4.1",
41
+ "@e22m4u/js-debug": "~0.5.0",
42
42
  "@e22m4u/js-format": "~0.4.0",
43
- "@e22m4u/js-path-trie": "~0.2.0",
44
- "@e22m4u/js-service": "~0.6.1",
43
+ "@e22m4u/js-path-trie": "~0.2.1",
44
+ "@e22m4u/js-service": "~0.6.2",
45
45
  "debug": "~4.4.3",
46
46
  "http-errors": "~2.0.1",
47
47
  "statuses": "~2.0.2"
48
48
  },
49
49
  "devDependencies": {
50
- "@commitlint/cli": "~20.4.3",
51
- "@commitlint/config-conventional": "~20.4.3",
50
+ "@commitlint/cli": "~20.4.4",
51
+ "@commitlint/config-conventional": "~20.4.4",
52
52
  "@eslint/js": "~9.39.2",
53
53
  "@types/chai": "~5.2.3",
54
54
  "@types/chai-as-promised": "~8.0.2",
@@ -56,12 +56,12 @@
56
56
  "c8": "~11.0.0",
57
57
  "chai": "~6.2.2",
58
58
  "chai-as-promised": "~8.0.2",
59
- "esbuild": "~0.27.3",
59
+ "esbuild": "~0.27.4",
60
60
  "eslint": "~9.39.2",
61
61
  "eslint-config-prettier": "~10.1.8",
62
- "eslint-plugin-chai-expect": "~3.1.0",
62
+ "eslint-plugin-chai-expect": "~4.0.0",
63
63
  "eslint-plugin-import": "~2.32.0",
64
- "eslint-plugin-jsdoc": "~62.7.1",
64
+ "eslint-plugin-jsdoc": "~62.8.0",
65
65
  "eslint-plugin-mocha": "~11.2.0",
66
66
  "globals": "~17.4.0",
67
67
  "husky": "~9.1.7",
@@ -21,7 +21,7 @@ export class DebuggableService extends BaseDebuggableService {
21
21
  constructor(container = undefined) {
22
22
  super(container, {
23
23
  namespace: MODULE_DEBUG_NAMESPACE,
24
- noEnvironmentNamespace: true,
24
+ noGlobalNamespace: true,
25
25
  });
26
26
  }
27
27
  }
@@ -1,7 +1,8 @@
1
- import {Callable} from '../types.js';
2
1
  import {RouteDefinition} from '../route/index.js';
3
2
  import {ServiceContainer} from '@e22m4u/js-service';
4
3
  import {RequestContext} from '../request-context.js';
4
+ import {Callable, ValueOrPromise} from '../types.js';
5
+ import {IncomingMessage, ServerResponse} from 'http';
5
6
  import {DebuggableService} from '../debuggable-service.js';
6
7
 
7
8
  /**
@@ -30,6 +31,23 @@ export const ROUTER_HOOK_TYPES: RouterHookType[];
30
31
  */
31
32
  export type RouterHook = Callable;
32
33
 
34
+ /**
35
+ * On defined route hook.
36
+ */
37
+ export type OnDefineRouteHook = (
38
+ routeDef: RouteDefinition,
39
+ container: ServiceContainer,
40
+ ) => RouteDefinition | undefined;
41
+
42
+ /**
43
+ * On request hook.
44
+ */
45
+ export type OnRequestHook = (
46
+ request: IncomingMessage,
47
+ response: ServerResponse,
48
+ container: ServiceContainer,
49
+ ) => ValueOrPromise<boolean | undefined>;
50
+
33
51
  /**
34
52
  * Pre handler hook.
35
53
  */
@@ -40,18 +58,48 @@ export type PreHandlerHook = (ctx: RequestContext) => unknown;
40
58
  */
41
59
  export type PostHandlerHook = (ctx: RequestContext, data: unknown) => unknown;
42
60
 
43
- /**
44
- * On defined route hook.
45
- */
46
- export type OnDefineRouteHook = (
47
- routeDef: RouteDefinition,
48
- container: ServiceContainer,
49
- ) => RouteDefinition | undefined;
50
-
51
61
  /**
52
62
  * Router hook registry.
53
63
  */
54
64
  export declare class RouterHookRegistry extends DebuggableService {
65
+ /**
66
+ * Add hook (overload for "onDefineRoute" hook).
67
+ *
68
+ * @param type
69
+ * @param hook
70
+ */
71
+ addHook(
72
+ type: typeof RouterHookType.ON_DEFINE_ROUTE,
73
+ hook: OnDefineRouteHook,
74
+ ): this;
75
+
76
+ /**
77
+ * Add hook (overload for "onRequest" hook).
78
+ *
79
+ * @param type
80
+ * @param hook
81
+ */
82
+ addHook(type: typeof RouterHookType.ON_REQUEST, hook: OnRequestHook): this;
83
+
84
+ /**
85
+ * Add hook (overload for "preHandler" hook).
86
+ *
87
+ * @param type
88
+ * @param hook
89
+ */
90
+ addHook(type: typeof RouterHookType.PRE_HANDLER, hook: PreHandlerHook): this;
91
+
92
+ /**
93
+ * Add hook (overload for "postHandler" hook).
94
+ *
95
+ * @param type
96
+ * @param hook
97
+ */
98
+ addHook(
99
+ type: typeof RouterHookType.POST_HANDLER,
100
+ hook: PostHandlerHook,
101
+ ): this;
102
+
55
103
  /**
56
104
  * Add hook.
57
105
  *
@@ -68,6 +116,34 @@ export declare class RouterHookRegistry extends DebuggableService {
68
116
  */
69
117
  hasHook(type: RouterHookType, hook: RouterHook): boolean;
70
118
 
119
+ /**
120
+ * Get hooks (overload for "onDefineRoute").
121
+ *
122
+ * @param type
123
+ */
124
+ getHooks(type: typeof RouterHookType.ON_DEFINE_ROUTE): OnDefineRouteHook[];
125
+
126
+ /**
127
+ * Get hooks (overload for "onRequest").
128
+ *
129
+ * @param type
130
+ */
131
+ getHooks(type: typeof RouterHookType.ON_REQUEST): OnRequestHook[];
132
+
133
+ /**
134
+ * Get hooks (overload for "preHandler").
135
+ *
136
+ * @param type
137
+ */
138
+ getHooks(type: typeof RouterHookType.PRE_HANDLER): PreHandlerHook[];
139
+
140
+ /**
141
+ * Get hooks (overload for "postHandler").
142
+ *
143
+ * @param type
144
+ */
145
+ getHooks(type: typeof RouterHookType.POST_HANDLER): PostHandlerHook[];
146
+
71
147
  /**
72
148
  * Get hooks.
73
149
  *
@@ -78,7 +78,7 @@ export class RouteRegistry extends DebuggableService {
78
78
  const route = new Route(routeDef);
79
79
  const triePath = `${route.method}/${route.path}`;
80
80
  this._trie.add(triePath, route);
81
- debug('Registered a route %s %v.', route.method.toUpperCase(), route.path);
81
+ debug('Route %s %v registered.', route.method.toUpperCase(), route.path);
82
82
  return route;
83
83
  }
84
84
 
@@ -127,7 +127,7 @@ export class Route extends Debuggable {
127
127
  constructor(routeDef) {
128
128
  super({
129
129
  namespace: MODULE_DEBUG_NAMESPACE,
130
- noEnvironmentNamespace: true,
130
+ noGlobalNamespace: true,
131
131
  noInstantiationMessage: true,
132
132
  });
133
133
  validateRouteDefinition(routeDef);
@@ -151,7 +151,7 @@ export class Route extends Debuggable {
151
151
  this._hookRegistry.addHook(RouterHookType.POST_HANDLER, hook);
152
152
  });
153
153
  }
154
- this.ctorDebug('Created a route %s %v.', this.method, this.path);
154
+ this.ctorDebug('Route %s %v created.', this.method, this.path);
155
155
  }
156
156
 
157
157
  /**
@@ -1,4 +1,3 @@
1
- import {inspect} from 'util';
2
1
  import getStatusMessage from 'statuses';
3
2
  import {getRequestPathname} from '../utils/index.js';
4
3
  import {DebuggableService} from '../debuggable-service.js';
@@ -44,24 +43,6 @@ export class RouterErrorSender extends DebuggableService {
44
43
  body.error[name] = safeError[name];
45
44
  }
46
45
  });
47
- const requestData = {
48
- url: request.url,
49
- method: request.method,
50
- headers: request.headers,
51
- };
52
- const inspectOptions = {
53
- showHidden: false,
54
- depth: null,
55
- colors: true,
56
- compact: false,
57
- };
58
- console.warn(inspect(requestData, inspectOptions));
59
- console.warn(inspect(body, inspectOptions));
60
- if (error.stack) {
61
- console.log(error.stack);
62
- } else {
63
- console.error(error);
64
- }
65
46
  response.statusCode = statusCode;
66
47
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
67
48
  response.end(JSON.stringify(body, null, 2), 'utf-8');
@@ -3,10 +3,18 @@ import {RouteDefinition} from './route/index.js';
3
3
  import {ServiceContainer} from '@e22m4u/js-service';
4
4
  import {IncomingMessage, ServerResponse} from 'http';
5
5
  import {DebuggableService} from './debuggable-service.js';
6
- import {RouterHook, RouterHookType} from './hooks/index.js';
7
6
  import {TrieRouterOptionsInput} from './trie-router-options.js';
8
7
  import {RouterBranch, RouterBranchDefinition} from './branch/index.js';
9
8
 
9
+ import {
10
+ RouterHook,
11
+ OnRequestHook,
12
+ RouterHookType,
13
+ PreHandlerHook,
14
+ PostHandlerHook,
15
+ OnDefineRouteHook,
16
+ } from './hooks/index.js';
17
+
10
18
  /**
11
19
  * Trie router.
12
20
  */
@@ -105,6 +113,44 @@ export declare class TrieRouter extends DebuggableService {
105
113
  response: ServerResponse,
106
114
  ): Promise<void>;
107
115
 
116
+ /**
117
+ * Add hook (overload for "onDefineRoute" hook).
118
+ *
119
+ * @param type
120
+ * @param hook
121
+ */
122
+ addHook(
123
+ type: typeof RouterHookType.ON_DEFINE_ROUTE,
124
+ hook: OnDefineRouteHook,
125
+ ): this;
126
+
127
+ /**
128
+ * Add hook (overload for "onRequest" hook).
129
+ *
130
+ * @param type
131
+ * @param hook
132
+ */
133
+ addHook(type: typeof RouterHookType.ON_REQUEST, hook: OnRequestHook): this;
134
+
135
+ /**
136
+ * Add hook (overload for "preHandler" hook).
137
+ *
138
+ * @param type
139
+ * @param hook
140
+ */
141
+ addHook(type: typeof RouterHookType.PRE_HANDLER, hook: PreHandlerHook): this;
142
+
143
+ /**
144
+ * Add hook (overload for "postHandler" hook).
145
+ *
146
+ * @param type
147
+ * @param hook
148
+ */
149
+ addHook(
150
+ type: typeof RouterHookType.POST_HANDLER,
151
+ hook: PostHandlerHook,
152
+ ): this;
153
+
108
154
  /**
109
155
  * Add hook.
110
156
  *
@@ -7,7 +7,7 @@ import {createRequestMock} from './create-request-mock.js';
7
7
  import {CHARACTER_ENCODING_LIST} from './fetch-request-body.js';
8
8
 
9
9
  describe('createRequestMock', function () {
10
- it('should require the option "options" to be an Object', function () {
10
+ it('should require the parameter "options" to be an Object', function () {
11
11
  const throwable = v => () => createRequestMock(v);
12
12
  const error = v =>
13
13
  format('Parameter "options" must be an Object, but %s was given.', v);
@@ -496,24 +496,24 @@ describe('createRequestMock', function () {
496
496
  expect(data).to.be.eql(body);
497
497
  });
498
498
 
499
- it('should pass a value form the "url" option to the request url', function () {
499
+ it('should pass a value from the "url" option to the request url', function () {
500
500
  const req = createRequestMock({url: '/test'});
501
501
  expect(req.url).to.be.eq('/test');
502
502
  });
503
503
 
504
- it('should pass a value form the "path" option to the request url', function () {
504
+ it('should pass a value from the "path" option to the request url', function () {
505
505
  const req = createRequestMock({path: '/test'});
506
506
  expect(req.url).to.be.eq('/test');
507
507
  });
508
508
 
509
- it('should pass a string form the "query" option to the request url', async function () {
509
+ it('should pass a string from the "query" option to the request url', async function () {
510
510
  const req1 = createRequestMock({query: 'p1=foo&p2=bar'});
511
511
  const req2 = createRequestMock({query: '?p1=foo&p2=bar'});
512
512
  expect(req1.url).to.be.eq('/?p1=foo&p2=bar');
513
513
  expect(req2.url).to.be.eq('/?p1=foo&p2=bar');
514
514
  });
515
515
 
516
- it('should pass an object form the "query" option to the request url', async function () {
516
+ it('should pass an object from the "query" option to the request url', async function () {
517
517
  const req = createRequestMock({query: {foo: 'bar', baz: 'qux'}});
518
518
  expect(req.url).to.be.eq('/?foo=bar&baz=qux');
519
519
  });
@@ -139,7 +139,7 @@ describe('createResponseMock', function () {
139
139
  });
140
140
 
141
141
  describe('Stream', function () {
142
- it('should set the property "headerSent" to true when the stream ends', function () {
142
+ it('should set the property "headersSent" to true when the stream ends', function () {
143
143
  const res = createResponseMock();
144
144
  expect(res.headersSent).to.be.false;
145
145
  res.end('test');
@@ -23,4 +23,4 @@ export const CHARACTER_ENCODING_LIST: (
23
23
  export declare function fetchRequestBody(
24
24
  request: IncomingMessage,
25
25
  bodyBytesLimit?: number,
26
- ): Promise<string>;
26
+ ): Promise<string | undefined>;
@@ -123,7 +123,7 @@ describe('fetchRequestBody', function () {
123
123
  );
124
124
  });
125
125
 
126
- it('should not throw an error if the body length does match with the header', async function () {
126
+ it('should not throw an error if the body length matches the header', async function () {
127
127
  const body = 'Lorem Ipsum is simply dummy text.';
128
128
  const contentLength = String(Buffer.from(body).byteLength);
129
129
  const req = createRequestMock({