@e22m4u/js-trie-router 0.3.4 → 0.3.6

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
@@ -9,6 +9,21 @@ HTTP маршрутизатор для Node.js на основе
9
9
  - Поддержка `preHandler` и `postHandler` хуков.
10
10
  - Позволяет использовать асинхронные обработчики.
11
11
 
12
+ ## Содержание
13
+
14
+ - [Установка](#установка)
15
+ - [Обзор](#обзор)
16
+ - [Контекст запроса](#контекст-запроса)
17
+ - [Отправка ответа](#отправка-ответа)
18
+ - [Хуки маршрута](#хуки-маршрута)
19
+ - [preHandler](#prehandler)
20
+ - [postHandler](#posthandler)
21
+ - [Глобальные хуки](#глобальные-хуки)
22
+ - [Метаданные](#метаданные)
23
+ - [Отладка](#отладка)
24
+ - [Тестирование](#тестирование)
25
+ - [Лицензия](#лицензия)
26
+
12
27
  ## Установка
13
28
 
14
29
  Требуется Node.js 16 и выше.
@@ -74,6 +89,7 @@ server.listen(3000, 'localhost'); // прослушивание за
74
89
  - `path: string` путь включающий строку запроса, например `/myPath?foo=bar`
75
90
  - `pathname: string` путь запроса, например `/myPath`
76
91
  - `body: unknown` тело запроса
92
+ - `meta: object` мета-данные из определения маршрута
77
93
 
78
94
  Пример доступа к контексту из обработчика маршрута.
79
95
 
@@ -81,6 +97,7 @@ server.listen(3000, 'localhost'); // прослушивание за
81
97
  router.defineRoute({
82
98
  method: 'GET',
83
99
  path: '/users/:id',
100
+ meta: {prop: 'value'},
84
101
  handler(ctx) {
85
102
  // GET /users/10?include=city
86
103
  // Cookie: foo=bar; baz=qux;
@@ -93,6 +110,7 @@ router.defineRoute({
93
110
  console.log(ctx.method); // "GET"
94
111
  console.log(ctx.path); // "/users/10?include=city"
95
112
  console.log(ctx.pathname); // "/users/10"
113
+ console.log(ctx.meta); // {prop: 'value'}
96
114
  // ...
97
115
  },
98
116
  });
@@ -213,18 +231,16 @@ router.defineRoute({
213
231
  имеют более высокий приоритет перед хуками маршрута, и вызываются
214
232
  в первую очередь.
215
233
 
216
- - `preHandler` выполняется перед вызовом обработчика каждого маршрута
217
- - `postHandler` выполняется после вызова обработчика каждого маршрута
234
+ - `preHandler` выполняется перед вызовом обработчика каждого маршрута;
235
+ - `postHandler` выполняется после вызова обработчика каждого маршрута;
218
236
 
219
- Добавить глобальные хуки можно методом `addHook` экземпляра роутера,
220
- где первым параметром передается тип хука, а вторым его функция.
237
+ Добавить глобальные хуки можно методами экземпляра `TrieRouter`.
221
238
 
222
239
  ```js
223
- router.addHook('preHandler', (ctx) => {
240
+ router.addPreHandler((ctx) => {
224
241
  // перед обработчиком маршрута
225
242
  });
226
-
227
- router.addHook('postHandler', (ctx, data) => {
243
+ router.addPostHandler((ctx, data) => {
228
244
  // после обработчика маршрута
229
245
  });
230
246
  ```
@@ -233,6 +249,43 @@ router.addHook('postHandler', (ctx, data) => {
233
249
  отличное от `undefined` и `null`, то такое значение будет использовано
234
250
  как ответ сервера.
235
251
 
252
+ ### Метаданные
253
+
254
+ Иногда требуется связать с маршрутом дополнительные, статические данные, которые
255
+ могут быть использованы хуками для расширения функционала. Например, это могут
256
+ быть схемы для валидации данных, правила доступа или настройки кэширования.
257
+ Для этой цели определение маршрута поддерживает необязательное свойство `meta`.
258
+
259
+ Маршрутизатор лишь обеспечивает передачу мета-данных в контекст запроса,
260
+ откуда его могут прочитать обработчики или хуки.
261
+
262
+ ```js
263
+ import http from 'http';
264
+ import {TrieRouter} from '@e22m4u/js-trie-router';
265
+
266
+ const server = new http.Server();
267
+ const router = new TrieRouter();
268
+
269
+ // глобальный pre-handler хук, который срабатывает
270
+ // перед основным обработчиком каждого маршрута
271
+ router.addPreHandler((ctx) => {
272
+ // доступ к метаданным текущего маршрута
273
+ console.log(ctx.meta); // {foo: 'bar'}
274
+ });
275
+
276
+ router.defineRoute({
277
+ method: 'GET',
278
+ path: '/',
279
+ meta: {foo: 'bar'}, // <= мета-данные
280
+ handler(ctx) {
281
+ return 'Hello World!';
282
+ },
283
+ });
284
+
285
+ server.on('request', router.requestListener);
286
+ server.listen(3000, 'localhost');
287
+ ```
288
+
236
289
  ## Отладка
237
290
 
238
291
  Установка переменной `DEBUG` включает вывод логов.
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  RouterOptions: () => RouterOptions,
51
51
  TrieRouter: () => TrieRouter,
52
52
  UNPARSABLE_MEDIA_TYPES: () => UNPARSABLE_MEDIA_TYPES,
53
+ cloneDeep: () => cloneDeep,
53
54
  createCookiesString: () => createCookiesString,
54
55
  createDebugger: () => createDebugger,
55
56
  createError: () => createError,
@@ -75,6 +76,31 @@ var import_js_debug = require("@e22m4u/js-debug");
75
76
  // src/hooks/hook-invoker.js
76
77
  var import_js_format13 = require("@e22m4u/js-format");
77
78
 
79
+ // src/utils/clone-deep.js
80
+ function cloneDeep(value) {
81
+ if (value == null || typeof value !== "object") {
82
+ return value;
83
+ }
84
+ if (value instanceof Date) {
85
+ return new Date(value.getTime());
86
+ }
87
+ if (Array.isArray(value)) {
88
+ return value.map((item) => cloneDeep(item));
89
+ }
90
+ const proto = Object.getPrototypeOf(value);
91
+ if (proto === Object.prototype || proto === null) {
92
+ const newObj = {};
93
+ for (const key in value) {
94
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
95
+ newObj[key] = cloneDeep(value[key]);
96
+ }
97
+ }
98
+ return newObj;
99
+ }
100
+ return value;
101
+ }
102
+ __name(cloneDeep, "cloneDeep");
103
+
78
104
  // src/utils/is-promise.js
79
105
  function isPromise(value) {
80
106
  if (!value) return false;
@@ -605,9 +631,13 @@ function patchBody(res) {
605
631
  res.on("data", (c) => data.push(c));
606
632
  res.on("error", (e) => reject(e));
607
633
  res.on("end", () => {
608
- res._headersSent = true;
609
634
  resolve(Buffer.concat(data));
610
635
  });
636
+ const originalEnd = res.end.bind(res);
637
+ res.end = function(...args) {
638
+ this._headersSent = true;
639
+ return originalEnd(...args);
640
+ };
611
641
  Object.defineProperty(res, "getBody", {
612
642
  configurable: true,
613
643
  value: /* @__PURE__ */ __name(function() {
@@ -827,6 +857,20 @@ var _Route = class _Route extends import_js_debug.Debuggable {
827
857
  get path() {
828
858
  return this._path;
829
859
  }
860
+ /**
861
+ * Meta.
862
+ *
863
+ * @type {object}
864
+ */
865
+ _meta = {};
866
+ /**
867
+ * Getter of the meta.
868
+ *
869
+ * @returns {object}
870
+ */
871
+ get meta() {
872
+ return this._meta;
873
+ }
830
874
  /**
831
875
  * Handler.
832
876
  *
@@ -890,6 +934,14 @@ var _Route = class _Route extends import_js_debug.Debuggable {
890
934
  'The option "handler" of the Route should be a Function, but %v was given.',
891
935
  routeDef.handler
892
936
  );
937
+ if (routeDef.meta != null) {
938
+ if (typeof routeDef.meta !== "object" || Array.isArray(routeDef.meta))
939
+ throw new import_js_format14.Errorf(
940
+ 'The option "meta" of the Route should be a plain Object, but %v was given.',
941
+ routeDef.meta
942
+ );
943
+ this._meta = cloneDeep(routeDef.meta);
944
+ }
893
945
  this._handler = routeDef.handler;
894
946
  if (routeDef.preHandler != null) {
895
947
  const preHandlerHooks = Array.isArray(routeDef.preHandler) ? routeDef.preHandler : [routeDef.preHandler];
@@ -925,156 +977,12 @@ var _Route = class _Route extends import_js_debug.Debuggable {
925
977
  __name(_Route, "Route");
926
978
  var Route = _Route;
927
979
 
928
- // src/trie-router.js
929
- var import_http4 = require("http");
930
- var import_http5 = require("http");
931
-
932
- // src/senders/data-sender.js
933
- var import_js_format15 = require("@e22m4u/js-format");
934
- var _DataSender = class _DataSender extends DebuggableService {
935
- /**
936
- * Send.
937
- *
938
- * @param {import('http').ServerResponse} res
939
- * @param {*} data
940
- * @returns {undefined}
941
- */
942
- send(res, data) {
943
- const debug = this.getDebuggerFor(this.send);
944
- if (data === res || res.headersSent) {
945
- debug(
946
- "Response sending was skipped because its headers where sent already."
947
- );
948
- return;
949
- }
950
- if (data == null) {
951
- res.statusCode = 204;
952
- res.end();
953
- debug("The empty response was sent.");
954
- return;
955
- }
956
- if (isReadableStream(data)) {
957
- res.setHeader("Content-Type", "application/octet-stream");
958
- data.pipe(res);
959
- debug("The stream response was sent.");
960
- return;
961
- }
962
- let debugMsg;
963
- switch (typeof data) {
964
- case "object":
965
- case "boolean":
966
- case "number":
967
- if (Buffer.isBuffer(data)) {
968
- res.setHeader("content-type", "application/octet-stream");
969
- debugMsg = "The Buffer was sent as binary data.";
970
- } else {
971
- res.setHeader("content-type", "application/json");
972
- debugMsg = (0, import_js_format15.format)("The %v was sent as JSON.", typeof data);
973
- data = JSON.stringify(data);
974
- }
975
- break;
976
- default:
977
- res.setHeader("content-type", "text/plain");
978
- debugMsg = "The response data was sent as plain text.";
979
- data = String(data);
980
- break;
981
- }
982
- res.end(data);
983
- debug(debugMsg);
984
- }
985
- };
986
- __name(_DataSender, "DataSender");
987
- var DataSender = _DataSender;
988
-
989
- // src/senders/error-sender.js
990
- var import_util = require("util");
991
- var import_statuses = __toESM(require("statuses"), 1);
992
- var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
993
- var _ErrorSender = class _ErrorSender extends DebuggableService {
994
- /**
995
- * Handle.
996
- *
997
- * @param {import('http').IncomingMessage} req
998
- * @param {import('http').ServerResponse} res
999
- * @param {Error} error
1000
- * @returns {undefined}
1001
- */
1002
- send(req, res, error) {
1003
- const debug = this.getDebuggerFor(this.send);
1004
- let safeError = {};
1005
- if (error) {
1006
- if (typeof error === "object") {
1007
- safeError = error;
1008
- } else {
1009
- safeError = { message: String(error) };
1010
- }
1011
- }
1012
- const statusCode = error.statusCode || error.status || 500;
1013
- const body = { error: {} };
1014
- if (safeError.message && typeof safeError.message === "string") {
1015
- body.error.message = safeError.message;
1016
- } else {
1017
- body.error.message = (0, import_statuses.default)(statusCode);
1018
- }
1019
- EXPOSED_ERROR_PROPERTIES.forEach((name) => {
1020
- if (name in safeError) body.error[name] = safeError[name];
1021
- });
1022
- const requestData = {
1023
- url: req.url,
1024
- method: req.method,
1025
- headers: req.headers
1026
- };
1027
- const inspectOptions = {
1028
- showHidden: false,
1029
- depth: null,
1030
- colors: true,
1031
- compact: false
1032
- };
1033
- console.warn((0, import_util.inspect)(requestData, inspectOptions));
1034
- console.warn((0, import_util.inspect)(body, inspectOptions));
1035
- if (error.stack) {
1036
- console.log(error.stack);
1037
- } else {
1038
- console.error(error);
1039
- }
1040
- res.statusCode = statusCode;
1041
- res.setHeader("content-type", "application/json; charset=utf-8");
1042
- res.end(JSON.stringify(body, null, 2), "utf-8");
1043
- debug(
1044
- "The %s error was sent for the request %s %v.",
1045
- statusCode,
1046
- req.method,
1047
- getRequestPathname(req)
1048
- );
1049
- }
1050
- /**
1051
- * Send 404.
1052
- *
1053
- * @param {import('http').IncomingMessage} req
1054
- * @param {import('http').ServerResponse} res
1055
- * @returns {undefined}
1056
- */
1057
- send404(req, res) {
1058
- const debug = this.getDebuggerFor(this.send404);
1059
- res.statusCode = 404;
1060
- res.setHeader("content-type", "text/plain; charset=utf-8");
1061
- res.end("404 Not Found", "utf-8");
1062
- debug(
1063
- "The 404 error was sent for the request %s %v.",
1064
- req.method,
1065
- getRequestPathname(req)
1066
- );
1067
- }
1068
- };
1069
- __name(_ErrorSender, "ErrorSender");
1070
- var ErrorSender = _ErrorSender;
1071
-
1072
980
  // src/parsers/body-parser.js
1073
981
  var import_http_errors2 = __toESM(require("http-errors"), 1);
1074
- var import_js_format17 = require("@e22m4u/js-format");
982
+ var import_js_format16 = require("@e22m4u/js-format");
1075
983
 
1076
984
  // src/router-options.js
1077
- var import_js_format16 = require("@e22m4u/js-format");
985
+ var import_js_format15 = require("@e22m4u/js-format");
1078
986
  var _RouterOptions = class _RouterOptions extends DebuggableService {
1079
987
  /**
1080
988
  * Request body bytes limit.
@@ -1100,7 +1008,7 @@ var _RouterOptions = class _RouterOptions extends DebuggableService {
1100
1008
  */
1101
1009
  setRequestBodyBytesLimit(input) {
1102
1010
  if (typeof input !== "number" || input < 0)
1103
- throw new import_js_format16.Errorf(
1011
+ throw new import_js_format15.Errorf(
1104
1012
  'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v was given.',
1105
1013
  input
1106
1014
  );
@@ -1133,12 +1041,12 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1133
1041
  */
1134
1042
  defineParser(mediaType, parser) {
1135
1043
  if (!mediaType || typeof mediaType !== "string")
1136
- throw new import_js_format17.Errorf(
1044
+ throw new import_js_format16.Errorf(
1137
1045
  'The parameter "mediaType" of BodyParser.defineParser should be a non-empty String, but %v was given.',
1138
1046
  mediaType
1139
1047
  );
1140
1048
  if (!parser || typeof parser !== "function")
1141
- throw new import_js_format17.Errorf(
1049
+ throw new import_js_format16.Errorf(
1142
1050
  'The parameter "parser" of BodyParser.defineParser should be a Function, but %v was given.',
1143
1051
  parser
1144
1052
  );
@@ -1153,7 +1061,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1153
1061
  */
1154
1062
  hasParser(mediaType) {
1155
1063
  if (!mediaType || typeof mediaType !== "string")
1156
- throw new import_js_format17.Errorf(
1064
+ throw new import_js_format16.Errorf(
1157
1065
  'The parameter "mediaType" of BodyParser.hasParser should be a non-empty String, but %v was given.',
1158
1066
  mediaType
1159
1067
  );
@@ -1167,12 +1075,12 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1167
1075
  */
1168
1076
  deleteParser(mediaType) {
1169
1077
  if (!mediaType || typeof mediaType !== "string")
1170
- throw new import_js_format17.Errorf(
1078
+ throw new import_js_format16.Errorf(
1171
1079
  'The parameter "mediaType" of BodyParser.deleteParser should be a non-empty String, but %v was given.',
1172
1080
  mediaType
1173
1081
  );
1174
1082
  const parser = this._parsers[mediaType];
1175
- if (!parser) throw new import_js_format17.Errorf("The parser of %v is not found.", mediaType);
1083
+ if (!parser) throw new import_js_format16.Errorf("The parser of %v is not found.", mediaType);
1176
1084
  delete this._parsers[mediaType];
1177
1085
  return this;
1178
1086
  }
@@ -1301,7 +1209,7 @@ var CookiesParser = _CookiesParser;
1301
1209
 
1302
1210
  // src/parsers/request-parser.js
1303
1211
  var import_http3 = require("http");
1304
- var import_js_format18 = require("@e22m4u/js-format");
1212
+ var import_js_format17 = require("@e22m4u/js-format");
1305
1213
  var _RequestParser = class _RequestParser extends DebuggableService {
1306
1214
  /**
1307
1215
  * Parse.
@@ -1311,7 +1219,7 @@ var _RequestParser = class _RequestParser extends DebuggableService {
1311
1219
  */
1312
1220
  parse(req) {
1313
1221
  if (!(req instanceof import_http3.IncomingMessage))
1314
- throw new import_js_format18.Errorf(
1222
+ throw new import_js_format17.Errorf(
1315
1223
  "The first argument of RequestParser.parse should be an instance of IncomingMessage, but %v was given.",
1316
1224
  req
1317
1225
  );
@@ -1343,7 +1251,7 @@ __name(_RequestParser, "RequestParser");
1343
1251
  var RequestParser = _RequestParser;
1344
1252
 
1345
1253
  // src/route-registry.js
1346
- var import_js_format19 = require("@e22m4u/js-format");
1254
+ var import_js_format18 = require("@e22m4u/js-format");
1347
1255
  var import_js_path_trie = require("@e22m4u/js-path-trie");
1348
1256
  var import_js_service2 = require("@e22m4u/js-service");
1349
1257
  var _RouteRegistry = class _RouteRegistry extends DebuggableService {
@@ -1365,7 +1273,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
1365
1273
  defineRoute(routeDef) {
1366
1274
  const debug = this.getDebuggerFor(this.defineRoute);
1367
1275
  if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
1368
- throw new import_js_format19.Errorf(
1276
+ throw new import_js_format18.Errorf(
1369
1277
  "The route definition should be an Object, but %v was given.",
1370
1278
  routeDef
1371
1279
  );
@@ -1428,7 +1336,7 @@ __name(_RouteRegistry, "RouteRegistry");
1428
1336
  var RouteRegistry = _RouteRegistry;
1429
1337
 
1430
1338
  // src/request-context.js
1431
- var import_js_format20 = require("@e22m4u/js-format");
1339
+ var import_js_format19 = require("@e22m4u/js-format");
1432
1340
  var import_js_service3 = require("@e22m4u/js-service");
1433
1341
  var import_js_service4 = require("@e22m4u/js-service");
1434
1342
  var _RequestContext = class _RequestContext {
@@ -1480,6 +1388,12 @@ var _RequestContext = class _RequestContext {
1480
1388
  * @type {*}
1481
1389
  */
1482
1390
  body;
1391
+ /**
1392
+ * Route meta.
1393
+ *
1394
+ * @type {object}
1395
+ */
1396
+ meta = {};
1483
1397
  /**
1484
1398
  * Method.
1485
1399
  *
@@ -1522,20 +1436,20 @@ var _RequestContext = class _RequestContext {
1522
1436
  */
1523
1437
  constructor(container, request, response) {
1524
1438
  if (!(0, import_js_service4.isServiceContainer)(container))
1525
- throw new import_js_format20.Errorf(
1439
+ throw new import_js_format19.Errorf(
1526
1440
  'The parameter "container" of RequestContext.constructor should be an instance of ServiceContainer, but %v was given.',
1527
1441
  container
1528
1442
  );
1529
1443
  this.container = container;
1530
1444
  if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
1531
- throw new import_js_format20.Errorf(
1445
+ throw new import_js_format19.Errorf(
1532
1446
  'The parameter "request" of RequestContext.constructor should be an instance of IncomingMessage, but %v was given.',
1533
1447
  request
1534
1448
  );
1535
1449
  }
1536
1450
  this.req = request;
1537
1451
  if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
1538
- throw new import_js_format20.Errorf(
1452
+ throw new import_js_format19.Errorf(
1539
1453
  'The parameter "response" of RequestContext.constructor should be an instance of ServerResponse, but %v was given.',
1540
1454
  response
1541
1455
  );
@@ -1548,6 +1462,149 @@ var RequestContext = _RequestContext;
1548
1462
 
1549
1463
  // src/trie-router.js
1550
1464
  var import_js_service5 = require("@e22m4u/js-service");
1465
+ var import_http4 = require("http");
1466
+
1467
+ // src/senders/data-sender.js
1468
+ var import_js_format20 = require("@e22m4u/js-format");
1469
+ var _DataSender = class _DataSender extends DebuggableService {
1470
+ /**
1471
+ * Send.
1472
+ *
1473
+ * @param {import('http').ServerResponse} res
1474
+ * @param {*} data
1475
+ * @returns {undefined}
1476
+ */
1477
+ send(res, data) {
1478
+ const debug = this.getDebuggerFor(this.send);
1479
+ if (data === res || res.headersSent) {
1480
+ debug(
1481
+ "Response sending was skipped because its headers where sent already."
1482
+ );
1483
+ return;
1484
+ }
1485
+ if (data == null) {
1486
+ res.statusCode = 204;
1487
+ res.end();
1488
+ debug("The empty response was sent.");
1489
+ return;
1490
+ }
1491
+ if (isReadableStream(data)) {
1492
+ res.setHeader("Content-Type", "application/octet-stream");
1493
+ data.pipe(res);
1494
+ debug("The stream response was sent.");
1495
+ return;
1496
+ }
1497
+ let debugMsg;
1498
+ switch (typeof data) {
1499
+ case "object":
1500
+ case "boolean":
1501
+ case "number":
1502
+ if (Buffer.isBuffer(data)) {
1503
+ res.setHeader("content-type", "application/octet-stream");
1504
+ debugMsg = "The Buffer was sent as binary data.";
1505
+ } else {
1506
+ res.setHeader("content-type", "application/json");
1507
+ debugMsg = (0, import_js_format20.format)("The %v was sent as JSON.", typeof data);
1508
+ data = JSON.stringify(data);
1509
+ }
1510
+ break;
1511
+ default:
1512
+ res.setHeader("content-type", "text/plain");
1513
+ debugMsg = "The response data was sent as plain text.";
1514
+ data = String(data);
1515
+ break;
1516
+ }
1517
+ res.end(data);
1518
+ debug(debugMsg);
1519
+ }
1520
+ };
1521
+ __name(_DataSender, "DataSender");
1522
+ var DataSender = _DataSender;
1523
+
1524
+ // src/senders/error-sender.js
1525
+ var import_util = require("util");
1526
+ var import_statuses = __toESM(require("statuses"), 1);
1527
+ var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
1528
+ var _ErrorSender = class _ErrorSender extends DebuggableService {
1529
+ /**
1530
+ * Handle.
1531
+ *
1532
+ * @param {import('http').IncomingMessage} req
1533
+ * @param {import('http').ServerResponse} res
1534
+ * @param {Error} error
1535
+ * @returns {undefined}
1536
+ */
1537
+ send(req, res, error) {
1538
+ const debug = this.getDebuggerFor(this.send);
1539
+ let safeError = {};
1540
+ if (error) {
1541
+ if (typeof error === "object") {
1542
+ safeError = error;
1543
+ } else {
1544
+ safeError = { message: String(error) };
1545
+ }
1546
+ }
1547
+ const statusCode = error.statusCode || error.status || 500;
1548
+ const body = { error: {} };
1549
+ if (safeError.message && typeof safeError.message === "string") {
1550
+ body.error.message = safeError.message;
1551
+ } else {
1552
+ body.error.message = (0, import_statuses.default)(statusCode);
1553
+ }
1554
+ EXPOSED_ERROR_PROPERTIES.forEach((name) => {
1555
+ if (name in safeError) body.error[name] = safeError[name];
1556
+ });
1557
+ const requestData = {
1558
+ url: req.url,
1559
+ method: req.method,
1560
+ headers: req.headers
1561
+ };
1562
+ const inspectOptions = {
1563
+ showHidden: false,
1564
+ depth: null,
1565
+ colors: true,
1566
+ compact: false
1567
+ };
1568
+ console.warn((0, import_util.inspect)(requestData, inspectOptions));
1569
+ console.warn((0, import_util.inspect)(body, inspectOptions));
1570
+ if (error.stack) {
1571
+ console.log(error.stack);
1572
+ } else {
1573
+ console.error(error);
1574
+ }
1575
+ res.statusCode = statusCode;
1576
+ res.setHeader("content-type", "application/json; charset=utf-8");
1577
+ res.end(JSON.stringify(body, null, 2), "utf-8");
1578
+ debug(
1579
+ "The %s error was sent for the request %s %v.",
1580
+ statusCode,
1581
+ req.method,
1582
+ getRequestPathname(req)
1583
+ );
1584
+ }
1585
+ /**
1586
+ * Send 404.
1587
+ *
1588
+ * @param {import('http').IncomingMessage} req
1589
+ * @param {import('http').ServerResponse} res
1590
+ * @returns {undefined}
1591
+ */
1592
+ send404(req, res) {
1593
+ const debug = this.getDebuggerFor(this.send404);
1594
+ res.statusCode = 404;
1595
+ res.setHeader("content-type", "text/plain; charset=utf-8");
1596
+ res.end("404 Not Found", "utf-8");
1597
+ debug(
1598
+ "The 404 error was sent for the request %s %v.",
1599
+ req.method,
1600
+ getRequestPathname(req)
1601
+ );
1602
+ }
1603
+ };
1604
+ __name(_ErrorSender, "ErrorSender");
1605
+ var ErrorSender = _ErrorSender;
1606
+
1607
+ // src/trie-router.js
1551
1608
  var _TrieRouter = class _TrieRouter extends DebuggableService {
1552
1609
  /**
1553
1610
  * Define route.
@@ -1623,8 +1680,11 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
1623
1680
  const { route, params } = resolved;
1624
1681
  const container = new import_js_service5.ServiceContainer(this.container);
1625
1682
  const context = new RequestContext(container, req, res);
1683
+ if (route.meta != null) {
1684
+ context.meta = cloneDeep(route.meta);
1685
+ }
1626
1686
  container.set(RequestContext, context);
1627
- container.set(import_http5.IncomingMessage, req);
1687
+ container.set(import_http4.IncomingMessage, req);
1628
1688
  container.set(import_http4.ServerResponse, res);
1629
1689
  context.params = params;
1630
1690
  let data;
@@ -1699,6 +1759,26 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
1699
1759
  this.getService(HookRegistry).addHook(type, hook);
1700
1760
  return this;
1701
1761
  }
1762
+ /**
1763
+ * Add pre-handler hook.
1764
+ *
1765
+ * @param {Function} hook
1766
+ * @returns {this}
1767
+ */
1768
+ addPreHandler(hook) {
1769
+ this.getService(HookRegistry).addHook(RouterHookType.PRE_HANDLER, hook);
1770
+ return this;
1771
+ }
1772
+ /**
1773
+ * Add post-handler hook.
1774
+ *
1775
+ * @param {Function} hook
1776
+ * @returns {this}
1777
+ */
1778
+ addPostHandler(hook) {
1779
+ this.getService(HookRegistry).addHook(RouterHookType.POST_HANDLER, hook);
1780
+ return this;
1781
+ }
1702
1782
  };
1703
1783
  __name(_TrieRouter, "TrieRouter");
1704
1784
  var TrieRouter = _TrieRouter;
@@ -1723,6 +1803,7 @@ var TrieRouter = _TrieRouter;
1723
1803
  RouterOptions,
1724
1804
  TrieRouter,
1725
1805
  UNPARSABLE_MEDIA_TYPES,
1806
+ cloneDeep,
1726
1807
  createCookiesString,
1727
1808
  createDebugger,
1728
1809
  createError,