@e22m4u/js-trie-router 0.3.6 → 0.3.7

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
@@ -101,16 +101,17 @@ router.defineRoute({
101
101
  handler(ctx) {
102
102
  // GET /users/10?include=city
103
103
  // Cookie: foo=bar; baz=qux;
104
- console.log(ctx.req); // IncomingMessage
105
- console.log(ctx.res); // ServerResponse
106
- console.log(ctx.params); // {id: 10}
107
- console.log(ctx.query); // {include: 'city'}
108
- console.log(ctx.headers); // {cookie: 'foo=bar; baz=qux;'}
109
- console.log(ctx.cookies); // {foo: 'bar', baz: 'qux'}
110
- console.log(ctx.method); // "GET"
111
- console.log(ctx.path); // "/users/10?include=city"
112
- console.log(ctx.pathname); // "/users/10"
113
- console.log(ctx.meta); // {prop: 'value'}
104
+ console.log(ctx.req); // IncomingMessage
105
+ console.log(ctx.res); // ServerResponse
106
+ console.log(ctx.params); // {id: 10}
107
+ console.log(ctx.query); // {include: 'city'}
108
+ console.log(ctx.headers); // {cookie: 'foo=bar; baz=qux;'}
109
+ console.log(ctx.cookies); // {foo: 'bar', baz: 'qux'}
110
+ console.log(ctx.method); // "GET"
111
+ console.log(ctx.path); // "/users/10?include=city"
112
+ console.log(ctx.pathname); // "/users/10"
113
+ console.log(ctx.meta); // {prop: 'value'}
114
+ console.log(ctx.container); // ServiceContainer
114
115
  // ...
115
116
  },
116
117
  });
@@ -76,6 +76,25 @@ var import_js_debug = require("@e22m4u/js-debug");
76
76
  // src/hooks/hook-invoker.js
77
77
  var import_js_format13 = require("@e22m4u/js-format");
78
78
 
79
+ // src/debuggable-service.js
80
+ var import_js_service = require("@e22m4u/js-service");
81
+ var MODULE_DEBUG_NAMESPACE = "jsTrieRouter";
82
+ var _DebuggableService = class _DebuggableService extends import_js_service.DebuggableService {
83
+ /**
84
+ * Constructor.
85
+ *
86
+ * @param {ServiceContainer} container
87
+ */
88
+ constructor(container = void 0) {
89
+ super(container, {
90
+ namespace: MODULE_DEBUG_NAMESPACE,
91
+ noEnvironmentNamespace: true
92
+ });
93
+ }
94
+ };
95
+ __name(_DebuggableService, "DebuggableService");
96
+ var DebuggableService = _DebuggableService;
97
+
79
98
  // src/utils/clone-deep.js
80
99
  function cloneDeep(value) {
81
100
  if (value == null || typeof value !== "object") {
@@ -739,25 +758,6 @@ var _HookRegistry = class _HookRegistry {
739
758
  __name(_HookRegistry, "HookRegistry");
740
759
  var HookRegistry = _HookRegistry;
741
760
 
742
- // src/debuggable-service.js
743
- var import_js_service = require("@e22m4u/js-service");
744
- var MODULE_DEBUG_NAMESPACE = "jsTrieRouter";
745
- var _DebuggableService = class _DebuggableService extends import_js_service.DebuggableService {
746
- /**
747
- * Constructor.
748
- *
749
- * @param {ServiceContainer} container
750
- */
751
- constructor(container = void 0) {
752
- super(container, {
753
- namespace: MODULE_DEBUG_NAMESPACE,
754
- noEnvironmentNamespace: true
755
- });
756
- }
757
- };
758
- __name(_DebuggableService, "DebuggableService");
759
- var DebuggableService = _DebuggableService;
760
-
761
761
  // src/hooks/hook-invoker.js
762
762
  var _HookInvoker = class _HookInvoker extends DebuggableService {
763
763
  /**
@@ -788,31 +788,46 @@ var _HookInvoker = class _HookInvoker extends DebuggableService {
788
788
  response
789
789
  );
790
790
  }
791
+ if (isResponseSent(response)) {
792
+ return response;
793
+ }
791
794
  const hooks = [
792
795
  ...this.getService(HookRegistry).getHooks(hookType),
793
796
  ...route.hookRegistry.getHooks(hookType)
794
797
  ];
795
798
  let result = void 0;
796
- for (const hook of hooks) {
799
+ for (let i = 0; i < hooks.length; i++) {
800
+ const hook = hooks[i];
801
+ result = hook(...args);
797
802
  if (isResponseSent(response)) {
798
- result = response;
799
- break;
803
+ return response;
800
804
  }
801
- if (result == null) {
802
- result = hook(...args);
803
- } else if (isPromise(result)) {
804
- result = result.then((prevVal) => {
805
- if (isResponseSent(response)) {
805
+ if (result != null) {
806
+ if (isPromise(result)) {
807
+ return (async () => {
808
+ let asyncResult = await result;
809
+ if (isResponseSent(response)) {
810
+ return response;
811
+ }
812
+ if (asyncResult != null) {
813
+ return asyncResult;
814
+ }
815
+ for (let j = i + 1; j < hooks.length; j++) {
816
+ asyncResult = await hooks[j](...args);
817
+ if (isResponseSent(response)) {
818
+ return response;
819
+ }
820
+ if (asyncResult != null) {
821
+ return asyncResult;
822
+ }
823
+ }
806
824
  return;
807
- }
808
- if (prevVal != null) return prevVal;
809
- return hook(...args);
810
- });
811
- } else {
812
- break;
825
+ })();
826
+ }
827
+ return result;
813
828
  }
814
829
  }
815
- return result;
830
+ return;
816
831
  }
817
832
  };
818
833
  __name(_HookInvoker, "HookInvoker");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-trie-router",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
5
5
  "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
6
6
  "license": "MIT",
@@ -58,7 +58,7 @@
58
58
  "eslint": "~9.39.1",
59
59
  "eslint-config-prettier": "~10.1.8",
60
60
  "eslint-plugin-chai-expect": "~3.1.0",
61
- "eslint-plugin-jsdoc": "~61.1.12",
61
+ "eslint-plugin-jsdoc": "~61.2.0",
62
62
  "eslint-plugin-mocha": "~11.2.0",
63
63
  "globals": "~16.5.0",
64
64
  "husky": "~9.1.7",
@@ -1,10 +1,8 @@
1
1
  import {Route} from '../route.js';
2
2
  import {Errorf} from '@e22m4u/js-format';
3
- import {isPromise} from '../utils/index.js';
4
- import {HookRegistry} from './hook-registry.js';
5
- import {isResponseSent} from '../utils/index.js';
6
- import {RouterHookType} from './hook-registry.js';
7
3
  import {DebuggableService} from '../debuggable-service.js';
4
+ import {isPromise, isResponseSent} from '../utils/index.js';
5
+ import {HookRegistry, RouterHookType} from './hook-registry.js';
8
6
 
9
7
  /**
10
8
  * Hook invoker.
@@ -49,6 +47,11 @@ export class HookInvoker extends DebuggableService {
49
47
  response,
50
48
  );
51
49
  }
50
+ // если ответ уже отправлен,
51
+ // то возвращается ServerResponse
52
+ if (isResponseSent(response)) {
53
+ return response;
54
+ }
52
55
  // так как хуки роута выполняются
53
56
  // после глобальных, то объединяем
54
57
  // их в данной последовательности
@@ -56,48 +59,72 @@ export class HookInvoker extends DebuggableService {
56
59
  ...this.getService(HookRegistry).getHooks(hookType),
57
60
  ...route.hookRegistry.getHooks(hookType),
58
61
  ];
59
- // последовательный вызов хуков будет прерван,
60
- // если один из них вернет значение (или Promise)
61
- // отличное от "undefined" и "null"
62
62
  let result = undefined;
63
- for (const hook of hooks) {
64
- // если ответ уже был отправлен,
65
- // то завершаем обход
63
+ // итерация по хукам выполняется по индексу,
64
+ // чтобы знать, с какого места продолжать
65
+ // в асинхронном режиме
66
+ for (let i = 0; i < hooks.length; i++) {
67
+ const hook = hooks[i];
68
+ // вызов хука выполняется
69
+ // в синхронном режиме
70
+ result = hook(...args);
71
+ // если ответ уже отправлен,
72
+ // то возвращается ServerResponse
66
73
  if (isResponseSent(response)) {
67
- result = response;
68
- break;
69
- }
70
- // если выполняется первый хук, или предыдущий
71
- // хук вернул пустое значение, то выполняем
72
- // следующий, записывая возвращаемое
73
- // значение в результат
74
- if (result == null) {
75
- result = hook(...args);
74
+ return response;
76
75
  }
77
- // если какой-то из предыдущих хуков вернул
78
- // Promise, то последующие значения будут
79
- // оборачиваться именно им
80
- else if (isPromise(result)) {
81
- result = result.then(prevVal => {
82
- // если ответ уже был отправлен,
83
- // то останавливаем выполнение
84
- if (isResponseSent(response)) {
76
+ // если синхронный вызов хука вернул значение отличное
77
+ // от undefined и null, то требуется проверить данное
78
+ // значение для коррекции режима вызова оставшихся хуков
79
+ if (result != null) {
80
+ // если синхронный вызов хука вернул Promise, то дальнейшее
81
+ // выполнение переключается в асинхронный режим, начиная
82
+ // с индекса следующего хука
83
+ if (isPromise(result)) {
84
+ return (async () => {
85
+ // ожидание Promise, который был получен
86
+ // на предыдущем шаге (в синхронном режиме)
87
+ let asyncResult = await result;
88
+ // если ответ уже отправлен,
89
+ // то возвращается ServerResponse
90
+ if (isResponseSent(response)) {
91
+ return response;
92
+ }
93
+ // если Promise разрешился значением отличным
94
+ // от undefined и null, то данное значение
95
+ // возвращается в качестве результата
96
+ if (asyncResult != null) {
97
+ return asyncResult;
98
+ }
99
+ // продолжение вызова хуков начиная
100
+ // со следующего индекса (асинхронно)
101
+ for (let j = i + 1; j < hooks.length; j++) {
102
+ // с этого момента все синхронные
103
+ // хуки выполняются как асинхронные
104
+ asyncResult = await hooks[j](...args);
105
+ // если ответ уже отправлен,
106
+ // то возвращается ServerResponse
107
+ if (isResponseSent(response)) {
108
+ return response;
109
+ }
110
+ // если хук вернул значение отличное
111
+ // от undefined и null, то данное значение
112
+ // возвращается в качестве результата
113
+ if (asyncResult != null) {
114
+ return asyncResult;
115
+ }
116
+ }
85
117
  return;
86
- }
87
- // если предыдущий Promise вернул значение
88
- // отличное от "undefined" и "null",
89
- // то завершаем обход
90
- if (prevVal != null) return prevVal;
91
- return hook(...args);
92
- });
93
- }
94
- // если предыдущий хук вернул значение
95
- // отличное от "undefined" и "null",
96
- // то завершаем обход
97
- else {
98
- break;
118
+ })();
119
+ }
120
+ // если синхронный хук вернул значение отличное
121
+ // от undefined и null, то данное значение
122
+ // возвращается в качестве результата
123
+ return result;
99
124
  }
100
125
  }
101
- return result;
126
+ // все хуки были синхронными
127
+ // и не вернули значения
128
+ return;
102
129
  }
103
130
  }
@@ -234,7 +234,32 @@ describe('HookInvoker', function () {
234
234
  ]);
235
235
  });
236
236
 
237
- it('stops global hooks invocation and returns the given response if it was sent', function () {
237
+ it('returns the given response and should not call hooks if the response is already sent', function () {
238
+ const s = new HookInvoker();
239
+ const res = createResponseMock();
240
+ res._headersSent = true;
241
+ s.getService(HookRegistry).addHook(RouterHookType.PRE_HANDLER, () => {
242
+ throw new Error('Should not be called');
243
+ });
244
+ const route = new Route({
245
+ method: HttpMethod.GET,
246
+ path: '/',
247
+ preHandler: [
248
+ () => {
249
+ throw new Error('Should not be called');
250
+ },
251
+ ],
252
+ handler: () => undefined,
253
+ });
254
+ const result = s.invokeAndContinueUntilValueReceived(
255
+ route,
256
+ RouterHookType.PRE_HANDLER,
257
+ res,
258
+ );
259
+ expect(result).to.be.eq(res);
260
+ });
261
+
262
+ it('stops global hooks invocation and returns the given response if it is already sent', function () {
238
263
  const s = new HookInvoker();
239
264
  const order = [];
240
265
  const res = createResponseMock();
@@ -270,7 +295,7 @@ describe('HookInvoker', function () {
270
295
  expect(order).to.be.eql(['globalHook1', 'globalHook2']);
271
296
  });
272
297
 
273
- it('stops route hooks invocation and returns the given response if it was sent', function () {
298
+ it('stops route hooks invocation and returns the given response if it is already sent', function () {
274
299
  const s = new HookInvoker();
275
300
  const order = [];
276
301
  const res = createResponseMock();
package/src/route.d.ts CHANGED
@@ -39,7 +39,9 @@ export type RoutePostHandler<T = unknown, U = unknown> = (
39
39
  /**
40
40
  * Route meta.
41
41
  */
42
- export type RouteMeta = Record<PropertyKey, any>;
42
+ export type RouteMeta = {
43
+ [key: string]: unknown;
44
+ };
43
45
 
44
46
  /**
45
47
  * Route definition.
package/src/route.spec.js CHANGED
@@ -370,14 +370,13 @@ describe('Route', function () {
370
370
 
371
371
  describe('handle', function () {
372
372
  it('invokes the handler with the given RequestContext and return its result', function () {
373
- const handler = ctx => {
374
- expect(ctx).to.be.instanceof(RequestContext);
375
- return 'OK';
376
- };
377
373
  const route = new Route({
378
374
  method: HttpMethod.GET,
379
375
  path: '/',
380
- handler,
376
+ handler(ctx) {
377
+ expect(ctx).to.be.instanceof(RequestContext);
378
+ return 'OK';
379
+ },
381
380
  });
382
381
  const req = createRequestMock();
383
382
  const res = createResponseMock();