@e22m4u/js-trie-router 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/cjs/index.cjs +160 -98
  2. package/package.json +4 -4
  3. package/src/branch/merge-router-branch-definition.spec.js +17 -15
  4. package/src/branch/router-branch.js +4 -3
  5. package/src/branch/router-branch.spec.js +71 -9
  6. package/src/branch/validate-router-branch-definition.js +4 -3
  7. package/src/branch/validate-router-branch-definition.spec.js +16 -16
  8. package/src/debuggable-service.spec.js +10 -2
  9. package/src/hooks/router-hook-invoker.js +5 -3
  10. package/src/hooks/router-hook-invoker.spec.js +19 -17
  11. package/src/hooks/router-hook-registry.spec.js +32 -18
  12. package/src/parsers/body-parser.d.ts +11 -4
  13. package/src/parsers/body-parser.js +28 -12
  14. package/src/parsers/body-parser.spec.js +136 -92
  15. package/src/parsers/cookies-parser.spec.js +3 -3
  16. package/src/parsers/query-parser.spec.js +3 -3
  17. package/src/parsers/request-parser.js +2 -2
  18. package/src/parsers/request-parser.spec.js +9 -9
  19. package/src/request-context.js +7 -8
  20. package/src/request-context.spec.js +17 -18
  21. package/src/route/route.d.ts +1 -0
  22. package/src/route/route.js +2 -0
  23. package/src/route/route.spec.js +30 -30
  24. package/src/route/validate-route-definition.js +6 -4
  25. package/src/route/validate-route-definition.spec.js +16 -16
  26. package/src/route-registry.d.ts +8 -1
  27. package/src/route-registry.js +35 -5
  28. package/src/route-registry.spec.js +98 -7
  29. package/src/router-options.js +2 -2
  30. package/src/router-options.spec.js +6 -6
  31. package/src/senders/data-sender.spec.js +7 -7
  32. package/src/senders/error-sender.spec.js +3 -3
  33. package/src/trie-router.d.ts +6 -6
  34. package/src/trie-router.js +25 -3
  35. package/src/trie-router.spec.js +150 -22
  36. package/src/utils/clone-deep.spec.js +7 -7
  37. package/src/utils/create-cookie-string.js +1 -2
  38. package/src/utils/create-cookie-string.spec.js +4 -8
  39. package/src/utils/create-error.js +2 -4
  40. package/src/utils/create-error.spec.js +5 -13
  41. package/src/utils/create-request-mock.d.ts +4 -4
  42. package/src/utils/create-request-mock.js +73 -90
  43. package/src/utils/create-request-mock.spec.js +61 -98
  44. package/src/utils/create-response-mock.d.ts +1 -0
  45. package/src/utils/create-response-mock.js +1 -0
  46. package/src/utils/create-response-mock.spec.js +20 -13
  47. package/src/utils/create-route-mock.spec.js +4 -4
  48. package/src/utils/fetch-request-body.js +3 -4
  49. package/src/utils/fetch-request-body.spec.js +22 -23
  50. package/src/utils/get-request-pathname.js +2 -2
  51. package/src/utils/get-request-pathname.spec.js +11 -4
  52. package/src/utils/is-promise.spec.js +2 -2
  53. package/src/utils/is-readable-stream.spec.js +2 -2
  54. package/src/utils/is-response-sent.js +2 -2
  55. package/src/utils/is-response-sent.spec.js +5 -5
  56. package/src/utils/is-writable-stream.spec.js +2 -2
  57. package/src/utils/merge-deep.spec.js +2 -2
  58. package/src/utils/parse-content-type.js +1 -2
  59. package/src/utils/parse-content-type.spec.js +7 -11
  60. package/src/utils/parse-cookie-string.js +1 -2
  61. package/src/utils/parse-cookie-string.spec.js +2 -6
  62. package/src/utils/to-camel-case.js +1 -2
  63. package/src/utils/to-camel-case.spec.js +3 -7
  64. package/src/utils/to-pascal-case.spec.js +1 -1
@@ -166,13 +166,13 @@ var import_js_format = require("@e22m4u/js-format");
166
166
  function createError(errorCtor, message, ...args) {
167
167
  if (typeof errorCtor !== "function") {
168
168
  throw new import_js_format.InvalidArgumentError(
169
- 'The first parameter of "createError" must be a constructor, but %v was given.',
169
+ 'Parameter "errorCtor" must be a Function, but %v was given.',
170
170
  errorCtor
171
171
  );
172
172
  }
173
173
  if (message != null && typeof message !== "string") {
174
174
  throw new import_js_format.InvalidArgumentError(
175
- 'The second parameter of "createError" must be a String, but %v was given.',
175
+ 'Parameter "message" must be a String, but %v was given.',
176
176
  message
177
177
  );
178
178
  }
@@ -189,7 +189,7 @@ var import_js_format2 = require("@e22m4u/js-format");
189
189
  function toCamelCase(input) {
190
190
  if (typeof input !== "string") {
191
191
  throw new import_js_format2.InvalidArgumentError(
192
- 'The first parameter of "toCamelCase" must be a String, but %v was given.',
192
+ 'Parameter "input" must be a String, but %v was given.',
193
193
  input
194
194
  );
195
195
  }
@@ -211,7 +211,7 @@ var import_js_format3 = require("@e22m4u/js-format");
211
211
  function isResponseSent(response) {
212
212
  if (!response || typeof response !== "object" || Array.isArray(response) || typeof response.headersSent !== "boolean") {
213
213
  throw new import_js_format3.InvalidArgumentError(
214
- 'The first parameter of "isResponseSent" must be an instance of ServerResponse, but %v was given.',
214
+ 'Parameter "response" must be an instance of ServerResponse, but %v was given.',
215
215
  response
216
216
  );
217
217
  }
@@ -243,7 +243,7 @@ var import_js_format4 = require("@e22m4u/js-format");
243
243
  function parseContentType(input) {
244
244
  if (typeof input !== "string") {
245
245
  throw new import_js_format4.InvalidArgumentError(
246
- "The first parameter of `parseContentType` must be a String, but %v was given.",
246
+ 'Parameter "input" must be a String, but %v was given.',
247
247
  input
248
248
  );
249
249
  }
@@ -289,13 +289,13 @@ var CHARACTER_ENCODING_LIST = [
289
289
  function fetchRequestBody(request, bodyBytesLimit = 0) {
290
290
  if (!(request instanceof import_http.IncomingMessage)) {
291
291
  throw new import_js_format5.InvalidArgumentError(
292
- 'The first parameter of "fetchRequestBody" must be an IncomingMessage instance, but %v was given.',
292
+ 'Parameter "request" must be an instance of IncomingMessage, but %v was given.',
293
293
  request
294
294
  );
295
295
  }
296
296
  if (typeof bodyBytesLimit !== "number") {
297
297
  throw new import_js_format5.InvalidArgumentError(
298
- 'The parameter "bodyBytesLimit" of "fetchRequestBody" must be a Number, but %v was given.',
298
+ 'Parameter "bodyBytesLimit" must be a Number, but %v was given.',
299
299
  bodyBytesLimit
300
300
  );
301
301
  }
@@ -382,7 +382,7 @@ var import_js_format6 = require("@e22m4u/js-format");
382
382
  function parseCookieString(input) {
383
383
  if (typeof input !== "string") {
384
384
  throw new import_js_format6.InvalidArgumentError(
385
- 'The first parameter of "parseCookieString" must be a String, but %v was given.',
385
+ 'Parameter "input" must be a String, but %v was given.',
386
386
  input
387
387
  );
388
388
  }
@@ -410,7 +410,7 @@ var import_js_format7 = require("@e22m4u/js-format");
410
410
  function createCookieString(data) {
411
411
  if (!data || typeof data !== "object" || Array.isArray(data)) {
412
412
  throw new import_js_format7.InvalidArgumentError(
413
- 'The first parameter of "createCookieString" must be an Object, but %v was given.',
413
+ "Cookie data must be an Object, but %v was given.",
414
414
  data
415
415
  );
416
416
  }
@@ -430,111 +430,111 @@ function createCookieString(data) {
430
430
  __name(createCookieString, "createCookieString");
431
431
 
432
432
  // src/utils/create-request-mock.js
433
- function createRequestMock(patch) {
434
- if (patch != null && typeof patch !== "object" || Array.isArray(patch)) {
433
+ function createRequestMock(options) {
434
+ if (options != null && typeof options !== "object" || Array.isArray(options)) {
435
435
  throw new import_js_format8.InvalidArgumentError(
436
- 'The first parameter of "createRequestMock" must be an Object, but %v was given.',
437
- patch
436
+ 'Parameter "options" must be an Object, but %v was given.',
437
+ options
438
438
  );
439
439
  }
440
- patch = patch || {};
441
- if (patch.host != null && typeof patch.host !== "string") {
440
+ options = options || {};
441
+ if (options.host != null && typeof options.host !== "string") {
442
442
  throw new import_js_format8.InvalidArgumentError(
443
- 'The parameter "host" of "createRequestMock" must be a String, but %v was given.',
444
- patch.host
443
+ 'Option "host" must be a String, but %v was given.',
444
+ options.host
445
445
  );
446
446
  }
447
- if (patch.method != null && typeof patch.method !== "string") {
447
+ if (options.method != null && typeof options.method !== "string") {
448
448
  throw new import_js_format8.InvalidArgumentError(
449
- 'The parameter "method" of "createRequestMock" must be a String, but %v was given.',
450
- patch.method
449
+ 'Option "method" must be a String, but %v was given.',
450
+ options.method
451
451
  );
452
452
  }
453
- if (patch.secure != null && typeof patch.secure !== "boolean") {
453
+ if (options.secure != null && typeof options.secure !== "boolean") {
454
454
  throw new import_js_format8.InvalidArgumentError(
455
- 'The parameter "secure" of "createRequestMock" must be a Boolean, but %v was given.',
456
- patch.secure
455
+ 'Option "secure" must be a Boolean, but %v was given.',
456
+ options.secure
457
457
  );
458
458
  }
459
- if (patch.path != null && typeof patch.path !== "string") {
459
+ if (options.path != null && typeof options.path !== "string") {
460
460
  throw new import_js_format8.InvalidArgumentError(
461
- 'The parameter "path" of "createRequestMock" must be a String, but %v was given.',
462
- patch.path
461
+ 'Option "path" must be a String, but %v was given.',
462
+ options.path
463
463
  );
464
464
  }
465
- if (patch.query != null && typeof patch.query !== "object" && typeof patch.query !== "string" || Array.isArray(patch.query)) {
465
+ if (options.query != null && typeof options.query !== "object" && typeof options.query !== "string" || Array.isArray(options.query)) {
466
466
  throw new import_js_format8.InvalidArgumentError(
467
- 'The parameter "query" of "createRequestMock" must be a String or Object, but %v was given.',
468
- patch.query
467
+ 'Option "query" must be a String or Object, but %v was given.',
468
+ options.query
469
469
  );
470
470
  }
471
- if (patch.cookies != null && typeof patch.cookies !== "string" && typeof patch.cookies !== "object" || Array.isArray(patch.cookies)) {
471
+ if (options.cookies != null && typeof options.cookies !== "string" && typeof options.cookies !== "object" || Array.isArray(options.cookies)) {
472
472
  throw new import_js_format8.InvalidArgumentError(
473
- 'The parameter "cookies" of "createRequestMock" must be a String or Object, but %v was given.',
474
- patch.cookies
473
+ 'Option "cookies" must be a String or Object, but %v was given.',
474
+ options.cookies
475
475
  );
476
476
  }
477
- if (patch.headers != null && typeof patch.headers !== "object" || Array.isArray(patch.headers)) {
477
+ if (options.headers != null && typeof options.headers !== "object" || Array.isArray(options.headers)) {
478
478
  throw new import_js_format8.InvalidArgumentError(
479
- 'The parameter "headers" of "createRequestMock" must be an Object, but %v was given.',
480
- patch.headers
479
+ 'Option "headers" must be an Object, but %v was given.',
480
+ options.headers
481
481
  );
482
482
  }
483
- if (patch.stream != null && !isReadableStream(patch.stream)) {
483
+ if (options.stream != null && !isReadableStream(options.stream)) {
484
484
  throw new import_js_format8.InvalidArgumentError(
485
- 'The parameter "stream" of "createRequestMock" must be a Stream, but %v was given.',
486
- patch.stream
485
+ 'Option "stream" must be a Stream, but %v was given.',
486
+ options.stream
487
487
  );
488
488
  }
489
- if (patch.encoding != null) {
490
- if (typeof patch.encoding !== "string") {
489
+ if (options.encoding != null) {
490
+ if (typeof options.encoding !== "string") {
491
491
  throw new import_js_format8.InvalidArgumentError(
492
- 'The parameter "encoding" of "createRequestMock" must be a String, but %v was given.',
493
- patch.encoding
492
+ 'Option "encoding" must be a String, but %v was given.',
493
+ options.encoding
494
494
  );
495
495
  }
496
- if (!CHARACTER_ENCODING_LIST.includes(patch.encoding)) {
496
+ if (!CHARACTER_ENCODING_LIST.includes(options.encoding)) {
497
497
  throw new import_js_format8.InvalidArgumentError(
498
498
  "Character encoding %v is not supported.",
499
- patch.encoding
499
+ options.encoding
500
500
  );
501
501
  }
502
502
  }
503
- if (patch.stream) {
504
- if (patch.secure != null) {
503
+ if (options.stream) {
504
+ if (options.secure != null) {
505
505
  throw new import_js_format8.InvalidArgumentError(
506
- 'The "createRequestMock" does not allow specifying the "stream" and "secure" options simultaneously.'
506
+ 'The "stream" and "secure" options cannot be used together.'
507
507
  );
508
508
  }
509
- if (patch.body != null) {
509
+ if (options.body != null) {
510
510
  throw new import_js_format8.InvalidArgumentError(
511
- 'The "createRequestMock" does not allow specifying the "stream" and "body" options simultaneously.'
511
+ 'The "stream" and "body" options cannot be used together.'
512
512
  );
513
513
  }
514
- if (patch.encoding != null) {
514
+ if (options.encoding != null) {
515
515
  throw new import_js_format8.InvalidArgumentError(
516
- 'The "createRequestMock" does not allow specifying the "stream" and "encoding" options simultaneously.'
516
+ 'The "stream" and "encoding" options cannot be used together.'
517
517
  );
518
518
  }
519
519
  }
520
- const request = patch.stream || createRequestStream(patch.secure, patch.body, patch.encoding);
521
- request.url = createRequestUrl(patch.path || "/", patch.query);
520
+ const request = options.stream || createRequestStream(options.secure, options.body, options.encoding);
521
+ request.url = createRequestUrl(options.path || "/", options.query);
522
522
  request.headers = createRequestHeaders(
523
- patch.host,
524
- patch.secure,
525
- patch.body,
526
- patch.cookies,
527
- patch.encoding,
528
- patch.headers
523
+ options.host,
524
+ options.secure,
525
+ options.body,
526
+ options.cookies,
527
+ options.encoding,
528
+ options.headers
529
529
  );
530
- request.method = (patch.method || "get").toUpperCase();
530
+ request.method = (options.method || "get").toUpperCase();
531
531
  return request;
532
532
  }
533
533
  __name(createRequestMock, "createRequestMock");
534
534
  function createRequestStream(secure, body, encoding) {
535
535
  if (encoding != null && typeof encoding !== "string") {
536
536
  throw new import_js_format8.InvalidArgumentError(
537
- 'The parameter "encoding" of "createRequestStream" must be a String, but %v was given.',
537
+ 'Parameter "encoding" must be a String, but %v was given.',
538
538
  encoding
539
539
  );
540
540
  }
@@ -560,13 +560,13 @@ __name(createRequestStream, "createRequestStream");
560
560
  function createRequestUrl(path, query) {
561
561
  if (typeof path !== "string") {
562
562
  throw new import_js_format8.InvalidArgumentError(
563
- 'The parameter "path" of "createRequestUrl" must be a String, but %v was given.',
563
+ 'Parameter "path" must be a String, but %v was given.',
564
564
  path
565
565
  );
566
566
  }
567
567
  if (query != null && typeof query !== "string" && typeof query !== "object" || Array.isArray(query)) {
568
568
  throw new import_js_format8.InvalidArgumentError(
569
- 'The parameter "query" of "createRequestUrl" must be a String or Object, but %v was given.',
569
+ 'Parameter "query" must be a String or Object, but %v was given.',
570
570
  query
571
571
  );
572
572
  }
@@ -585,34 +585,34 @@ __name(createRequestUrl, "createRequestUrl");
585
585
  function createRequestHeaders(host, secure, body, cookies, encoding, headers) {
586
586
  if (host != null && typeof host !== "string") {
587
587
  throw new import_js_format8.InvalidArgumentError(
588
- 'The parameter "host" of "createRequestHeaders" a non-empty String, but %v was given.',
588
+ 'Parameter "host" must be a non-empty String, but %v was given.',
589
589
  host
590
590
  );
591
591
  }
592
592
  host = host || "localhost";
593
593
  if (secure != null && typeof secure !== "boolean") {
594
594
  throw new import_js_format8.InvalidArgumentError(
595
- 'The parameter "secure" of "createRequestHeaders" must be a String, but %v was given.',
595
+ 'Parameter "secure" must be a String, but %v was given.',
596
596
  secure
597
597
  );
598
598
  }
599
599
  secure = Boolean(secure);
600
600
  if (cookies != null && typeof cookies !== "object" && typeof cookies !== "string" || Array.isArray(cookies)) {
601
601
  throw new import_js_format8.InvalidArgumentError(
602
- 'The parameter "cookies" of "createRequestHeaders" must be a String or Object, but %v was given.',
602
+ 'Parameter "cookies" must be a String or an Object, but %v was given.',
603
603
  cookies
604
604
  );
605
605
  }
606
606
  if (headers != null && typeof headers !== "object" || Array.isArray(headers)) {
607
607
  throw new import_js_format8.InvalidArgumentError(
608
- 'The parameter "headers" of "createRequestHeaders" must be an Object, but %v was given.',
608
+ 'Parameter "headers" must be an Object, but %v was given.',
609
609
  headers
610
610
  );
611
611
  }
612
612
  headers = headers || {};
613
613
  if (encoding != null && typeof encoding !== "string") {
614
614
  throw new import_js_format8.InvalidArgumentError(
615
- 'The parameter "encoding" of "createRequestHeaders" must be a String, but %v was given.',
615
+ 'Parameter "encoding" must be a String, but %v was given.',
616
616
  encoding
617
617
  );
618
618
  }
@@ -662,6 +662,7 @@ __name(createRequestHeaders, "createRequestHeaders");
662
662
  var import_stream = require("stream");
663
663
  function createResponseMock() {
664
664
  const response = new import_stream.PassThrough();
665
+ response.statusCode = 200;
665
666
  patchEncoding(response);
666
667
  patchHeaders(response);
667
668
  patchBody(response);
@@ -768,7 +769,7 @@ var import_js_format9 = require("@e22m4u/js-format");
768
769
  function getRequestPathname(request) {
769
770
  if (!request || typeof request !== "object" || Array.isArray(request) || typeof request.url !== "string") {
770
771
  throw new import_js_format9.InvalidArgumentError(
771
- 'The first parameter of "getRequestPathname" must be an instance of IncomingMessage, but %v was given.',
772
+ 'Parameter "request" must be an instance of IncomingMessage, but %v was given.',
772
773
  request
773
774
  );
774
775
  }
@@ -891,7 +892,7 @@ var _RouterHookInvoker = class _RouterHookInvoker extends DebuggableService {
891
892
  invokeAndContinueUntilValueReceived(route, hookType, response, ...args) {
892
893
  if (!route || !(route instanceof Route)) {
893
894
  throw new import_js_format11.InvalidArgumentError(
894
- 'Parameter "route" must be a Route instance, but %v was given.',
895
+ 'Parameter "route" must be an instance of Route, but %v was given.',
895
896
  route
896
897
  );
897
898
  }
@@ -909,7 +910,7 @@ var _RouterHookInvoker = class _RouterHookInvoker extends DebuggableService {
909
910
  }
910
911
  if (!response || typeof response !== "object" || Array.isArray(response) || typeof response.headersSent !== "boolean") {
911
912
  throw new import_js_format11.InvalidArgumentError(
912
- 'Parameter "response" must be a ServerResponse instance, but %v was given.',
913
+ 'Parameter "response" must be an instance of ServerResponse, but %v was given.',
913
914
  response
914
915
  );
915
916
  }
@@ -1018,7 +1019,7 @@ function validateRouteDefinition(routeDef) {
1018
1019
  routeDef.preHandler.forEach((preHandler) => {
1019
1020
  if (typeof preHandler !== "function") {
1020
1021
  throw new import_js_format12.InvalidArgumentError(
1021
- "Route pre-handler must be a Function, but %v was given.",
1022
+ 'Hook "preHandler" must be a Function, but %v was given.',
1022
1023
  preHandler
1023
1024
  );
1024
1025
  }
@@ -1035,7 +1036,7 @@ function validateRouteDefinition(routeDef) {
1035
1036
  routeDef.postHandler.forEach((postHandler) => {
1036
1037
  if (typeof postHandler !== "function") {
1037
1038
  throw new import_js_format12.InvalidArgumentError(
1038
- "Route post-handler must be a Function, but %v was given.",
1039
+ 'Hook "postHandler" must be a Function, but %v was given.',
1039
1040
  postHandler
1040
1041
  );
1041
1042
  }
@@ -1064,7 +1065,8 @@ var HttpMethod = {
1064
1065
  POST: "POST",
1065
1066
  PUT: "PUT",
1066
1067
  PATCH: "PATCH",
1067
- DELETE: "DELETE"
1068
+ DELETE: "DELETE",
1069
+ OPTIONS: "OPTIONS"
1068
1070
  };
1069
1071
  var DEFAULT_META = Object.freeze({});
1070
1072
  var _Route = class _Route extends import_js_debug.Debuggable {
@@ -1203,7 +1205,7 @@ var _RouterOptions = class _RouterOptions extends DebuggableService {
1203
1205
  setRequestBodyBytesLimit(input) {
1204
1206
  if (typeof input !== "number" || input < 0) {
1205
1207
  throw new import_js_format13.InvalidArgumentError(
1206
- 'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v was given.',
1208
+ 'Option "requestBodyBytesLimit" must be a positive Number or 0, but %v was given.',
1207
1209
  input
1208
1210
  );
1209
1211
  }
@@ -1238,13 +1240,13 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1238
1240
  defineParser(mediaType, parser) {
1239
1241
  if (!mediaType || typeof mediaType !== "string") {
1240
1242
  throw new import_js_format14.InvalidArgumentError(
1241
- 'The parameter "mediaType" of BodyParser.defineParser must be a non-empty String, but %v was given.',
1243
+ 'Parameter "mediaType" must be a non-empty String, but %v was given.',
1242
1244
  mediaType
1243
1245
  );
1244
1246
  }
1245
1247
  if (!parser || typeof parser !== "function") {
1246
1248
  throw new import_js_format14.InvalidArgumentError(
1247
- 'The parameter "parser" of BodyParser.defineParser must be a Function, but %v was given.',
1249
+ 'Parameter "parser" must be a Function, but %v was given.',
1248
1250
  parser
1249
1251
  );
1250
1252
  }
@@ -1260,29 +1262,44 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1260
1262
  hasParser(mediaType) {
1261
1263
  if (!mediaType || typeof mediaType !== "string") {
1262
1264
  throw new import_js_format14.InvalidArgumentError(
1263
- 'The parameter "mediaType" of BodyParser.hasParser must be a non-empty String, but %v was given.',
1265
+ 'Parameter "mediaType" must be a non-empty String, but %v was given.',
1264
1266
  mediaType
1265
1267
  );
1266
1268
  }
1267
1269
  return Boolean(this._parsers[mediaType]);
1268
1270
  }
1269
1271
  /**
1270
- * Delete parser.
1272
+ * Get parser.
1271
1273
  *
1272
1274
  * @param {string} mediaType
1273
- * @returns {this}
1275
+ * @returns {Function}
1274
1276
  */
1275
- deleteParser(mediaType) {
1277
+ getParser(mediaType) {
1276
1278
  if (!mediaType || typeof mediaType !== "string") {
1277
1279
  throw new import_js_format14.InvalidArgumentError(
1278
- 'The parameter "mediaType" of BodyParser.deleteParser must be a non-empty String, but %v was given.',
1280
+ 'Parameter "mediaType" must be a non-empty String, but %v was given.',
1279
1281
  mediaType
1280
1282
  );
1281
1283
  }
1282
1284
  const parser = this._parsers[mediaType];
1283
1285
  if (!parser) {
1284
1286
  throw new import_js_format14.InvalidArgumentError(
1285
- "The parser of %v is not found.",
1287
+ "Media type %v does not have a parser.",
1288
+ mediaType
1289
+ );
1290
+ }
1291
+ return parser;
1292
+ }
1293
+ /**
1294
+ * Remove parser.
1295
+ *
1296
+ * @param {string} mediaType
1297
+ * @returns {this}
1298
+ */
1299
+ removeParser(mediaType) {
1300
+ if (!mediaType || typeof mediaType !== "string") {
1301
+ throw new import_js_format14.InvalidArgumentError(
1302
+ 'Parameter "mediaType" must be a non-empty String, but %v was given.',
1286
1303
  mediaType
1287
1304
  );
1288
1305
  }
@@ -1437,7 +1454,7 @@ var _RequestParser = class _RequestParser extends DebuggableService {
1437
1454
  parse(request) {
1438
1455
  if (!(request instanceof import_http3.IncomingMessage)) {
1439
1456
  throw new import_js_format15.InvalidArgumentError(
1440
- "The first parameter of RequestParser.parse must be an instance of IncomingMessage, but %v was given.",
1457
+ 'Parameter "request" must be an instance of IncomingMessage, but %v was given.',
1441
1458
  request
1442
1459
  );
1443
1460
  }
@@ -1476,7 +1493,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
1476
1493
  /**
1477
1494
  * Constructor.
1478
1495
  *
1479
- * @param {ServiceContainer} container
1496
+ * @param {ServiceContainer} [container]
1480
1497
  */
1481
1498
  constructor(container) {
1482
1499
  super(container);
@@ -1492,7 +1509,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
1492
1509
  const debug = this.getDebuggerFor(this.defineRoute);
1493
1510
  if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef)) {
1494
1511
  throw new import_js_format16.InvalidArgumentError(
1495
- "The route definition must be an Object, but %v was given.",
1512
+ "Route definition must be an Object, but %v was given.",
1496
1513
  routeDef
1497
1514
  );
1498
1515
  }
@@ -1562,6 +1579,35 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
1562
1579
  requestPath
1563
1580
  );
1564
1581
  }
1582
+ /**
1583
+ * Get allowed methods for request path.
1584
+ *
1585
+ * @param {string} requestPath
1586
+ * @returns {string[]}
1587
+ */
1588
+ getAllowedMethodsForRequestPath(requestPath) {
1589
+ if (typeof requestPath !== "string") {
1590
+ throw new import_js_format16.InvalidArgumentError(
1591
+ 'Parameter "requestPath" must be a String, but %v was given.',
1592
+ requestPath
1593
+ );
1594
+ }
1595
+ const debug = this.getDebuggerFor(this.getAllowedMethodsForRequestPath);
1596
+ const allowedMethods = [];
1597
+ for (const method of Object.values(HttpMethod)) {
1598
+ const rawTriePath = `${method}/${requestPath}`;
1599
+ const triePath = rawTriePath.replace(/\/+/g, "/");
1600
+ if (this._trie.match(triePath)) {
1601
+ allowedMethods.push(method);
1602
+ }
1603
+ }
1604
+ if (allowedMethods.length) {
1605
+ debug("Allowed methods for %v are: %l.", requestPath, allowedMethods);
1606
+ } else {
1607
+ debug("Path %v does not have allowed methods.", requestPath);
1608
+ }
1609
+ return allowedMethods;
1610
+ }
1565
1611
  };
1566
1612
  __name(_RouteRegistry, "RouteRegistry");
1567
1613
  var RouteRegistry = _RouteRegistry;
@@ -1716,28 +1762,28 @@ var _RequestContext = class _RequestContext {
1716
1762
  constructor(container, request, response, route) {
1717
1763
  if (!(0, import_js_service3.isServiceContainer)(container)) {
1718
1764
  throw new import_js_format17.InvalidArgumentError(
1719
- 'The parameter "container" of RequestContext.constructor must be an instance of ServiceContainer, but %v was given.',
1765
+ 'Parameter "container" must be an instance of ServiceContainer, but %v was given.',
1720
1766
  container
1721
1767
  );
1722
1768
  }
1723
1769
  this._container = container;
1724
1770
  if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
1725
1771
  throw new import_js_format17.InvalidArgumentError(
1726
- 'The parameter "request" of RequestContext.constructor must be an instance of IncomingMessage, but %v was given.',
1772
+ 'Parameter "request" must be an instance of IncomingMessage, but %v was given.',
1727
1773
  request
1728
1774
  );
1729
1775
  }
1730
1776
  this._request = request;
1731
1777
  if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
1732
1778
  throw new import_js_format17.InvalidArgumentError(
1733
- 'The parameter "response" of RequestContext.constructor must be an instance of ServerResponse, but %v was given.',
1779
+ 'Parameter "response" must be an instance of ServerResponse, but %v was given.',
1734
1780
  response
1735
1781
  );
1736
1782
  }
1737
1783
  this._response = response;
1738
1784
  if (!(route instanceof Route)) {
1739
1785
  throw new import_js_format17.InvalidArgumentError(
1740
- 'The parameter "route" of RequestContext.constructor must be an instance of Route, but %v was given.',
1786
+ 'Parameter "route" must be an instance of Route, but %v was given.',
1741
1787
  route
1742
1788
  );
1743
1789
  }
@@ -1792,7 +1838,7 @@ function validateRouterBranchDefinition(branchDef) {
1792
1838
  branchDef.preHandler.forEach((preHandler) => {
1793
1839
  if (typeof preHandler !== "function") {
1794
1840
  throw new import_js_format18.InvalidArgumentError(
1795
- "Route pre-handler must be a Function, but %v was given.",
1841
+ 'Hook "preHandler" must be a Function, but %v was given.',
1796
1842
  preHandler
1797
1843
  );
1798
1844
  }
@@ -1809,7 +1855,7 @@ function validateRouterBranchDefinition(branchDef) {
1809
1855
  branchDef.postHandler.forEach((postHandler) => {
1810
1856
  if (typeof postHandler !== "function") {
1811
1857
  throw new import_js_format18.InvalidArgumentError(
1812
- "Route post-handler must be a Function, but %v was given.",
1858
+ 'Hook "postHandler" must be a Function, but %v was given.',
1813
1859
  postHandler
1814
1860
  );
1815
1861
  }
@@ -1924,17 +1970,17 @@ var _RouterBranch = class _RouterBranch extends DebuggableService {
1924
1970
  * @param {RouterBranch} [parentBranch]
1925
1971
  */
1926
1972
  constructor(router, branchDef, parentBranch) {
1927
- super(router.container);
1928
1973
  if (!(router instanceof TrieRouter)) {
1929
1974
  throw new import_js_format19.InvalidArgumentError(
1930
- 'Parameter "router" must be a TrieRouter instance, but %v was given.',
1975
+ 'Parameter "router" must be an instance of TrieRouter, but %v was given.',
1931
1976
  router
1932
1977
  );
1933
1978
  }
1979
+ super(router.container);
1934
1980
  this._router = router;
1935
1981
  if (parentBranch !== void 0 && !(parentBranch instanceof _RouterBranch)) {
1936
1982
  throw new import_js_format19.InvalidArgumentError(
1937
- 'Parameter "parentBranch" must be a RouterBranch instance, but %v was given.',
1983
+ 'Parameter "parentBranch" must be an instance of RouterBranch, but %v was given.',
1938
1984
  parentBranch
1939
1985
  );
1940
1986
  }
@@ -2154,9 +2200,9 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2154
2200
  * router.defineRoute({
2155
2201
  * method: HttpMethod.POST, // Request method.
2156
2202
  * path: '/users/:id', // The path template may have parameters.
2157
- * preHandler(ctx) { ... }, // The "preHandler" executes before a route handler.
2158
- * handler(ctx) { ... }, // Request handler function.
2159
- * postHandler(ctx, data) { ... }, // The "postHandler" executes after a route handler.
2203
+ * preHandler(ctx) { ... }, // The hook "preHandler" executes before a route handler.
2204
+ * handler(ctx) { ... }, // Route handler function.
2205
+ * postHandler(ctx, data) { ... }, // The hook "postHandler" executes after a route handler.
2160
2206
  * });
2161
2207
  * ```
2162
2208
  *
@@ -2218,9 +2264,25 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2218
2264
  async _handleRequest(request, response) {
2219
2265
  const debug = this.getDebuggerFor(this._handleRequest);
2220
2266
  const requestPath = getRequestPathname(request);
2267
+ const routeRegistry = this.getService(RouteRegistry);
2221
2268
  debug("Handling an incoming request %s %v.", request.method, requestPath);
2222
2269
  const resolved = this.getService(RouteRegistry).matchRouteByRequest(request);
2223
2270
  if (!resolved) {
2271
+ if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
2272
+ const allowedMethods = routeRegistry.getAllowedMethodsForRequestPath(requestPath);
2273
+ if (allowedMethods.length > 0) {
2274
+ debug("Auto-handling OPTIONS request.");
2275
+ if (!allowedMethods.includes("OPTIONS")) {
2276
+ allowedMethods.push("OPTIONS");
2277
+ }
2278
+ const allowHeader = allowedMethods.join(", ");
2279
+ response.statusCode = 204;
2280
+ response.setHeader("Allow", allowHeader);
2281
+ response.setHeader("Access-Control-Allow-Methods", allowHeader);
2282
+ response.end();
2283
+ return;
2284
+ }
2285
+ }
2224
2286
  debug(
2225
2287
  "No route found for the request %s %v.",
2226
2288
  request.method,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-trie-router",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
5
5
  "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
6
6
  "license": "MIT",
@@ -47,8 +47,8 @@
47
47
  "statuses": "~2.0.2"
48
48
  },
49
49
  "devDependencies": {
50
- "@commitlint/cli": "~20.4.1",
51
- "@commitlint/config-conventional": "~20.4.1",
50
+ "@commitlint/cli": "~20.4.2",
51
+ "@commitlint/config-conventional": "~20.4.2",
52
52
  "@eslint/js": "~9.39.2",
53
53
  "@types/chai": "~5.2.3",
54
54
  "@types/chai-as-promised": "~8.0.2",
@@ -61,7 +61,7 @@
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.6.0",
64
+ "eslint-plugin-jsdoc": "~62.7.0",
65
65
  "eslint-plugin-mocha": "~11.2.0",
66
66
  "globals": "~17.3.0",
67
67
  "husky": "~9.1.7",