Browse Source

refactor: rollback

e22m4u 3 days ago
parent
commit
c903c29194
54 changed files with 1069 additions and 140 deletions
  1. 5 0
      .mocharc.cjs
  2. 0 5
      .mocharc.json
  3. 84 82
      dist/cjs/index.cjs
  4. 2 5
      eslint.config.js
  5. 0 7
      jsconfig.json
  6. 0 0
      mocha-setup.js
  7. 16 13
      package.json
  8. 6 0
      src/debuggable-service.d.ts
  9. 25 0
      src/hooks/hook-invoker.d.ts
  10. 4 2
      src/hooks/hook-invoker.spec.js
  11. 93 0
      src/hooks/hook-registry.d.ts
  12. 2 1
      src/hooks/hook-registry.spec.js
  13. 2 0
      src/hooks/index.d.ts
  14. 9 0
      src/index.d.ts
  15. 52 0
      src/parsers/body-parser.d.ts
  16. 3 6
      src/parsers/body-parser.js
  17. 3 6
      src/parsers/body-parser.spec.js
  18. 15 0
      src/parsers/cookies-parser.d.ts
  19. 2 1
      src/parsers/cookies-parser.js
  20. 4 0
      src/parsers/index.d.ts
  21. 21 0
      src/parsers/query-parser.d.ts
  22. 34 0
      src/parsers/request-parser.d.ts
  23. 101 0
      src/request-context.d.ts
  24. 39 0
      src/route-registry.d.ts
  25. 2 1
      src/route-registry.spec.js
  26. 100 0
      src/route.d.ts
  27. 4 2
      src/route.spec.js
  28. 18 0
      src/router-options.d.ts
  29. 15 0
      src/senders/data-sender.d.ts
  30. 30 0
      src/senders/error-sender.d.ts
  31. 5 3
      src/senders/error-sender.spec.js
  32. 2 0
      src/senders/index.d.ts
  33. 104 0
      src/trie-router.d.ts
  34. 10 5
      src/trie-router.spec.js
  35. 19 0
      src/types.d.ts
  36. 6 0
      src/utils/clone-deep.d.ts
  37. 6 0
      src/utils/create-cookies-string.d.ts
  38. 11 0
      src/utils/create-debugger.d.ts
  39. 14 0
      src/utils/create-error.d.ts
  40. 2 1
      src/utils/create-error.js
  41. 27 0
      src/utils/create-request-mock.d.ts
  42. 17 0
      src/utils/create-response-mock.d.ts
  43. 18 0
      src/utils/create-route-mock.d.ts
  44. 26 0
      src/utils/fetch-request-body.d.ts
  45. 8 0
      src/utils/get-request-pathname.d.ts
  46. 16 0
      src/utils/index.d.ts
  47. 10 0
      src/utils/is-promise.d.ts
  48. 9 0
      src/utils/is-readable-stream.d.ts
  49. 8 0
      src/utils/is-response-sent.d.ts
  50. 9 0
      src/utils/is-writable-stream.d.ts
  51. 15 0
      src/utils/parse-content-type.d.ts
  52. 19 0
      src/utils/parse-cookies.d.ts
  53. 6 0
      src/utils/to-camel-case.d.ts
  54. 11 0
      tsconfig.json

+ 5 - 0
.mocharc.cjs

@@ -0,0 +1,5 @@
+module.exports = {
+  extension: ['js'],
+  spec: 'src/**/*.spec.js',
+  require: ['./mocha-setup.js'],
+}

+ 0 - 5
.mocharc.json

@@ -1,5 +0,0 @@
-{
-  "extension": ["js"],
-  "spec": "src/**/*.spec.js",
-  "require": "./mocha.setup.js"
-}

+ 84 - 82
dist/cjs/index.cjs

@@ -1,3 +1,4 @@
+"use strict";
 var __create = Object.create;
 var __defProp = Object.defineProperty;
 var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -70,11 +71,11 @@ __export(index_exports, {
 module.exports = __toCommonJS(index_exports);
 
 // src/route.js
-var import_js_format13 = require("@e22m4u/js-format");
+var import_js_format14 = require("@e22m4u/js-format");
 var import_js_debug = require("@e22m4u/js-debug");
 
 // src/hooks/hook-invoker.js
-var import_js_format12 = require("@e22m4u/js-format");
+var import_js_format13 = require("@e22m4u/js-format");
 
 // src/debuggable-service.js
 var import_js_service = require("@e22m4u/js-service");
@@ -130,14 +131,15 @@ __name(isPromise, "isPromise");
 
 // src/utils/create-error.js
 var import_js_format = require("@e22m4u/js-format");
+var import_js_format2 = require("@e22m4u/js-format");
 function createError(errorCtor, message, ...args) {
   if (typeof errorCtor !== "function")
-    throw new import_js_format.Errorf(
+    throw new import_js_format2.Errorf(
       'The first argument of "createError" should be a constructor, but %v was given.',
       errorCtor
     );
   if (message != null && typeof message !== "string")
-    throw new import_js_format.Errorf(
+    throw new import_js_format2.Errorf(
       'The second argument of "createError" should be a String, but %v was given.',
       message
     );
@@ -148,10 +150,10 @@ function createError(errorCtor, message, ...args) {
 __name(createError, "createError");
 
 // src/utils/parse-cookies.js
-var import_js_format2 = require("@e22m4u/js-format");
+var import_js_format3 = require("@e22m4u/js-format");
 function parseCookies(input) {
   if (typeof input !== "string")
-    throw new import_js_format2.Errorf(
+    throw new import_js_format3.Errorf(
       'The first parameter of "parseCookies" should be a String, but %v was given.',
       input
     );
@@ -165,10 +167,10 @@ function parseCookies(input) {
 __name(parseCookies, "parseCookies");
 
 // src/utils/to-camel-case.js
-var import_js_format3 = require("@e22m4u/js-format");
+var import_js_format4 = require("@e22m4u/js-format");
 function toCamelCase(input) {
   if (typeof input !== "string")
-    throw new import_js_format3.Errorf(
+    throw new import_js_format4.Errorf(
       'The first argument of "toCamelCase" should be a String, but %v was given.',
       input
     );
@@ -178,26 +180,26 @@ __name(toCamelCase, "toCamelCase");
 
 // src/utils/create-debugger.js
 var import_debug = __toESM(require("debug"), 1);
-var import_js_format4 = require("@e22m4u/js-format");
+var import_js_format5 = require("@e22m4u/js-format");
 function createDebugger(name) {
   if (typeof name !== "string")
-    throw new import_js_format4.Errorf(
+    throw new import_js_format5.Errorf(
       'The first argument of "createDebugger" should be a String, but %v was given.',
       name
     );
   const debug = (0, import_debug.default)(`jsTrieRouter:${name}`);
   return function(message, ...args) {
-    const interpolatedMessage = (0, import_js_format4.format)(message, ...args);
+    const interpolatedMessage = (0, import_js_format5.format)(message, ...args);
     return debug(interpolatedMessage);
   };
 }
 __name(createDebugger, "createDebugger");
 
 // src/utils/is-response-sent.js
-var import_js_format5 = require("@e22m4u/js-format");
+var import_js_format6 = require("@e22m4u/js-format");
 function isResponseSent(response) {
   if (!response || typeof response !== "object" || Array.isArray(response) || typeof response.headersSent !== "boolean") {
-    throw new import_js_format5.Errorf(
+    throw new import_js_format6.Errorf(
       'The first argument of "isResponseSent" should be an instance of ServerResponse, but %v was given.',
       response
     );
@@ -224,10 +226,10 @@ function isReadableStream(value) {
 __name(isReadableStream, "isReadableStream");
 
 // src/utils/parse-content-type.js
-var import_js_format6 = require("@e22m4u/js-format");
+var import_js_format7 = require("@e22m4u/js-format");
 function parseContentType(input) {
   if (typeof input !== "string")
-    throw new import_js_format6.Errorf(
+    throw new import_js_format7.Errorf(
       'The parameter "input" of "parseContentType" should be a String, but %v was given.',
       input
     );
@@ -253,7 +255,7 @@ __name(isWritableStream, "isWritableStream");
 // src/utils/fetch-request-body.js
 var import_http_errors = __toESM(require("http-errors"), 1);
 var import_http = require("http");
-var import_js_format7 = require("@e22m4u/js-format");
+var import_js_format8 = require("@e22m4u/js-format");
 var CHARACTER_ENCODING_LIST = [
   "ascii",
   "utf8",
@@ -266,12 +268,12 @@ var CHARACTER_ENCODING_LIST = [
 ];
 function fetchRequestBody(request, bodyBytesLimit = 0) {
   if (!(request instanceof import_http.IncomingMessage))
-    throw new import_js_format7.Errorf(
+    throw new import_js_format8.Errorf(
       'The first parameter of "fetchRequestBody" should be an IncomingMessage instance, but %v was given.',
       request
     );
   if (typeof bodyBytesLimit !== "number")
-    throw new import_js_format7.Errorf(
+    throw new import_js_format8.Errorf(
       'The parameter "bodyBytesLimit" of "fetchRequestBody" should be a number, but %v was given.',
       bodyBytesLimit
     );
@@ -349,13 +351,13 @@ var import_net = require("net");
 var import_tls = require("tls");
 var import_http2 = require("http");
 var import_querystring = __toESM(require("querystring"), 1);
-var import_js_format9 = require("@e22m4u/js-format");
+var import_js_format10 = require("@e22m4u/js-format");
 
 // src/utils/create-cookies-string.js
-var import_js_format8 = require("@e22m4u/js-format");
+var import_js_format9 = require("@e22m4u/js-format");
 function createCookiesString(data) {
   if (!data || typeof data !== "object" || Array.isArray(data))
-    throw new import_js_format8.Errorf(
+    throw new import_js_format9.Errorf(
       'The first parameter of "createCookiesString" should be an Object, but %v was given.',
       data
     );
@@ -373,78 +375,78 @@ __name(createCookiesString, "createCookiesString");
 // src/utils/create-request-mock.js
 function createRequestMock(patch) {
   if (patch != null && typeof patch !== "object" || Array.isArray(patch)) {
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The first parameter of "createRequestMock" should be an Object, but %v was given.',
       patch
     );
   }
   patch = patch || {};
   if (patch.host != null && typeof patch.host !== "string")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "host" of "createRequestMock" should be a String, but %v was given.',
       patch.host
     );
   if (patch.method != null && typeof patch.method !== "string")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "method" of "createRequestMock" should be a String, but %v was given.',
       patch.method
     );
   if (patch.secure != null && typeof patch.secure !== "boolean")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "secure" of "createRequestMock" should be a Boolean, but %v was given.',
       patch.secure
     );
   if (patch.path != null && typeof patch.path !== "string")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "path" of "createRequestMock" should be a String, but %v was given.',
       patch.path
     );
   if (patch.query != null && typeof patch.query !== "object" && typeof patch.query !== "string" || Array.isArray(patch.query)) {
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "query" of "createRequestMock" should be a String or Object, but %v was given.',
       patch.query
     );
   }
   if (patch.cookies != null && typeof patch.cookies !== "string" && typeof patch.cookies !== "object" || Array.isArray(patch.cookies)) {
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "cookies" of "createRequestMock" should be a String or Object, but %v was given.',
       patch.cookies
     );
   }
   if (patch.headers != null && typeof patch.headers !== "object" || Array.isArray(patch.headers)) {
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "headers" of "createRequestMock" should be an Object, but %v was given.',
       patch.headers
     );
   }
   if (patch.stream != null && !isReadableStream(patch.stream))
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "stream" of "createRequestMock" should be a Stream, but %v was given.',
       patch.stream
     );
   if (patch.encoding != null) {
     if (typeof patch.encoding !== "string")
-      throw new import_js_format9.Errorf(
+      throw new import_js_format10.Errorf(
         'The parameter "encoding" of "createRequestMock" should be a String, but %v was given.',
         patch.encoding
       );
     if (!CHARACTER_ENCODING_LIST.includes(patch.encoding))
-      throw new import_js_format9.Errorf(
+      throw new import_js_format10.Errorf(
         "Character encoding %v is not supported.",
         patch.encoding
       );
   }
   if (patch.stream) {
     if (patch.secure != null)
-      throw new import_js_format9.Errorf(
+      throw new import_js_format10.Errorf(
         'The "createRequestMock" does not allow specifying the "stream" and "secure" options simultaneously.'
       );
     if (patch.body != null)
-      throw new import_js_format9.Errorf(
+      throw new import_js_format10.Errorf(
         'The "createRequestMock" does not allow specifying the "stream" and "body" options simultaneously.'
       );
     if (patch.encoding != null)
-      throw new import_js_format9.Errorf(
+      throw new import_js_format10.Errorf(
         'The "createRequestMock" does not allow specifying the "stream" and "encoding" options simultaneously.'
       );
   }
@@ -464,7 +466,7 @@ function createRequestMock(patch) {
 __name(createRequestMock, "createRequestMock");
 function createRequestStream(secure, body, encoding) {
   if (encoding != null && typeof encoding !== "string")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "encoding" of "createRequestStream" should be a String, but %v was given.',
       encoding
     );
@@ -487,12 +489,12 @@ function createRequestStream(secure, body, encoding) {
 __name(createRequestStream, "createRequestStream");
 function createRequestUrl(path, query) {
   if (typeof path !== "string")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "path" of "createRequestUrl" should be a String, but %v was given.',
       path
     );
   if (query != null && typeof query !== "string" && typeof query !== "object" || Array.isArray(query)) {
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "query" of "createRequestUrl" should be a String or Object, but %v was given.',
       query
     );
@@ -509,32 +511,32 @@ function createRequestUrl(path, query) {
 __name(createRequestUrl, "createRequestUrl");
 function createRequestHeaders(host, secure, body, cookies, encoding, headers) {
   if (host != null && typeof host !== "string")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "host" of "createRequestHeaders" a non-empty String, but %v was given.',
       host
     );
   host = host || "localhost";
   if (secure != null && typeof secure !== "boolean")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "secure" of "createRequestHeaders" should be a String, but %v was given.',
       secure
     );
   secure = Boolean(secure);
   if (cookies != null && typeof cookies !== "object" && typeof cookies !== "string" || Array.isArray(cookies)) {
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "cookies" of "createRequestHeaders" should be a String or Object, but %v was given.',
       cookies
     );
   }
   if (headers != null && typeof headers !== "object" || Array.isArray(headers)) {
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "headers" of "createRequestHeaders" should be an Object, but %v was given.',
       headers
     );
   }
   headers = headers || {};
   if (encoding != null && typeof encoding !== "string")
-    throw new import_js_format9.Errorf(
+    throw new import_js_format10.Errorf(
       'The parameter "encoding" of "createRequestHeaders" should be a String, but %v was given.',
       encoding
     );
@@ -683,10 +685,10 @@ function patchBody(response) {
 __name(patchBody, "patchBody");
 
 // src/utils/get-request-pathname.js
-var import_js_format10 = require("@e22m4u/js-format");
+var import_js_format11 = require("@e22m4u/js-format");
 function getRequestPathname(request) {
   if (!request || typeof request !== "object" || Array.isArray(request) || typeof request.url !== "string") {
-    throw new import_js_format10.Errorf(
+    throw new import_js_format11.Errorf(
       'The first argument of "getRequestPathname" should be an instance of IncomingMessage, but %v was given.',
       request
     );
@@ -696,7 +698,7 @@ function getRequestPathname(request) {
 __name(getRequestPathname, "getRequestPathname");
 
 // src/hooks/hook-registry.js
-var import_js_format11 = require("@e22m4u/js-format");
+var import_js_format12 = require("@e22m4u/js-format");
 var RouterHookType = {
   PRE_HANDLER: "preHandler",
   POST_HANDLER: "postHandler"
@@ -718,11 +720,11 @@ var _HookRegistry = class _HookRegistry {
    */
   addHook(type, hook) {
     if (!type || typeof type !== "string")
-      throw new import_js_format11.Errorf("The hook type is required, but %v was given.", type);
+      throw new import_js_format12.Errorf("The hook type is required, but %v was given.", type);
     if (!Object.values(RouterHookType).includes(type))
-      throw new import_js_format11.Errorf("The hook type %v is not supported.", type);
+      throw new import_js_format12.Errorf("The hook type %v is not supported.", type);
     if (!hook || typeof hook !== "function")
-      throw new import_js_format11.Errorf(
+      throw new import_js_format12.Errorf(
         "The hook %v should be a Function, but %v was given.",
         type,
         hook
@@ -741,11 +743,11 @@ var _HookRegistry = class _HookRegistry {
    */
   hasHook(type, hook) {
     if (!type || typeof type !== "string")
-      throw new import_js_format11.Errorf("The hook type is required, but %v was given.", type);
+      throw new import_js_format12.Errorf("The hook type is required, but %v was given.", type);
     if (!Object.values(RouterHookType).includes(type))
-      throw new import_js_format11.Errorf("The hook type %v is not supported.", type);
+      throw new import_js_format12.Errorf("The hook type %v is not supported.", type);
     if (!hook || typeof hook !== "function")
-      throw new import_js_format11.Errorf(
+      throw new import_js_format12.Errorf(
         "The hook %v should be a Function, but %v was given.",
         type,
         hook
@@ -761,9 +763,9 @@ var _HookRegistry = class _HookRegistry {
    */
   getHooks(type) {
     if (!type || typeof type !== "string")
-      throw new import_js_format11.Errorf("The hook type is required, but %v was given.", type);
+      throw new import_js_format12.Errorf("The hook type is required, but %v was given.", type);
     if (!Object.values(RouterHookType).includes(type))
-      throw new import_js_format11.Errorf("The hook type %v is not supported.", type);
+      throw new import_js_format12.Errorf("The hook type %v is not supported.", type);
     return this._hooks.get(type) || [];
   }
 };
@@ -783,19 +785,19 @@ var _HookInvoker = class _HookInvoker extends DebuggableService {
    */
   invokeAndContinueUntilValueReceived(route, hookType, response, ...args) {
     if (!route || !(route instanceof Route))
-      throw new import_js_format12.Errorf(
+      throw new import_js_format13.Errorf(
         'The parameter "route" of the HookInvoker.invokeAndContinueUntilValueReceived should be a Route instance, but %v was given.',
         route
       );
     if (!hookType || typeof hookType !== "string")
-      throw new import_js_format12.Errorf(
+      throw new import_js_format13.Errorf(
         'The parameter "hookType" of the HookInvoker.invokeAndContinueUntilValueReceived should be a non-empty String, but %v was given.',
         hookType
       );
     if (!Object.values(RouterHookType).includes(hookType))
-      throw new import_js_format12.Errorf("The hook type %v is not supported.", hookType);
+      throw new import_js_format13.Errorf("The hook type %v is not supported.", hookType);
     if (!response || typeof response !== "object" || Array.isArray(response) || typeof response.headersSent !== "boolean") {
-      throw new import_js_format12.Errorf(
+      throw new import_js_format13.Errorf(
         'The parameter "response" of the HookInvoker.invokeAndContinueUntilValueReceived should be a ServerResponse instance, but %v was given.',
         response
       );
@@ -940,30 +942,30 @@ var _Route = class _Route extends import_js_debug.Debuggable {
       noInstantiationMessage: true
     });
     if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
-      throw new import_js_format13.Errorf(
+      throw new import_js_format14.Errorf(
         "The first parameter of Route.constructor should be an Object, but %v was given.",
         routeDef
       );
     if (!routeDef.method || typeof routeDef.method !== "string")
-      throw new import_js_format13.Errorf(
+      throw new import_js_format14.Errorf(
         'The option "method" of the Route should be a non-empty String, but %v was given.',
         routeDef.method
       );
     this._method = routeDef.method.toUpperCase();
     if (typeof routeDef.path !== "string")
-      throw new import_js_format13.Errorf(
+      throw new import_js_format14.Errorf(
         'The option "path" of the Route should be a String, but %v was given.',
         routeDef.path
       );
     this._path = routeDef.path;
     if (typeof routeDef.handler !== "function")
-      throw new import_js_format13.Errorf(
+      throw new import_js_format14.Errorf(
         'The option "handler" of the Route should be a Function, but %v was given.',
         routeDef.handler
       );
     if (routeDef.meta != null) {
       if (typeof routeDef.meta !== "object" || Array.isArray(routeDef.meta))
-        throw new import_js_format13.Errorf(
+        throw new import_js_format14.Errorf(
           'The option "meta" of the Route should be a plain Object, but %v was given.',
           routeDef.meta
         );
@@ -1006,10 +1008,10 @@ var Route = _Route;
 
 // src/parsers/body-parser.js
 var import_http_errors2 = __toESM(require("http-errors"), 1);
-var import_js_format15 = require("@e22m4u/js-format");
+var import_js_format16 = require("@e22m4u/js-format");
 
 // src/router-options.js
-var import_js_format14 = require("@e22m4u/js-format");
+var import_js_format15 = require("@e22m4u/js-format");
 var _RouterOptions = class _RouterOptions extends DebuggableService {
   /**
    * Request body bytes limit.
@@ -1035,7 +1037,7 @@ var _RouterOptions = class _RouterOptions extends DebuggableService {
    */
   setRequestBodyBytesLimit(input) {
     if (typeof input !== "number" || input < 0)
-      throw new import_js_format14.Errorf(
+      throw new import_js_format15.Errorf(
         'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v was given.',
         input
       );
@@ -1068,12 +1070,12 @@ var _BodyParser = class _BodyParser extends DebuggableService {
    */
   defineParser(mediaType, parser) {
     if (!mediaType || typeof mediaType !== "string")
-      throw new import_js_format15.Errorf(
+      throw new import_js_format16.Errorf(
         'The parameter "mediaType" of BodyParser.defineParser should be a non-empty String, but %v was given.',
         mediaType
       );
     if (!parser || typeof parser !== "function")
-      throw new import_js_format15.Errorf(
+      throw new import_js_format16.Errorf(
         'The parameter "parser" of BodyParser.defineParser should be a Function, but %v was given.',
         parser
       );
@@ -1088,7 +1090,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
    */
   hasParser(mediaType) {
     if (!mediaType || typeof mediaType !== "string")
-      throw new import_js_format15.Errorf(
+      throw new import_js_format16.Errorf(
         'The parameter "mediaType" of BodyParser.hasParser should be a non-empty String, but %v was given.',
         mediaType
       );
@@ -1102,12 +1104,12 @@ var _BodyParser = class _BodyParser extends DebuggableService {
    */
   deleteParser(mediaType) {
     if (!mediaType || typeof mediaType !== "string")
-      throw new import_js_format15.Errorf(
+      throw new import_js_format16.Errorf(
         'The parameter "mediaType" of BodyParser.deleteParser should be a non-empty String, but %v was given.',
         mediaType
       );
     const parser = this._parsers[mediaType];
-    if (!parser) throw new import_js_format15.Errorf("The parser of %v is not found.", mediaType);
+    if (!parser) throw new import_js_format16.Errorf("The parser of %v is not found.", mediaType);
     delete this._parsers[mediaType];
     return this;
   }
@@ -1236,7 +1238,7 @@ var CookiesParser = _CookiesParser;
 
 // src/parsers/request-parser.js
 var import_http3 = require("http");
-var import_js_format16 = require("@e22m4u/js-format");
+var import_js_format17 = require("@e22m4u/js-format");
 var _RequestParser = class _RequestParser extends DebuggableService {
   /**
    * Parse.
@@ -1246,7 +1248,7 @@ var _RequestParser = class _RequestParser extends DebuggableService {
    */
   parse(request) {
     if (!(request instanceof import_http3.IncomingMessage))
-      throw new import_js_format16.Errorf(
+      throw new import_js_format17.Errorf(
         "The first argument of RequestParser.parse should be an instance of IncomingMessage, but %v was given.",
         request
       );
@@ -1278,7 +1280,7 @@ __name(_RequestParser, "RequestParser");
 var RequestParser = _RequestParser;
 
 // src/route-registry.js
-var import_js_format17 = require("@e22m4u/js-format");
+var import_js_format18 = require("@e22m4u/js-format");
 var import_js_path_trie = require("@e22m4u/js-path-trie");
 var import_js_service2 = require("@e22m4u/js-service");
 var _RouteRegistry = class _RouteRegistry extends DebuggableService {
@@ -1300,7 +1302,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
   defineRoute(routeDef) {
     const debug = this.getDebuggerFor(this.defineRoute);
     if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
-      throw new import_js_format17.Errorf(
+      throw new import_js_format18.Errorf(
         "The route definition should be an Object, but %v was given.",
         routeDef
       );
@@ -1363,7 +1365,7 @@ __name(_RouteRegistry, "RouteRegistry");
 var RouteRegistry = _RouteRegistry;
 
 // src/request-context.js
-var import_js_format18 = require("@e22m4u/js-format");
+var import_js_format19 = require("@e22m4u/js-format");
 var import_js_service3 = require("@e22m4u/js-service");
 var _RequestContext = class _RequestContext {
   /**
@@ -1509,27 +1511,27 @@ var _RequestContext = class _RequestContext {
    */
   constructor(container, request, response, route) {
     if (!(0, import_js_service3.isServiceContainer)(container))
-      throw new import_js_format18.Errorf(
+      throw new import_js_format19.Errorf(
         'The parameter "container" of RequestContext.constructor should be an instance of ServiceContainer, but %v was given.',
         container
       );
     this._container = container;
     if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
-      throw new import_js_format18.Errorf(
+      throw new import_js_format19.Errorf(
         'The parameter "request" of RequestContext.constructor should be an instance of IncomingMessage, but %v was given.',
         request
       );
     }
     this._request = request;
     if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
-      throw new import_js_format18.Errorf(
+      throw new import_js_format19.Errorf(
         'The parameter "response" of RequestContext.constructor should be an instance of ServerResponse, but %v was given.',
         response
       );
     }
     this._response = response;
     if (!(route instanceof Route)) {
-      throw new import_js_format18.Errorf(
+      throw new import_js_format19.Errorf(
         'The parameter "route" of RequestContext.constructor should be an instance of Route, but %v was given.',
         route
       );
@@ -1545,7 +1547,7 @@ var import_js_service4 = require("@e22m4u/js-service");
 var import_http4 = require("http");
 
 // src/senders/data-sender.js
-var import_js_format19 = require("@e22m4u/js-format");
+var import_js_format20 = require("@e22m4u/js-format");
 var _DataSender = class _DataSender extends DebuggableService {
   /**
    * Send.
@@ -1584,7 +1586,7 @@ var _DataSender = class _DataSender extends DebuggableService {
           debugMsg = "The Buffer was sent as binary data.";
         } else {
           response.setHeader("content-type", "application/json");
-          debugMsg = (0, import_js_format19.format)("The %v was sent as JSON.", typeof data);
+          debugMsg = (0, import_js_format20.format)("The %v was sent as JSON.", typeof data);
           data = JSON.stringify(data);
         }
         break;

+ 2 - 5
eslint.config.js

@@ -2,7 +2,6 @@ import globals from 'globals';
 import eslintJs from '@eslint/js';
 import eslintJsdocPlugin from 'eslint-plugin-jsdoc';
 import eslintMochaPlugin from 'eslint-plugin-mocha';
-import eslintImportPlugin from 'eslint-plugin-import';
 import eslintPrettierConfig from 'eslint-config-prettier';
 import eslintChaiExpectPlugin from 'eslint-plugin-chai-expect';
 
@@ -17,17 +16,15 @@ export default [{
   plugins: {
     'jsdoc': eslintJsdocPlugin,
     'mocha': eslintMochaPlugin,
-    'import': eslintImportPlugin,
     'chai-expect': eslintChaiExpectPlugin,
   },
   rules: {
     ...eslintJs.configs.recommended.rules,
     ...eslintPrettierConfig.rules,
-    ...eslintImportPlugin.flatConfigs.recommended.rules,
+    ...eslintJsdocPlugin.configs['flat/recommended-error'].rules,
     ...eslintMochaPlugin.configs.recommended.rules,
     ...eslintChaiExpectPlugin.configs['recommended-flat'].rules,
-    ...eslintJsdocPlugin.configs['flat/recommended-error'].rules,
-    'no-duplicate-imports': 'error',
+    'no-unused-vars': ['error', {'caughtErrors': 'none'}],
     'jsdoc/reject-any-type': 0,
     'jsdoc/reject-function-type': 0,
     'jsdoc/require-param-description': 0,

+ 0 - 7
jsconfig.json

@@ -1,7 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "es2022",
-    "module": "NodeNext",
-    "moduleResolution": "NodeNext"
-  }
-}

+ 0 - 0
mocha.setup.js → mocha-setup.js


+ 16 - 13
package.json

@@ -1,6 +1,6 @@
 {
   "name": "@e22m4u/js-trie-router",
-  "version": "0.5.0",
+  "version": "0.4.4",
   "description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
   "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
   "license": "MIT",
@@ -11,15 +11,17 @@
     "server",
     "nodejs"
   ],
-  "homepage": "https://gitrepos.ru/e22m4u/js-trie-router",
+  "homepage": "https://github.com/e22m4u/js-trie-router",
   "repository": {
     "type": "git",
-    "url": "git+https://gitrepos.ru/e22m4u/js-trie-router.git"
+    "url": "git+https://github.com/e22m4u/js-trie-router.git"
   },
   "type": "module",
+  "types": "./src/index.d.ts",
   "module": "./src/index.js",
   "main": "./dist/cjs/index.cjs",
   "exports": {
+    "types": "./src/index.d.ts",
     "import": "./src/index.js",
     "require": "./dist/cjs/index.cjs"
   },
@@ -27,19 +29,19 @@
     "node": ">=16"
   },
   "scripts": {
-    "lint": "eslint ./src",
-    "lint:fix": "eslint ./src --fix",
+    "lint": "tsc && eslint ./src",
+    "lint:fix": "tsc && eslint ./src --fix",
     "format": "prettier --write \"./src/**/*.{js,ts}\"",
     "test": "npm run lint && c8 --reporter=text-summary mocha --bail",
     "test:coverage": "npm run lint && c8 --reporter=text mocha",
-    "build:cjs": "rimraf ./dist/cjs && node build-cjs.js",
+    "build:cjs": "rimraf ./dist/cjs && node --no-warnings=ExperimentalWarning build-cjs.js",
     "prepare": "husky"
   },
   "dependencies": {
-    "@e22m4u/js-debug": "~0.4.0",
-    "@e22m4u/js-format": "~0.3.0",
-    "@e22m4u/js-path-trie": "~0.1.0",
-    "@e22m4u/js-service": "~0.5.0",
+    "@e22m4u/js-debug": "~0.3.3",
+    "@e22m4u/js-format": "~0.2.1",
+    "@e22m4u/js-path-trie": "~0.0.13",
+    "@e22m4u/js-service": "~0.4.6",
     "debug": "~4.4.3",
     "http-errors": "~2.0.1",
     "statuses": "~2.0.2"
@@ -48,6 +50,7 @@
     "@commitlint/cli": "~20.1.0",
     "@commitlint/config-conventional": "~20.0.0",
     "@eslint/js": "~9.39.1",
+    "@types/chai-as-promised": "~8.0.2",
     "c8": "~10.1.3",
     "chai": "~6.2.1",
     "chai-as-promised": "~8.0.2",
@@ -55,13 +58,13 @@
     "eslint": "~9.39.1",
     "eslint-config-prettier": "~10.1.8",
     "eslint-plugin-chai-expect": "~3.1.0",
-    "eslint-plugin-import": "~2.32.0",
     "eslint-plugin-jsdoc": "~61.4.1",
     "eslint-plugin-mocha": "~11.2.0",
     "globals": "~16.5.0",
     "husky": "~9.1.7",
     "mocha": "~11.7.5",
-    "prettier": "~3.7.3",
-    "rimraf": "~6.1.2"
+    "prettier": "~3.6.2",
+    "rimraf": "~6.1.2",
+    "typescript": "~5.9.3"
   }
 }

+ 6 - 0
src/debuggable-service.d.ts

@@ -0,0 +1,6 @@
+import {DebuggableService as BaseDebuggableService} from '@e22m4u/js-service';
+
+/**
+ * Debuggable service.
+ */
+declare class DebuggableService extends BaseDebuggableService {}

+ 25 - 0
src/hooks/hook-invoker.d.ts

@@ -0,0 +1,25 @@
+import {Route} from '../route.js';
+import {ServerResponse} from 'http';
+import {ValueOrPromise} from '../types.js';
+import {RouterHookType} from './hook-registry.js';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Hook invoker.
+ */
+export declare class HookInvoker extends DebuggableService {
+  /**
+   * Invoke and continue until value received.
+   *
+   * @param route
+   * @param hookType
+   * @param response
+   * @param args
+   */
+  invokeAndContinueUntilValueReceived(
+    route: Route,
+    hookType: RouterHookType,
+    response: ServerResponse,
+    ...args: unknown[]
+  ): ValueOrPromise<unknown>;
+}

+ 4 - 2
src/hooks/hook-invoker.spec.js

@@ -1,9 +1,11 @@
 import {expect} from 'chai';
+import {Route} from '../route.js';
+import {HttpMethod} from '../route.js';
 import {format} from '@e22m4u/js-format';
 import {HookInvoker} from './hook-invoker.js';
-import {Route, HttpMethod} from '../route.js';
+import {HookRegistry} from './hook-registry.js';
+import {RouterHookType} from './hook-registry.js';
 import {createResponseMock} from '../utils/index.js';
-import {HookRegistry, RouterHookType} from './hook-registry.js';
 
 describe('HookInvoker', function () {
   describe('invokeAndContinueUntilValueReceived', function () {

+ 93 - 0
src/hooks/hook-registry.d.ts

@@ -0,0 +1,93 @@
+import {Callable} from '../types.js';
+import {RequestContext} from '../request-context.js';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Hook type.
+ */
+export declare const RouterHookType: {
+  PRE_HANDLER: 'preHandler';
+  POST_HANDLER: 'postHandler';
+};
+
+/**
+ * Type of RouterHookType.
+ */
+export type RouterHookType =
+  (typeof RouterHookType)[keyof typeof RouterHookType];
+
+/**
+ * Router hook.
+ */
+export type RouterHook = Callable;
+
+/**
+ * Pre handler hook.
+ */
+export type PreHandlerHook = (ctx: RequestContext) => unknown;
+
+/**
+ * Post handler hook.
+ */
+export type PostHandlerHook = (ctx: RequestContext, data: unknown) => unknown;
+
+/**
+ * Hook registry.
+ */
+export declare class HookRegistry extends DebuggableService {
+  /**
+   * Add hook.
+   *
+   * @param type
+   * @param hook
+   */
+  addHook(type: typeof RouterHookType.PRE_HANDLER, hook: PreHandlerHook): this;
+
+  /**
+   * Add hook.
+   *
+   * @param type
+   * @param hook
+   */
+  addHook(
+    type: typeof RouterHookType.POST_HANDLER,
+    hook: PostHandlerHook,
+  ): this;
+
+  /**
+   * Add hook.
+   *
+   * @param type
+   * @param hook
+   */
+  addHook(type: RouterHookType, hook: RouterHook): this;
+
+  /**
+   * Has hook.
+   *
+   * @param type
+   * @param hook
+   */
+  hasHook(type: RouterHookType, hook: RouterHook): boolean;
+
+  /**
+   * Get hooks.
+   *
+   * @param type
+   */
+  getHooks(type: typeof RouterHookType.PRE_HANDLER): PreHandlerHook[];
+
+  /**
+   * Get hooks.
+   *
+   * @param type
+   */
+  getHooks(type: typeof RouterHookType.POST_HANDLER): PostHandlerHook[];
+
+  /**
+   * Get hooks.
+   *
+   * @param type
+   */
+  getHooks(type: RouterHookType): RouterHook[];
+}

+ 2 - 1
src/hooks/hook-registry.spec.js

@@ -1,6 +1,7 @@
 import {expect} from 'chai';
 import {format} from '@e22m4u/js-format';
-import {HookRegistry, RouterHookType} from './hook-registry.js';
+import {HookRegistry} from './hook-registry.js';
+import {RouterHookType} from './hook-registry.js';
 
 describe('HookRegistry', function () {
   describe('addHook', function () {

+ 2 - 0
src/hooks/index.d.ts

@@ -0,0 +1,2 @@
+export * from './hook-invoker.js';
+export * from './hook-registry.js';

+ 9 - 0
src/index.d.ts

@@ -0,0 +1,9 @@
+export * from './route.js';
+export * from './utils/index.js';
+export * from './hooks/index.js';
+export * from './trie-router.js';
+export * from './parsers/index.js';
+export * from './senders/index.js';
+export * from './route-registry.js';
+export * from './router-options.js';
+export * from './request-context.js';

+ 52 - 0
src/parsers/body-parser.d.ts

@@ -0,0 +1,52 @@
+import {IncomingMessage} from 'http';
+import {ValueOrPromise} from '../types.js';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Method names to be parsed.
+ */
+export type METHODS_WITH_BODY = string[];
+
+/**
+ * Unparsable media types.
+ */
+export type UNPARSABLE_MEDIA_TYPES = string[];
+
+/**
+ * Body parser function.
+ */
+export type BodyParserFunction = <T = unknown>(input: string) => T;
+
+/**
+ * Body parser.
+ */
+export declare class BodyParser extends DebuggableService {
+  /**
+   * Define parser.
+   *
+   * @param mediaType
+   * @param parser
+   */
+  defineParser(mediaType: string, parser: BodyParserFunction): this;
+
+  /**
+   * Has parser.
+   *
+   * @param mediaType
+   */
+  hasParser(mediaType: string): boolean;
+
+  /**
+   * Delete parser.
+   *
+   * @param mediaType
+   */
+  deleteParser(mediaType: string): this;
+
+  /**
+   * Parse.
+   *
+   * @param request
+   */
+  parse<T = unknown>(request: IncomingMessage): ValueOrPromise<T>;
+}

+ 3 - 6
src/parsers/body-parser.js

@@ -1,14 +1,11 @@
 import HttpErrors from 'http-errors';
 import {Errorf} from '@e22m4u/js-format';
+import {createError} from '../utils/index.js';
 import {RouterOptions} from '../router-options.js';
+import {parseContentType} from '../utils/index.js';
+import {fetchRequestBody} from '../utils/index.js';
 import {DebuggableService} from '../debuggable-service.js';
 
-import {
-  createError,
-  parseContentType,
-  fetchRequestBody,
-} from '../utils/index.js';
-
 /**
  * Method names to be parsed.
  *

+ 3 - 6
src/parsers/body-parser.spec.js

@@ -2,14 +2,11 @@ import {expect} from 'chai';
 import HttpErrors from 'http-errors';
 import {HttpMethod} from '../route.js';
 import {format} from '@e22m4u/js-format';
+import {BodyParser} from './body-parser.js';
+import {METHODS_WITH_BODY} from './body-parser.js';
 import {RouterOptions} from '../router-options.js';
 import {createRequestMock} from '../utils/index.js';
-
-import {
-  BodyParser,
-  METHODS_WITH_BODY,
-  UNPARSABLE_MEDIA_TYPES,
-} from './body-parser.js';
+import {UNPARSABLE_MEDIA_TYPES} from './body-parser.js';
 
 describe('BodyParser', function () {
   describe('defineParser', function () {

+ 15 - 0
src/parsers/cookies-parser.d.ts

@@ -0,0 +1,15 @@
+import {IncomingMessage} from 'http';
+import {ParsedCookies} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Cookies parser.
+ */
+export declare class CookiesParser extends DebuggableService {
+  /**
+   * Parse.
+   *
+   * @param request
+   */
+  parse(request: IncomingMessage): ParsedCookies;
+}

+ 2 - 1
src/parsers/cookies-parser.js

@@ -1,5 +1,6 @@
+import {parseCookies} from '../utils/index.js';
+import {getRequestPathname} from '../utils/index.js';
 import {DebuggableService} from '../debuggable-service.js';
-import {parseCookies, getRequestPathname} from '../utils/index.js';
 
 /**
  * Cookies parser.

+ 4 - 0
src/parsers/index.d.ts

@@ -0,0 +1,4 @@
+export * from './body-parser.js';
+export * from './query-parser.js';
+export * from './cookies-parser.js';
+export * from './request-parser.js';

+ 21 - 0
src/parsers/query-parser.d.ts

@@ -0,0 +1,21 @@
+import {IncomingMessage} from 'http';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Parsed query.
+ */
+export type ParsedQuery = {
+  [key: string]: string | undefined;
+};
+
+/**
+ * Query parser.
+ */
+export declare class QueryParser extends DebuggableService {
+  /**
+   * Parse.
+   *
+   * @param request
+   */
+  parse(request: IncomingMessage): ParsedQuery;
+}

+ 34 - 0
src/parsers/request-parser.d.ts

@@ -0,0 +1,34 @@
+import {IncomingMessage} from 'http';
+import {ValueOrPromise} from '../types.js';
+import {ParsedQuery} from './query-parser.js';
+import {ParsedCookies} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Parsed headers.
+ */
+export type ParsedHeaders = {
+  [key: string]: string | undefined;
+};
+
+/**
+ * Parsed request.
+ */
+type ParsedRequestData = {
+  query: ParsedQuery;
+  cookies: ParsedCookies;
+  body: unknown;
+  headers: ParsedHeaders;
+};
+
+/**
+ * Request parser.
+ */
+export declare class RequestParser extends DebuggableService {
+  /**
+   * Parse.
+   *
+   * @param request
+   */
+  parse(request: IncomingMessage): ValueOrPromise<ParsedRequestData>;
+}

+ 101 - 0
src/request-context.d.ts

@@ -0,0 +1,101 @@
+import {Route, RouteMeta} from './route.js';
+import {ParsedCookies} from './utils/index.js';
+import {ServiceContainer} from '@e22m4u/js-service';
+import {IncomingMessage, ServerResponse} from 'http';
+import {ParsedQuery, ParsedHeaders} from './parsers/index.js';
+
+/**
+ * Parsed params.
+ */
+export type ParsedParams = {
+  [key: string]: string | undefined;
+};
+
+/**
+ * Request context.
+ */
+export declare class RequestContext {
+  /**
+   * Container.
+   */
+  get container(): ServiceContainer;
+
+  /**
+   * Request.
+   */
+  get request(): IncomingMessage;
+
+  /**
+   * Response.
+   */
+  get response(): ServerResponse;
+
+  /**
+   * Route.
+   */
+  get route(): Route;
+
+  /**
+   * Query.
+   */
+  query: ParsedQuery;
+
+  /**
+   * Params.
+   */
+  params: ParsedParams;
+
+  /**
+   * Headers.
+   */
+  headers: ParsedHeaders;
+
+  /**
+   * Cookies.
+   */
+  cookies: ParsedCookies;
+
+  /**
+   * Body.
+   */
+  body: unknown;
+
+  /**
+   * State.
+   */
+  state: Record<string, any>;
+
+  /**
+   * Route meta.
+   */
+  get meta(): RouteMeta;
+
+  /**
+   * Method.
+   */
+  get method(): string;
+
+  /**
+   * Path.
+   */
+  get path(): string;
+
+  /**
+   * Pathname.
+   */
+  get pathname(): string;
+
+  /**
+   * Constructor.
+   *
+   * @param container
+   * @param request
+   * @param response
+   */
+  constructor(
+    container: ServiceContainer,
+    request: IncomingMessage,
+    response: ServerResponse,
+    route: Route,
+  );
+}

+ 39 - 0
src/route-registry.d.ts

@@ -0,0 +1,39 @@
+import {Route} from './route.js';
+import {IncomingMessage} from 'http';
+import {RouteDefinition} from './route.js';
+import {ServiceContainer} from '@e22m4u/js-service';
+import {DebuggableService} from './debuggable-service.js';
+
+/**
+ * Resolved route.
+ */
+export type ResolvedRoute = {
+  route: Route;
+  params: {[key: string]: string | undefined};
+};
+
+/**
+ * Route registry.
+ */
+export declare class RouteRegistry extends DebuggableService {
+  /**
+   * Constructor.
+   *
+   * @param container
+   */
+  constructor(container: ServiceContainer);
+
+  /**
+   * Define route.
+   *
+   * @param routeDef
+   */
+  defineRoute(routeDef: RouteDefinition): Route;
+
+  /**
+   * Match route by request.
+   *
+   * @param request
+   */
+  matchRouteByRequest(request: IncomingMessage): ResolvedRoute | undefined;
+}

+ 2 - 1
src/route-registry.spec.js

@@ -1,6 +1,7 @@
+import {Route} from './route.js';
 import {expect} from 'chai';
+import {HttpMethod} from './route.js';
 import {format} from '@e22m4u/js-format';
-import {Route, HttpMethod} from './route.js';
 import {RouteRegistry} from './route-registry.js';
 import {ServiceContainer} from '@e22m4u/js-service';
 

+ 100 - 0
src/route.d.ts

@@ -0,0 +1,100 @@
+import {ValueOrPromise} from './types.js';
+import {HookRegistry} from './hooks/index.js';
+import {RequestContext} from './request-context.js';
+
+/**
+ * Http method.
+ */
+export declare const HttpMethod: {
+  GET: 'GET';
+  POST: 'POST';
+  PUT: 'PUT';
+  PATCH: 'PATCH';
+  DELETE: 'DELETE';
+};
+
+/**
+ * Type of HttpMethod.
+ */
+export type HttpMethod = (typeof HttpMethod)[keyof typeof HttpMethod];
+
+/**
+ * Route handler.
+ */
+export type RouteHandler = (ctx: RequestContext) => ValueOrPromise<unknown>;
+
+/**
+ * Route pre-handler.
+ */
+export type RoutePreHandler = RouteHandler;
+
+/**
+ * Route post-handler.
+ */
+export type RoutePostHandler<T = unknown, U = unknown> = (
+  ctx: RequestContext,
+  data: T,
+) => ValueOrPromise<U>;
+
+/**
+ * Route meta.
+ */
+export type RouteMeta = {
+  [key: string]: unknown;
+};
+
+/**
+ * Route definition.
+ */
+export type RouteDefinition = {
+  method: string;
+  path: string;
+  handler: RouteHandler;
+  preHandler?: RoutePreHandler | RoutePreHandler[];
+  postHandler?: RoutePostHandler | RoutePostHandler[];
+  meta?: RouteMeta;
+};
+
+/**
+ * Route.
+ */
+export declare class Route {
+  /**
+   * Method.
+   */
+  get method(): string;
+
+  /**
+   * Path.
+   */
+  get path(): string;
+
+  /**
+   * Meta.
+   */
+  get meta(): RouteMeta;
+
+  /**
+   * Handler.
+   */
+  get handler(): RouteHandler;
+
+  /**
+   * Hook registry.
+   */
+  get hookRegistry(): HookRegistry;
+
+  /**
+   * Constructor.
+   *
+   * @param routeDef
+   */
+  constructor(routeDef: RouteDefinition);
+
+  /**
+   * Handle.
+   *
+   * @param context
+   */
+  handle(context: RequestContext): ValueOrPromise<unknown>;
+}

+ 4 - 2
src/route.spec.js

@@ -1,10 +1,12 @@
+import {Route} from './route.js';
 import {expect} from 'chai';
+import {HttpMethod} from './route.js';
 import {format} from '@e22m4u/js-format';
-import {Route, HttpMethod} from './route.js';
 import {RouterHookType} from './hooks/index.js';
+import {createRequestMock} from './utils/index.js';
+import {createResponseMock} from './utils/index.js';
 import {RequestContext} from './request-context.js';
 import {ServiceContainer} from '@e22m4u/js-service';
-import {createRequestMock, createResponseMock} from './utils/index.js';
 
 describe('Route', function () {
   describe('constructor', function () {

+ 18 - 0
src/router-options.d.ts

@@ -0,0 +1,18 @@
+import {DebuggableService} from './debuggable-service.js';
+
+/**
+ * Router options.
+ */
+export declare class RouterOptions extends DebuggableService {
+  /**
+   * Request body bytes limit.
+   */
+  get requestBodyBytesLimit(): number;
+
+  /**
+   * Set request body bytes limit.
+   *
+   * @param input
+   */
+  setRequestBodyBytesLimit(input: number): this;
+}

+ 15 - 0
src/senders/data-sender.d.ts

@@ -0,0 +1,15 @@
+import {ServerResponse} from 'http';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Data sender.
+ */
+export declare class DataSender extends DebuggableService {
+  /**
+   * Send.
+   *
+   * @param response
+   * @param data
+   */
+  send(response: ServerResponse, data: unknown): void;
+}

+ 30 - 0
src/senders/error-sender.d.ts

@@ -0,0 +1,30 @@
+import {ServerResponse} from 'http';
+import {IncomingMessage} from 'http';
+import {DebuggableService} from '../debuggable-service.js';
+
+/**
+ * Exposed error properties.
+ */
+export const EXPOSED_ERROR_PROPERTIES: ['code', 'details'];
+
+/**
+ * Error sender.
+ */
+export declare class ErrorSender extends DebuggableService {
+  /**
+   * Send.
+   *
+   * @param request
+   * @param response
+   * @param error
+   */
+  send(request: IncomingMessage, response: ServerResponse, error: Error): void;
+
+  /**
+   * Send 404.
+   *
+   * @param request
+   * @param response
+   */
+  send404(request: IncomingMessage, response: ServerResponse): void;
+}

+ 5 - 3
src/senders/error-sender.spec.js

@@ -1,8 +1,10 @@
-import {expect} from 'chai';
 import {Writable} from 'stream';
+import {expect} from 'chai';
 import HttpErrors from 'http-errors';
-import {ErrorSender, EXPOSED_ERROR_PROPERTIES} from './error-sender.js';
-import {createRequestMock, createResponseMock} from '../utils/index.js';
+import {ErrorSender} from './error-sender.js';
+import {createRequestMock} from '../utils/index.js';
+import {createResponseMock} from '../utils/index.js';
+import {EXPOSED_ERROR_PROPERTIES} from './error-sender.js';
 
 describe('ErrorSender', function () {
   describe('send', function () {

+ 2 - 0
src/senders/index.d.ts

@@ -0,0 +1,2 @@
+export * from './data-sender.js';
+export * from './error-sender.js';

+ 104 - 0
src/trie-router.d.ts

@@ -0,0 +1,104 @@
+import {Route} from './route.js';
+import {RequestListener} from 'http';
+import {RouteDefinition} from './route.js';
+import {DebuggableService} from './debuggable-service.js';
+
+import {
+  RouterHook,
+  RouterHookType,
+  PostHandlerHook,
+  PreHandlerHook,
+} from './hooks/index.js';
+
+/**
+ * Trie router.
+ */
+export declare class TrieRouter extends DebuggableService {
+  /**
+   * Define route.
+   *
+   * Example 1:
+   * ```
+   * const router = new TrieRouter();
+   * router.defineRoute({
+   *   method: HttpMethod.GET,        // Request method.
+   *   path: '/',                      // Path template.
+   *   handler: ctx => 'Hello world!', // Request handler.
+   * });
+   * ```
+   *
+   * Example 2:
+   * ```
+   * const router = new TrieRouter();
+   * router.defineRoute({
+   *   method: HttpMethod.POST,       // Request method.
+   *   path: '/users/:id',             // The path template may have parameters.
+   *   preHandler(ctx) { ... },        // The "preHandler" is executed before a route handler.
+   *   handler(ctx) { ... },           // Request handler function.
+   *   postHandler(ctx, data) { ... }, // The "postHandler" is executed after a route handler
+   * });
+   * ```
+   *
+   * @param routeDef
+   */
+  defineRoute(routeDef: RouteDefinition): Route;
+
+  /**
+   * Request listener.
+   *
+   * Example:
+   * ```
+   * import http from 'http';
+   * import {TrieRouter} from '@e22m4u/js-trie-router';
+   *
+   * const router = new TrieRouter();
+   * const server = new http.Server();
+   * server.on('request', router.requestListener); // Sets the request listener.
+   * server.listen(3000);                          // Starts listening for connections.
+   * ```
+   *
+   * @returns {Function}
+   */
+  get requestListener(): RequestListener;
+
+  /**
+   * Add hook.
+   *
+   * @param type
+   * @param hook
+   */
+  addHook(type: typeof RouterHookType.PRE_HANDLER, hook: PreHandlerHook): this;
+
+  /**
+   * Add hook.
+   *
+   * @param type
+   * @param hook
+   */
+  addHook(
+    type: typeof RouterHookType.POST_HANDLER,
+    hook: PostHandlerHook,
+  ): this;
+
+  /**
+   * Add hook.
+   *
+   * @param type
+   * @param hook
+   */
+  addHook(type: RouterHookType, hook: RouterHook): this;
+
+  /**
+   * Add pre-handler hook.
+   *
+   * @param hook
+   */
+  addPreHandler(hook: PreHandlerHook): this;
+
+  /**
+   * Add post-handler hook.
+   *
+   * @param hook
+   */
+  addPostHandler(hook: PostHandlerHook): this;
+}

+ 10 - 5
src/trie-router.spec.js

@@ -1,11 +1,16 @@
+import {Route} from './route.js';
 import {expect} from 'chai';
+import {ServerResponse} from 'http';
+import {IncomingMessage} from 'http';
+import {HttpMethod} from './route.js';
 import {TrieRouter} from './trie-router.js';
-import {Route, HttpMethod} from './route.js';
+import {HookRegistry} from './hooks/index.js';
+import {DataSender} from './senders/index.js';
+import {ErrorSender} from './senders/index.js';
+import {RouterHookType} from './hooks/index.js';
+import {createRequestMock} from './utils/index.js';
+import {createResponseMock} from './utils/index.js';
 import {RequestContext} from './request-context.js';
-import {ServerResponse, IncomingMessage} from 'http';
-import {DataSender, ErrorSender} from './senders/index.js';
-import {HookRegistry, RouterHookType} from './hooks/index.js';
-import {createRequestMock, createResponseMock} from './utils/index.js';
 
 describe('TrieRouter', function () {
   describe('defineRoute', function () {

+ 19 - 0
src/types.d.ts

@@ -0,0 +1,19 @@
+/**
+ * A callable type with the "new" operator
+ * allows class and constructor.
+ */
+export interface Constructor<T = unknown> {
+  new (...args: any[]): T;
+}
+
+/**
+ * A function type without class and constructor.
+ */
+export type Callable<T = unknown> = (...args: any[]) => T;
+
+/**
+ * Representing a value or promise. This type is used
+ * to represent results of synchronous/asynchronous
+ * resolution of values.
+ */
+export type ValueOrPromise<T> = T | PromiseLike<T>;

+ 6 - 0
src/utils/clone-deep.d.ts

@@ -0,0 +1,6 @@
+/**
+ * Clone deep.
+ *
+ * @param value
+ */
+export declare function cloneDeep<T>(value: T): T;

+ 6 - 0
src/utils/create-cookies-string.d.ts

@@ -0,0 +1,6 @@
+/**
+ * Create cookies string.
+ *
+ * @param data
+ */
+export declare function createCookiesString(data: object): string;

+ 11 - 0
src/utils/create-debugger.d.ts

@@ -0,0 +1,11 @@
+/**
+ * Debugger.
+ */
+export type Debugger = (message: string, ...args: unknown[]) => void;
+
+/**
+ * Create debugger.
+ *
+ * @param name
+ */
+export declare function createDebugger(name: string): Debugger;

+ 14 - 0
src/utils/create-error.d.ts

@@ -0,0 +1,14 @@
+import {Constructor} from '../types.js';
+
+/**
+ * Create error.
+ *
+ * @param errorCtor
+ * @param message
+ * @param args
+ */
+export declare function createError<T>(
+  errorCtor: Constructor<T>,
+  message: string,
+  ...args: unknown[]
+): T;

+ 2 - 1
src/utils/create-error.js

@@ -1,4 +1,5 @@
-import {format, Errorf} from '@e22m4u/js-format';
+import {format} from '@e22m4u/js-format';
+import {Errorf} from '@e22m4u/js-format';
 
 /**
  * Create error.

+ 27 - 0
src/utils/create-request-mock.d.ts

@@ -0,0 +1,27 @@
+import {Readable} from 'stream';
+import {IncomingMessage} from 'http';
+
+/**
+ * Request patch.
+ */
+type RequestPatch = {
+  host?: string;
+  method?: string;
+  secure?: boolean;
+  path?: string;
+  query?: object;
+  cookies?: object;
+  headers?: object;
+  body?: string;
+  stream?: Readable;
+  encoding?: BufferEncoding;
+};
+
+/**
+ * Create request mock.
+ *
+ * @param patch
+ */
+export declare function createRequestMock(
+  patch?: RequestPatch,
+): IncomingMessage;

+ 17 - 0
src/utils/create-response-mock.d.ts

@@ -0,0 +1,17 @@
+import {ServerResponse} from 'http';
+
+/**
+ * Server response mock.
+ */
+export type ServerResponseMock = ServerResponse & {
+  _headersSent: boolean;
+  _headers: {[name: string]: string | undefined};
+  setEncoding(encoding: string): ServerResponseMock;
+  getEncoding(): string | undefined;
+  getBody(): Promise<string>;
+};
+
+/**
+ * Create response mock.
+ */
+export declare function createResponseMock(): ServerResponseMock;

+ 18 - 0
src/utils/create-route-mock.d.ts

@@ -0,0 +1,18 @@
+import {Route, HttpMethod} from '../route.js';
+import type {RouteHandler} from '../route.js';
+
+/**
+ * Route mock options.
+ */
+type RouteMockOptions = {
+  method?: HttpMethod;
+  path?: string;
+  handler?: RouteHandler;
+};
+
+/**
+ * Create route mock.
+ *
+ * @param options
+ */
+export function createRouteMock(options?: RouteMockOptions): Route;

+ 26 - 0
src/utils/fetch-request-body.d.ts

@@ -0,0 +1,26 @@
+import {IncomingMessage} from 'http';
+
+/**
+ * Character encoding list.
+ */
+export const CHARACTER_ENCODING_LIST: (
+  | 'ascii'
+  | 'utf8'
+  | 'utf-8'
+  | 'utf16le'
+  | 'utf-16le'
+  | 'ucs2'
+  | 'ucs-2'
+  | 'latin1'
+)[];
+
+/**
+ * Fetch request body.
+ *
+ * @param request
+ * @param bodyBytesLimit
+ */
+export declare function fetchRequestBody(
+  request: IncomingMessage,
+  bodyBytesLimit?: number,
+): Promise<string>;

+ 8 - 0
src/utils/get-request-pathname.d.ts

@@ -0,0 +1,8 @@
+import {IncomingMessage} from 'http';
+
+/**
+ * Get request pathname.
+ *
+ * @param request
+ */
+export declare function getRequestPathname(request: IncomingMessage): string;

+ 16 - 0
src/utils/index.d.ts

@@ -0,0 +1,16 @@
+export * from './clone-deep.js';
+export * from './is-promise.js';
+export * from './create-error.js';
+export * from './parse-cookies.js';
+export * from './to-camel-case.js';
+export * from './create-debugger.js';
+export * from './is-response-sent.js';
+export * from './create-route-mock.js';
+export * from './is-readable-stream.js';
+export * from './parse-content-type.js';
+export * from './is-writable-stream.js';
+export * from './fetch-request-body.js';
+export * from './create-request-mock.js';
+export * from './create-response-mock.js';
+export * from './create-cookies-string.js';
+export * from './get-request-pathname.js';

+ 10 - 0
src/utils/is-promise.d.ts

@@ -0,0 +1,10 @@
+/**
+ * Check whether a value is a Promise-like
+ * instance. Recognizes both native promises
+ * and third-party promise libraries.
+ *
+ * @param value
+ */
+export declare function isPromise<T = unknown>(
+  value: unknown,
+): value is Promise<T>;

+ 9 - 0
src/utils/is-readable-stream.d.ts

@@ -0,0 +1,9 @@
+import {Readable} from 'stream';
+
+/**
+ * Check whether a value has a pipe
+ * method.
+ *
+ * @param value
+ */
+export declare function isReadableStream(value: unknown): value is Readable;

+ 8 - 0
src/utils/is-response-sent.d.ts

@@ -0,0 +1,8 @@
+import {ServerResponse} from 'http';
+
+/**
+ * Is response sent.
+ *
+ * @param response
+ */
+export declare function isResponseSent(response: ServerResponse): boolean;

+ 9 - 0
src/utils/is-writable-stream.d.ts

@@ -0,0 +1,9 @@
+import {Writable} from 'stream';
+
+/**
+ * Check whether a value has an end
+ * method.
+ *
+ * @param value
+ */
+export declare function isWritableStream(value: unknown): value is Writable;

+ 15 - 0
src/utils/parse-content-type.d.ts

@@ -0,0 +1,15 @@
+/**
+ * Parsed content type.
+ */
+export type ParsedContentType = {
+  boundary: string | undefined;
+  charset: string | undefined;
+  mediaType: string | undefined;
+};
+
+/**
+ * Parse content type.
+ *
+ * @param input
+ */
+export declare function parseContentType(input: string): ParsedContentType;

+ 19 - 0
src/utils/parse-cookies.d.ts

@@ -0,0 +1,19 @@
+/**
+ * Parsed cookies.
+ */
+type ParsedCookies = {
+  [key: string]: string | undefined;
+};
+
+/**
+ * Parse cookies.
+ *
+ * @example
+ * ```ts
+ * parseCookies('pkg=math; equation=E%3Dmc%5E2');
+ * // {pkg: 'math', equation: 'E=mc^2'}
+ * ```
+ *
+ * @param input
+ */
+export declare function parseCookies(input: string): ParsedCookies;

+ 6 - 0
src/utils/to-camel-case.d.ts

@@ -0,0 +1,6 @@
+/**
+ * To camel case.
+ *
+ * @param input
+ */
+export declare function toCamelCase(input: string): string;

+ 11 - 0
tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "rootDir": "src",
+    "noEmit": true,
+    "target": "es2022",
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",
+    "strictNullChecks": true
+  }
+}