@e22m4u/js-trie-router 0.7.6 → 0.7.8

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
@@ -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` включает вывод логов.
package/build-cjs.js CHANGED
@@ -6,7 +6,7 @@ await esbuild.build({
6
6
  outfile: 'dist/cjs/index.cjs',
7
7
  format: 'cjs',
8
8
  platform: 'node',
9
- target: ['node16'],
9
+ target: ['node18'],
10
10
  bundle: true,
11
11
  keepNames: true,
12
12
  external: [
@@ -86,7 +86,10 @@ var import_js_debug = require("@e22m4u/js-debug");
86
86
  // src/debuggable-service.js
87
87
  var import_js_service = require("@e22m4u/js-service");
88
88
  var MODULE_DEBUG_NAMESPACE = "jsTrieRouter";
89
- var _DebuggableService = class _DebuggableService extends import_js_service.DebuggableService {
89
+ var DebuggableService = class extends import_js_service.DebuggableService {
90
+ static {
91
+ __name(this, "DebuggableService");
92
+ }
90
93
  /**
91
94
  * Constructor.
92
95
  *
@@ -99,8 +102,6 @@ var _DebuggableService = class _DebuggableService extends import_js_service.Debu
99
102
  });
100
103
  }
101
104
  };
102
- __name(_DebuggableService, "DebuggableService");
103
- var DebuggableService = _DebuggableService;
104
105
 
105
106
  // src/utils/clone-deep.js
106
107
  function cloneDeep(value) {
@@ -953,7 +954,10 @@ __name(getRequestPathname, "getRequestPathname");
953
954
  // src/request-context.js
954
955
  var import_js_format11 = require("@e22m4u/js-format");
955
956
  var import_js_service2 = require("@e22m4u/js-service");
956
- var _RequestContext = class _RequestContext {
957
+ var RequestContext = class {
958
+ static {
959
+ __name(this, "RequestContext");
960
+ }
957
961
  /**
958
962
  * Service container.
959
963
  *
@@ -1139,8 +1143,6 @@ var _RequestContext = class _RequestContext {
1139
1143
  this._route = route;
1140
1144
  }
1141
1145
  };
1142
- __name(_RequestContext, "RequestContext");
1143
- var RequestContext = _RequestContext;
1144
1146
 
1145
1147
  // src/hooks/router-hook-invoker.js
1146
1148
  var import_js_format13 = require("@e22m4u/js-format");
@@ -1154,7 +1156,10 @@ var RouterHookType = {
1154
1156
  POST_HANDLER: "postHandler"
1155
1157
  };
1156
1158
  var ROUTER_HOOK_TYPES = Object.values(RouterHookType);
1157
- var _RouterHookRegistry = class _RouterHookRegistry {
1159
+ var RouterHookRegistry = class {
1160
+ static {
1161
+ __name(this, "RouterHookRegistry");
1162
+ }
1158
1163
  /**
1159
1164
  * Hooks.
1160
1165
  *
@@ -1237,11 +1242,12 @@ var _RouterHookRegistry = class _RouterHookRegistry {
1237
1242
  return this._hooks.get(type) || [];
1238
1243
  }
1239
1244
  };
1240
- __name(_RouterHookRegistry, "RouterHookRegistry");
1241
- var RouterHookRegistry = _RouterHookRegistry;
1242
1245
 
1243
1246
  // src/hooks/router-hook-invoker.js
1244
- var _RouterHookInvoker = class _RouterHookInvoker extends DebuggableService {
1247
+ var RouterHookInvoker = class extends DebuggableService {
1248
+ static {
1249
+ __name(this, "RouterHookInvoker");
1250
+ }
1245
1251
  /**
1246
1252
  * Invoke on-request hooks.
1247
1253
  *
@@ -1502,8 +1508,6 @@ var _RouterHookInvoker = class _RouterHookInvoker extends DebuggableService {
1502
1508
  return currentData;
1503
1509
  }
1504
1510
  };
1505
- __name(_RouterHookInvoker, "RouterHookInvoker");
1506
- var RouterHookInvoker = _RouterHookInvoker;
1507
1511
 
1508
1512
  // src/route/validate-route-definition.js
1509
1513
  var import_js_format14 = require("@e22m4u/js-format");
@@ -1586,6 +1590,7 @@ __name(validateRouteDefinition, "validateRouteDefinition");
1586
1590
  // src/route/route.js
1587
1591
  var HttpMethod = {
1588
1592
  GET: "GET",
1593
+ HEAD: "HEAD",
1589
1594
  POST: "POST",
1590
1595
  PUT: "PUT",
1591
1596
  PATCH: "PATCH",
@@ -1593,7 +1598,10 @@ var HttpMethod = {
1593
1598
  OPTIONS: "OPTIONS"
1594
1599
  };
1595
1600
  var DEFAULT_META = Object.freeze({});
1596
- var _Route = class _Route extends import_js_debug.Debuggable {
1601
+ var Route = class extends import_js_debug.Debuggable {
1602
+ static {
1603
+ __name(this, "Route");
1604
+ }
1597
1605
  /**
1598
1606
  * Definition.
1599
1607
  *
@@ -1695,14 +1703,15 @@ var _Route = class _Route extends import_js_debug.Debuggable {
1695
1703
  return this.handler(context);
1696
1704
  }
1697
1705
  };
1698
- __name(_Route, "Route");
1699
- var Route = _Route;
1700
1706
 
1701
1707
  // src/route/route-registry.js
1702
1708
  var import_js_path_trie = require("@e22m4u/js-path-trie");
1703
1709
  var import_js_service3 = require("@e22m4u/js-service");
1704
1710
  var import_js_format15 = require("@e22m4u/js-format");
1705
- var _RouteRegistry = class _RouteRegistry extends DebuggableService {
1711
+ var RouteRegistry = class extends DebuggableService {
1712
+ static {
1713
+ __name(this, "RouteRegistry");
1714
+ }
1706
1715
  /**
1707
1716
  * Constructor.
1708
1717
  *
@@ -1792,38 +1801,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
1792
1801
  requestPath
1793
1802
  );
1794
1803
  }
1795
- /**
1796
- * Get allowed methods for request path.
1797
- *
1798
- * @param {string} requestPath
1799
- * @returns {string[]}
1800
- */
1801
- getAllowedMethodsForRequestPath(requestPath) {
1802
- if (typeof requestPath !== "string") {
1803
- throw new import_js_format15.InvalidArgumentError(
1804
- 'Parameter "requestPath" must be a String, but %v was given.',
1805
- requestPath
1806
- );
1807
- }
1808
- const debug = this.getDebuggerFor(this.getAllowedMethodsForRequestPath);
1809
- const allowedMethods = [];
1810
- for (const method of Object.values(HttpMethod)) {
1811
- const rawTriePath = `${method}/${requestPath}`;
1812
- const triePath = rawTriePath.replace(/\/+/g, "/");
1813
- if (this._trie.match(triePath)) {
1814
- allowedMethods.push(method);
1815
- }
1816
- }
1817
- if (allowedMethods.length) {
1818
- debug("Allowed methods for %v are: %l.", requestPath, allowedMethods);
1819
- } else {
1820
- debug("Path %v does not have allowed methods.", requestPath);
1821
- }
1822
- return allowedMethods;
1823
- }
1824
1804
  };
1825
- __name(_RouteRegistry, "RouteRegistry");
1826
- var RouteRegistry = _RouteRegistry;
1827
1805
 
1828
1806
  // src/branch/router-branch.js
1829
1807
  var import_js_format17 = require("@e22m4u/js-format");
@@ -1934,7 +1912,10 @@ function mergeRouterBranchDefinitions(firstDef, secondDef) {
1934
1912
  __name(mergeRouterBranchDefinitions, "mergeRouterBranchDefinitions");
1935
1913
 
1936
1914
  // src/branch/router-branch.js
1937
- var _RouterBranch = class _RouterBranch extends DebuggableService {
1915
+ var RouterBranch = class _RouterBranch extends DebuggableService {
1916
+ static {
1917
+ __name(this, "RouterBranch");
1918
+ }
1938
1919
  /**
1939
1920
  * Router.
1940
1921
  *
@@ -2053,8 +2034,6 @@ var _RouterBranch = class _RouterBranch extends DebuggableService {
2053
2034
  return new _RouterBranch(this._router, branchDef, this);
2054
2035
  }
2055
2036
  };
2056
- __name(_RouterBranch, "RouterBranch");
2057
- var RouterBranch = _RouterBranch;
2058
2037
 
2059
2038
  // src/parsers/request-parser.js
2060
2039
  var import_http3 = require("http");
@@ -2066,7 +2045,10 @@ var import_js_format19 = require("@e22m4u/js-format");
2066
2045
 
2067
2046
  // src/trie-router-options.js
2068
2047
  var import_js_format18 = require("@e22m4u/js-format");
2069
- var _TrieRouterOptions = class _TrieRouterOptions {
2048
+ var TrieRouterOptions = class {
2049
+ static {
2050
+ __name(this, "TrieRouterOptions");
2051
+ }
2070
2052
  /**
2071
2053
  * Request body bytes limit.
2072
2054
  *
@@ -2141,11 +2123,12 @@ var _TrieRouterOptions = class _TrieRouterOptions {
2141
2123
  }
2142
2124
  }
2143
2125
  };
2144
- __name(_TrieRouterOptions, "TrieRouterOptions");
2145
- var TrieRouterOptions = _TrieRouterOptions;
2146
2126
 
2147
2127
  // src/parsers/request-body-parser.js
2148
- var _RequestBodyParser = class _RequestBodyParser extends DebuggableService {
2128
+ var RequestBodyParser = class extends DebuggableService {
2129
+ static {
2130
+ __name(this, "RequestBodyParser");
2131
+ }
2149
2132
  /**
2150
2133
  * Parsers.
2151
2134
  *
@@ -2288,8 +2271,6 @@ var _RequestBodyParser = class _RequestBodyParser extends DebuggableService {
2288
2271
  });
2289
2272
  }
2290
2273
  };
2291
- __name(_RequestBodyParser, "RequestBodyParser");
2292
- var RequestBodyParser = _RequestBodyParser;
2293
2274
  function parseJsonBody(input) {
2294
2275
  if (typeof input !== "string") {
2295
2276
  return void 0;
@@ -2304,7 +2285,10 @@ __name(parseJsonBody, "parseJsonBody");
2304
2285
 
2305
2286
  // src/parsers/request-query-parser.js
2306
2287
  var import_querystring2 = __toESM(require("querystring"), 1);
2307
- var _RequestQueryParser = class _RequestQueryParser extends DebuggableService {
2288
+ var RequestQueryParser = class extends DebuggableService {
2289
+ static {
2290
+ __name(this, "RequestQueryParser");
2291
+ }
2308
2292
  /**
2309
2293
  * Parse
2310
2294
  *
@@ -2330,11 +2314,12 @@ var _RequestQueryParser = class _RequestQueryParser extends DebuggableService {
2330
2314
  return query;
2331
2315
  }
2332
2316
  };
2333
- __name(_RequestQueryParser, "RequestQueryParser");
2334
- var RequestQueryParser = _RequestQueryParser;
2335
2317
 
2336
2318
  // src/parsers/request-cookies-parser.js
2337
- var _RequestCookiesParser = class _RequestCookiesParser extends DebuggableService {
2319
+ var RequestCookiesParser = class extends DebuggableService {
2320
+ static {
2321
+ __name(this, "RequestCookiesParser");
2322
+ }
2338
2323
  /**
2339
2324
  * Parse
2340
2325
  *
@@ -2360,11 +2345,12 @@ var _RequestCookiesParser = class _RequestCookiesParser extends DebuggableServic
2360
2345
  return cookies;
2361
2346
  }
2362
2347
  };
2363
- __name(_RequestCookiesParser, "RequestCookiesParser");
2364
- var RequestCookiesParser = _RequestCookiesParser;
2365
2348
 
2366
2349
  // src/parsers/request-parser.js
2367
- var _RequestParser = class _RequestParser extends DebuggableService {
2350
+ var RequestParser = class extends DebuggableService {
2351
+ static {
2352
+ __name(this, "RequestParser");
2353
+ }
2368
2354
  /**
2369
2355
  * Parse.
2370
2356
  *
@@ -2402,15 +2388,16 @@ var _RequestParser = class _RequestParser extends DebuggableService {
2402
2388
  return promises.length ? Promise.all(promises).then(() => data) : data;
2403
2389
  }
2404
2390
  };
2405
- __name(_RequestParser, "RequestParser");
2406
- var RequestParser = _RequestParser;
2407
2391
 
2408
2392
  // src/trie-router.js
2409
2393
  var import_http4 = require("http");
2410
2394
 
2411
2395
  // src/senders/router-data-sender.js
2412
2396
  var import_js_format21 = require("@e22m4u/js-format");
2413
- var _RouterDataSender = class _RouterDataSender extends DebuggableService {
2397
+ var RouterDataSender = class extends DebuggableService {
2398
+ static {
2399
+ __name(this, "RouterDataSender");
2400
+ }
2414
2401
  /**
2415
2402
  * Send.
2416
2403
  *
@@ -2471,14 +2458,14 @@ var _RouterDataSender = class _RouterDataSender extends DebuggableService {
2471
2458
  debug(debugMsg);
2472
2459
  }
2473
2460
  };
2474
- __name(_RouterDataSender, "RouterDataSender");
2475
- var RouterDataSender = _RouterDataSender;
2476
2461
 
2477
2462
  // src/senders/router-error-sender.js
2478
- var import_util = require("util");
2479
2463
  var import_statuses = __toESM(require("statuses"), 1);
2480
2464
  var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
2481
- var _RouterErrorSender = class _RouterErrorSender extends DebuggableService {
2465
+ var RouterErrorSender = class extends DebuggableService {
2466
+ static {
2467
+ __name(this, "RouterErrorSender");
2468
+ }
2482
2469
  /**
2483
2470
  * Handle.
2484
2471
  *
@@ -2509,24 +2496,6 @@ var _RouterErrorSender = class _RouterErrorSender extends DebuggableService {
2509
2496
  body.error[name] = safeError[name];
2510
2497
  }
2511
2498
  });
2512
- const requestData = {
2513
- url: request.url,
2514
- method: request.method,
2515
- headers: request.headers
2516
- };
2517
- const inspectOptions = {
2518
- showHidden: false,
2519
- depth: null,
2520
- colors: true,
2521
- compact: false
2522
- };
2523
- console.warn((0, import_util.inspect)(requestData, inspectOptions));
2524
- console.warn((0, import_util.inspect)(body, inspectOptions));
2525
- if (error.stack) {
2526
- console.log(error.stack);
2527
- } else {
2528
- console.error(error);
2529
- }
2530
2499
  response.statusCode = statusCode;
2531
2500
  response.setHeader("Content-Type", "application/json; charset=utf-8");
2532
2501
  response.end(JSON.stringify(body, null, 2), "utf-8");
@@ -2556,12 +2525,13 @@ var _RouterErrorSender = class _RouterErrorSender extends DebuggableService {
2556
2525
  );
2557
2526
  }
2558
2527
  };
2559
- __name(_RouterErrorSender, "RouterErrorSender");
2560
- var RouterErrorSender = _RouterErrorSender;
2561
2528
 
2562
2529
  // src/trie-router.js
2563
2530
  var import_js_service4 = require("@e22m4u/js-service");
2564
- var _TrieRouter = class _TrieRouter extends DebuggableService {
2531
+ var TrieRouter = class extends DebuggableService {
2532
+ static {
2533
+ __name(this, "TrieRouter");
2534
+ }
2565
2535
  /**
2566
2536
  * Constructor.
2567
2537
  *
@@ -2616,7 +2586,7 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2616
2586
  * Example:
2617
2587
  * ```js
2618
2588
  * const router = new TrieRouter();
2619
- * const apiBranch = router.createBranch({path: 'api'});
2589
+ * const apiBranch = router.createBranch({path: '/api'});
2620
2590
  *
2621
2591
  * // GET /api/hello
2622
2592
  * apiBranch.defineRoute({
@@ -2683,22 +2653,6 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2683
2653
  }
2684
2654
  const resolved = this.getService(RouteRegistry).matchRouteByRequest(request);
2685
2655
  if (!resolved) {
2686
- if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
2687
- const allowedMethods = this.getService(RouteRegistry).getAllowedMethodsForRequestPath(
2688
- requestPath
2689
- );
2690
- if (allowedMethods.length > 0) {
2691
- debug("Auto-handling OPTIONS request.");
2692
- if (!allowedMethods.includes("OPTIONS")) {
2693
- allowedMethods.push("OPTIONS");
2694
- }
2695
- const allowHeader = allowedMethods.join(", ");
2696
- response.statusCode = 204;
2697
- response.setHeader("Allow", allowHeader);
2698
- response.end();
2699
- return;
2700
- }
2701
- }
2702
2656
  debug(
2703
2657
  "No route found for the request %s %v.",
2704
2658
  request.method,
@@ -2784,8 +2738,6 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2784
2738
  return this.getService(RouterHookRegistry).hasHook(type, hook);
2785
2739
  }
2786
2740
  };
2787
- __name(_TrieRouter, "TrieRouter");
2788
- var TrieRouter = _TrieRouter;
2789
2741
  // Annotate the CommonJS export names for ESM import in node:
2790
2742
  0 && (module.exports = {
2791
2743
  CHARACTER_ENCODING_LIST,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-trie-router",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
5
5
  "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
6
6
  "license": "MIT",
@@ -26,7 +26,7 @@
26
26
  "require": "./dist/cjs/index.cjs"
27
27
  },
28
28
  "engines": {
29
- "node": ">=16"
29
+ "node": ">=18"
30
30
  },
31
31
  "scripts": {
32
32
  "lint": "tsc && eslint ./src",
@@ -41,19 +41,19 @@
41
41
  "@e22m4u/js-debug": "~0.4.1",
42
42
  "@e22m4u/js-format": "~0.4.0",
43
43
  "@e22m4u/js-path-trie": "~0.2.0",
44
- "@e22m4u/js-service": "~0.5.1",
44
+ "@e22m4u/js-service": "~0.6.1",
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.2",
51
- "@commitlint/config-conventional": "~20.4.2",
50
+ "@commitlint/cli": "~20.4.3",
51
+ "@commitlint/config-conventional": "~20.4.3",
52
52
  "@eslint/js": "~9.39.2",
53
53
  "@types/chai": "~5.2.3",
54
54
  "@types/chai-as-promised": "~8.0.2",
55
55
  "@types/mocha": "~10.0.10",
56
- "c8": "~10.1.3",
56
+ "c8": "~11.0.0",
57
57
  "chai": "~6.2.2",
58
58
  "chai-as-promised": "~8.0.2",
59
59
  "esbuild": "~0.27.3",
@@ -61,9 +61,9 @@
61
61
  "eslint-config-prettier": "~10.1.8",
62
62
  "eslint-plugin-chai-expect": "~3.1.0",
63
63
  "eslint-plugin-import": "~2.32.0",
64
- "eslint-plugin-jsdoc": "~62.7.0",
64
+ "eslint-plugin-jsdoc": "~62.7.1",
65
65
  "eslint-plugin-mocha": "~11.2.0",
66
- "globals": "~17.3.0",
66
+ "globals": "~17.4.0",
67
67
  "husky": "~9.1.7",
68
68
  "mocha": "~11.7.5",
69
69
  "prettier": "~3.8.1",
@@ -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
  *
@@ -36,11 +36,4 @@ export declare class RouteRegistry extends DebuggableService {
36
36
  * @param request
37
37
  */
38
38
  matchRouteByRequest(request: IncomingMessage): ResolvedRoute | undefined;
39
-
40
- /**
41
- * Get allowed methods for request path.
42
- *
43
- * @param requestPath
44
- */
45
- getAllowedMethodsForRequestPath(requestPath: string): string[];
46
39
  }
@@ -1,4 +1,4 @@
1
- import {HttpMethod, Route} from './route.js';
1
+ import {Route} from './route.js';
2
2
  import {PathTrie} from '@e22m4u/js-path-trie';
3
3
  import {ServiceContainer} from '@e22m4u/js-service';
4
4
  import {getRequestPathname} from '../utils/index.js';
@@ -124,34 +124,4 @@ export class RouteRegistry extends DebuggableService {
124
124
  requestPath,
125
125
  );
126
126
  }
127
-
128
- /**
129
- * Get allowed methods for request path.
130
- *
131
- * @param {string} requestPath
132
- * @returns {string[]}
133
- */
134
- getAllowedMethodsForRequestPath(requestPath) {
135
- if (typeof requestPath !== 'string') {
136
- throw new InvalidArgumentError(
137
- 'Parameter "requestPath" must be a String, but %v was given.',
138
- requestPath,
139
- );
140
- }
141
- const debug = this.getDebuggerFor(this.getAllowedMethodsForRequestPath);
142
- const allowedMethods = [];
143
- for (const method of Object.values(HttpMethod)) {
144
- const rawTriePath = `${method}/${requestPath}`;
145
- const triePath = rawTriePath.replace(/\/+/g, '/');
146
- if (this._trie.match(triePath)) {
147
- allowedMethods.push(method);
148
- }
149
- }
150
- if (allowedMethods.length) {
151
- debug('Allowed methods for %v are: %l.', requestPath, allowedMethods);
152
- } else {
153
- debug('Path %v does not have allowed methods.', requestPath);
154
- }
155
- return allowedMethods;
156
- }
157
127
  }
@@ -2,7 +2,6 @@ import {expect} from 'chai';
2
2
  import {format} from '@e22m4u/js-format';
3
3
  import {Route, HttpMethod} from './route.js';
4
4
  import {RouteRegistry} from './route-registry.js';
5
- import {ServiceContainer} from '@e22m4u/js-service';
6
5
  import {RouterHookRegistry, RouterHookType} from '../hooks/index.js';
7
6
 
8
7
  describe('RouteRegistry', function () {
@@ -153,95 +152,4 @@ describe('RouteRegistry', function () {
153
152
  expect(res.params).to.be.eql({p1: 'baz', p2: 'qux'});
154
153
  });
155
154
  });
156
-
157
- describe('getAllowedMethodsForRequestPath', function () {
158
- it('should require the parameter "requestPath" to be a String', function () {
159
- const S = new RouteRegistry();
160
- const throwable = v => () => S.getAllowedMethodsForRequestPath(v);
161
- const error = v =>
162
- format(
163
- 'Parameter "requestPath" must be a String, but %s was given.',
164
- v,
165
- );
166
- expect(throwable(10)).to.throw(error('10'));
167
- expect(throwable(0)).to.throw(error('0'));
168
- expect(throwable(true)).to.throw(error('true'));
169
- expect(throwable(false)).to.throw(error('false'));
170
- expect(throwable([])).to.throw(error('Array'));
171
- expect(throwable({})).to.throw(error('Object'));
172
- expect(throwable(undefined)).to.throw(error('undefined'));
173
- expect(throwable(null)).to.throw(error('null'));
174
- expect(throwable(() => undefined)).to.throw(error('Function'));
175
- throwable('str')();
176
- throwable('')();
177
- });
178
-
179
- it('should return an empty array if no routes match the path', function () {
180
- const S = new RouteRegistry(new ServiceContainer());
181
- S.defineRoute({
182
- method: HttpMethod.GET,
183
- path: '/foo',
184
- handler: () => undefined,
185
- });
186
- const res = S.getAllowedMethodsForRequestPath('/bar');
187
- expect(res).to.be.eql([]);
188
- });
189
-
190
- it('should return an array with a single method if only one matches', function () {
191
- const S = new RouteRegistry(new ServiceContainer());
192
- S.defineRoute({
193
- method: HttpMethod.POST,
194
- path: '/foo',
195
- handler: () => undefined,
196
- });
197
- const res = S.getAllowedMethodsForRequestPath('/foo');
198
- expect(res).to.be.eql([HttpMethod.POST]);
199
- });
200
-
201
- it('should return an array with multiple methods if several routes match the path', function () {
202
- const S = new RouteRegistry(new ServiceContainer());
203
- const handler = () => undefined;
204
- S.defineRoute({method: HttpMethod.GET, path: '/foo', handler});
205
- S.defineRoute({method: HttpMethod.POST, path: '/foo', handler});
206
- S.defineRoute({method: HttpMethod.DELETE, path: '/foo', handler});
207
- const res = S.getAllowedMethodsForRequestPath('/foo');
208
- expect(res).to.be.eql([
209
- HttpMethod.GET,
210
- HttpMethod.POST,
211
- HttpMethod.DELETE,
212
- ]);
213
- });
214
-
215
- it('should correctly resolve allowed methods for paths with parameters', function () {
216
- const S = new RouteRegistry(new ServiceContainer());
217
- const handler = () => undefined;
218
- S.defineRoute({method: HttpMethod.GET, path: '/users/:id', handler});
219
- S.defineRoute({method: HttpMethod.PUT, path: '/users/:id', handler});
220
- S.defineRoute({method: HttpMethod.POST, path: '/users', handler});
221
- const res = S.getAllowedMethodsForRequestPath('/users/123');
222
- expect(res).to.be.eql([HttpMethod.GET, HttpMethod.PUT]);
223
- });
224
-
225
- it('should distinguish between paths with and without trailing slash', function () {
226
- const S = new RouteRegistry(new ServiceContainer());
227
- const handler = () => undefined;
228
- S.defineRoute({method: HttpMethod.GET, path: '/foo', handler});
229
- S.defineRoute({method: HttpMethod.POST, path: '/foo/', handler});
230
- const res1 = S.getAllowedMethodsForRequestPath('/foo');
231
- expect(res1).to.be.eql([HttpMethod.GET]);
232
- const res2 = S.getAllowedMethodsForRequestPath('/foo/');
233
- expect(res2).to.be.eql([HttpMethod.POST]);
234
- });
235
-
236
- it('should check for the explicitly defined OPTIONS method', function () {
237
- const S = new RouteRegistry(new ServiceContainer());
238
- S.defineRoute({
239
- method: HttpMethod.OPTIONS,
240
- path: '/api',
241
- handler: () => undefined,
242
- });
243
- const res = S.getAllowedMethodsForRequestPath('/api');
244
- expect(res).to.be.eql([HttpMethod.OPTIONS]);
245
- });
246
- });
247
155
  });
@@ -7,6 +7,7 @@ import {RouterHookRegistry} from '../hooks/index.js';
7
7
  */
8
8
  export declare const HttpMethod: {
9
9
  GET: 'GET';
10
+ HEAD: 'HEAD';
10
11
  POST: 'POST';
11
12
  PUT: 'PUT';
12
13
  PATCH: 'PATCH';
@@ -24,6 +24,7 @@ import {validateRouteDefinition} from './validate-route-definition.js';
24
24
  *
25
25
  * @type {{
26
26
  * GET: 'GET',
27
+ * HEAD: 'HEAD',
27
28
  * POST: 'POST',
28
29
  * PUT: 'PUT',
29
30
  * PATCH: 'PATCH',
@@ -33,6 +34,7 @@ import {validateRouteDefinition} from './validate-route-definition.js';
33
34
  */
34
35
  export const HttpMethod = {
35
36
  GET: 'GET',
37
+ HEAD: 'HEAD',
36
38
  POST: 'POST',
37
39
  PUT: 'PUT',
38
40
  PATCH: 'PATCH',
@@ -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
  *
@@ -1,10 +1,10 @@
1
1
  import {RouterBranch} from './branch/index.js';
2
+ import {RouteRegistry} from './route/index.js';
2
3
  import {RequestParser} from './parsers/index.js';
3
4
  import {RequestContext} from './request-context.js';
4
5
  import {ServerResponse, IncomingMessage} from 'http';
5
6
  import {DebuggableService} from './debuggable-service.js';
6
7
  import {TrieRouterOptions} from './trie-router-options.js';
7
- import {HttpMethod, RouteRegistry} from './route/index.js';
8
8
  import {RouterDataSender, RouterErrorSender} from './senders/index.js';
9
9
  import {isServiceContainer, ServiceContainer} from '@e22m4u/js-service';
10
10
  import {isPromise, isResponseSent, getRequestPathname} from './utils/index.js';
@@ -83,7 +83,7 @@ export class TrieRouter extends DebuggableService {
83
83
  * Example:
84
84
  * ```js
85
85
  * const router = new TrieRouter();
86
- * const apiBranch = router.createBranch({path: 'api'});
86
+ * const apiBranch = router.createBranch({path: '/api'});
87
87
  *
88
88
  * // GET /api/hello
89
89
  * apiBranch.defineRoute({
@@ -165,27 +165,6 @@ export class TrieRouter extends DebuggableService {
165
165
  const resolved =
166
166
  this.getService(RouteRegistry).matchRouteByRequest(request);
167
167
  if (!resolved) {
168
- // обработка метода OPTIONS выполняется автоматически
169
- // перед отправкой ошибки 404, если для пути запроса
170
- // имеются другие методы, то вместо ошибки 404 будет
171
- // отправлен ответ с "Allow*" заголовками
172
- if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
173
- const allowedMethods =
174
- this.getService(RouteRegistry).getAllowedMethodsForRequestPath(
175
- requestPath,
176
- );
177
- if (allowedMethods.length > 0) {
178
- debug('Auto-handling OPTIONS request.');
179
- if (!allowedMethods.includes('OPTIONS')) {
180
- allowedMethods.push('OPTIONS');
181
- }
182
- const allowHeader = allowedMethods.join(', ');
183
- response.statusCode = 204;
184
- response.setHeader('Allow', allowHeader);
185
- response.end();
186
- return;
187
- }
188
- }
189
168
  debug(
190
169
  'No route found for the request %s %v.',
191
170
  request.method,
@@ -1625,64 +1625,6 @@ describe('TrieRouter', function () {
1625
1625
  });
1626
1626
  });
1627
1627
  });
1628
-
1629
- describe('OPTIONS method handling', function () {
1630
- it('should automatically return 204 with specific headers for an unhandled OPTIONS request', async function () {
1631
- const router = new TrieRouter();
1632
- router.defineRoute({
1633
- method: HttpMethod.GET,
1634
- path: '/api/resource',
1635
- handler: () => 'OK',
1636
- });
1637
- router.defineRoute({
1638
- method: HttpMethod.POST,
1639
- path: '/api/resource',
1640
- handler: () => 'OK',
1641
- });
1642
- const req = createRequestMock({
1643
- method: HttpMethod.OPTIONS,
1644
- path: '/api/resource',
1645
- });
1646
- const res = createResponseMock();
1647
- await router.handleRequest(req, res);
1648
- expect(res.statusCode).to.be.eq(204);
1649
- expect(res.getHeader('Allow')).to.be.eq('GET, POST, OPTIONS');
1650
- });
1651
-
1652
- it('should execute a custom OPTIONS handler when it is explicitly defined', async function () {
1653
- const router = new TrieRouter();
1654
- let customOptionsCalled = false;
1655
- router.defineRoute({
1656
- method: HttpMethod.OPTIONS,
1657
- path: '/api/resource',
1658
- handler: () => {
1659
- customOptionsCalled = true;
1660
- return 'Custom OPTIONS response';
1661
- },
1662
- });
1663
- const req = createRequestMock({
1664
- method: HttpMethod.OPTIONS,
1665
- path: '/api/resource',
1666
- });
1667
- const res = createResponseMock();
1668
- await router.handleRequest(req, res);
1669
- const body = await res.getBody();
1670
- expect(customOptionsCalled).to.be.true;
1671
- expect(res.statusCode).to.be.eq(200);
1672
- expect(body).to.be.eq('Custom OPTIONS response');
1673
- });
1674
-
1675
- it('should return 404 for an OPTIONS request if the path does not exist for any method', async function () {
1676
- const router = new TrieRouter();
1677
- const req = createRequestMock({
1678
- method: HttpMethod.OPTIONS,
1679
- path: '/unknown',
1680
- });
1681
- const res = createResponseMock();
1682
- await router.handleRequest(req, res);
1683
- expect(res.statusCode).to.be.eq(404);
1684
- });
1685
- });
1686
1628
  });
1687
1629
 
1688
1630
  describe('addHook', function () {
@@ -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({