e22m4u 1 год назад
Родитель
Сommit
5049faca22

+ 432 - 432
dist/cjs/index.cjs

@@ -2189,6 +2189,135 @@ function getRequestPathname(req) {
   return (req.url || "/").replace(/\?.*$/, "");
 }
 
+// src/route.js
+var HTTP_METHOD = {
+  GET: "GET",
+  POST: "POST",
+  PUT: "PUT",
+  PATCH: "PATCH",
+  DELETE: "DELETE"
+};
+var debug = createDebugger("route");
+var Route = class {
+  /**
+   * Method.
+   *
+   * @type {string}
+   * @private
+   */
+  _method;
+  /**
+   * Getter of the method.
+   *
+   * @returns {string}
+   */
+  get method() {
+    return this._method;
+  }
+  /**
+   * Path template.
+   *
+   * @type {string}
+   * @private
+   */
+  _path;
+  /**
+   * Getter of the path.
+   *
+   * @returns {string}
+   */
+  get path() {
+    return this._path;
+  }
+  /**
+   * Handler.
+   *
+   * @type {RouteHandler}
+   * @private
+   */
+  _handler;
+  /**
+   * Getter of the handler.
+   *
+   * @returns {*}
+   */
+  get handler() {
+    return this._handler;
+  }
+  /**
+   * Hook registry.
+   *
+   * @type {HookRegistry}
+   * @private
+   */
+  _hookRegistry = new HookRegistry();
+  /**
+   * Getter of the hook registry.
+   *
+   * @returns {HookRegistry}
+   */
+  get hookRegistry() {
+    return this._hookRegistry;
+  }
+  /**
+   * Constructor.
+   *
+   * @param {RouteDefinition} routeDef
+   */
+  constructor(routeDef) {
+    if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
+      throw new Errorf(
+        "The first parameter of Route.controller should be an Object, but %v given.",
+        routeDef
+      );
+    if (!routeDef.method || typeof routeDef.method !== "string")
+      throw new Errorf(
+        'The option "method" of the Route should be a non-empty String, but %v given.',
+        routeDef.method
+      );
+    this._method = routeDef.method.toUpperCase();
+    if (typeof routeDef.path !== "string")
+      throw new Errorf(
+        'The option "path" of the Route should be a String, but %v given.',
+        routeDef.path
+      );
+    this._path = routeDef.path;
+    if (typeof routeDef.handler !== "function")
+      throw new Errorf(
+        'The option "handler" of the Route should be a Function, but %v given.',
+        routeDef.handler
+      );
+    this._handler = routeDef.handler;
+    if (routeDef.preHandler != null) {
+      const preHandlerHooks = Array.isArray(routeDef.preHandler) ? routeDef.preHandler : [routeDef.preHandler];
+      preHandlerHooks.forEach((hook) => {
+        this._hookRegistry.addHook(HOOK_NAME.PRE_HANDLER, hook);
+      });
+    }
+    if (routeDef.postHandler != null) {
+      const postHandlerHooks = Array.isArray(routeDef.postHandler) ? routeDef.postHandler : [routeDef.postHandler];
+      postHandlerHooks.forEach((hook) => {
+        this._hookRegistry.addHook(HOOK_NAME.POST_HANDLER, hook);
+      });
+    }
+  }
+  /**
+   * Handle request.
+   *
+   * @param {RequestContext} context
+   * @returns {*}
+   */
+  handle(context) {
+    const requestPath = getRequestPathname(context.req);
+    debug(
+      "Invoking the Route handler for the request %s %v.",
+      this.method.toUpperCase(),
+      requestPath
+    );
+    return this._handler(context);
+  }
+};
+
 // node_modules/@e22m4u/js-service/src/errors/invalid-argument-error.js
 var InvalidArgumentError = class extends Errorf {
 };
@@ -2388,8 +2517,8 @@ var Service = class {
   }
 };
 
-// src/service.js
-var Service2 = class extends Service {
+// src/debuggable-service.js
+var DebuggableService = class extends Service {
   /**
    * Debug.
    *
@@ -2414,7 +2543,7 @@ var HOOK_NAME = {
   PRE_HANDLER: "preHandler",
   POST_HANDLER: "postHandler"
 };
-var HookRegistry = class extends Service2 {
+var HookRegistry = class extends DebuggableService {
   /**
    * Hooks.
    *
@@ -2482,7 +2611,7 @@ var HookRegistry = class extends Service2 {
 };
 
 // src/hooks/hook-invoker.js
-var HookInvoker = class extends Service2 {
+var HookInvoker = class extends DebuggableService {
   /**
    * Invoke and continue until value received.
    *
@@ -2540,137 +2669,266 @@ var HookInvoker = class extends Service2 {
   }
 };
 
-// src/route.js
-var HTTP_METHOD = {
-  GET: "GET",
-  POST: "POST",
-  PUT: "PUT",
-  PATCH: "PATCH",
-  DELETE: "DELETE"
-};
-var debug = createDebugger("route");
-var Route = class {
+// src/parsers/body-parser.js
+var import_http_errors2 = __toESM(require_http_errors(), 1);
+
+// src/router-options.js
+var RouterOptions = class extends DebuggableService {
   /**
-   * Method.
+   * Request body bytes limit.
    *
-   * @type {string}
+   * @type {number}
    * @private
    */
-  _method;
+  _requestBodyBytesLimit = 512e3;
+  // 512kb
   /**
-   * Getter of the method.
+   * Getter of request body bytes limit.
    *
-   * @returns {string}
+   * @returns {number}
    */
-  get method() {
-    return this._method;
+  get requestBodyBytesLimit() {
+    return this._requestBodyBytesLimit;
   }
   /**
-   * Path template.
-   *
-   * @type {string}
-   * @private
-   */
-  _path;
-  /**
-   * Getter of the path.
+   * Set request body bytes limit.
    *
-   * @returns {string}
+   * @param {number} input
+   * @returns {RouterOptions}
    */
-  get path() {
-    return this._path;
+  setRequestBodyBytesLimit(input) {
+    if (typeof input !== "number" || input < 0)
+      throw new Errorf(
+        'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v given.',
+        input
+      );
+    this._requestBodyBytesLimit = input;
+    return this;
   }
+};
+
+// src/parsers/body-parser.js
+var METHODS_WITH_BODY = ["POST", "PUT", "PATCH", "DELETE"];
+var UNPARSABLE_MEDIA_TYPES = ["multipart/form-data"];
+var BodyParser = class extends DebuggableService {
   /**
-   * Handler.
+   * Parsers.
    *
-   * @type {RouteHandler}
-   * @private
+   * @type {{[mime: string]: Function}}
    */
-  _handler;
+  _parsers = {
+    "text/plain": (v) => String(v),
+    "application/json": parseJsonBody
+  };
   /**
-   * Getter of the handler.
+   * Set parser.
    *
-   * @returns {*}
+   * @param {string} mediaType
+   * @param {Function} parser
+   * @returns {this}
    */
-  get handler() {
-    return this._handler;
+  defineParser(mediaType, parser) {
+    if (!mediaType || typeof mediaType !== "string")
+      throw new Errorf(
+        'The parameter "mediaType" of BodyParser.defineParser should be a non-empty String, but %v given.',
+        mediaType
+      );
+    if (!parser || typeof parser !== "function")
+      throw new Errorf(
+        'The parameter "parser" of BodyParser.defineParser should be a Function, but %v given.',
+        parser
+      );
+    this._parsers[mediaType] = parser;
+    return this;
   }
   /**
-   * Hook registry.
+   * Has parser.
    *
-   * @type {HookRegistry}
-   * @private
+   * @param {string} mediaType
+   * @returns {boolean}
    */
-  _hookRegistry = new HookRegistry();
+  hasParser(mediaType) {
+    if (!mediaType || typeof mediaType !== "string")
+      throw new Errorf(
+        'The parameter "mediaType" of BodyParser.hasParser should be a non-empty String, but %v given.',
+        mediaType
+      );
+    return Boolean(this._parsers[mediaType]);
+  }
   /**
-   * Getter of the hook registry.
+   * Delete parser.
    *
-   * @returns {HookRegistry}
+   * @param {string} mediaType
+   * @returns {this}
    */
-  get hookRegistry() {
-    return this._hookRegistry;
+  deleteParser(mediaType) {
+    if (!mediaType || typeof mediaType !== "string")
+      throw new Errorf(
+        'The parameter "mediaType" of BodyParser.deleteParser should be a non-empty String, but %v given.',
+        mediaType
+      );
+    const parser = this._parsers[mediaType];
+    if (!parser) throw new Errorf("The parser of %v is not found.", mediaType);
+    delete this._parsers[mediaType];
+    return this;
   }
   /**
-   * Constructor.
+   * Parse.
    *
-   * @param {RouteDefinition} routeDef
+   * @param {import('http').IncomingMessage} req
+   * @returns {Promise<*>|undefined}
    */
-  constructor(routeDef) {
-    if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
-      throw new Errorf(
-        "The first parameter of Route.controller should be an Object, but %v given.",
-        routeDef
+  parse(req) {
+    if (!METHODS_WITH_BODY.includes(req.method.toUpperCase())) {
+      this.debug(
+        "Body parsing was skipped for the %s request.",
+        req.method.toUpperCase()
       );
-    if (!routeDef.method || typeof routeDef.method !== "string")
-      throw new Errorf(
-        'The option "method" of the Route should be a non-empty String, but %v given.',
-        routeDef.method
+      return;
+    }
+    const contentType = (req.headers["content-type"] || "").replace(
+      /^([^;]+);.*$/,
+      "$1"
+    );
+    if (!contentType) {
+      this.debug(
+        "Body parsing was skipped because the request has no content type."
       );
-    this._method = routeDef.method.toUpperCase();
-    if (typeof routeDef.path !== "string")
-      throw new Errorf(
-        'The option "path" of the Route should be a String, but %v given.',
-        routeDef.path
+      return;
+    }
+    const { mediaType } = parseContentType(contentType);
+    if (!mediaType)
+      throw createError(
+        import_http_errors2.default.BadRequest,
+        'Unable to parse the "content-type" header.'
       );
-    this._path = routeDef.path;
-    if (typeof routeDef.handler !== "function")
-      throw new Errorf(
-        'The option "handler" of the Route should be a Function, but %v given.',
-        routeDef.handler
+    const parser = this._parsers[mediaType];
+    if (!parser) {
+      if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
+        this.debug("Body parsing was skipped for %v.", mediaType);
+        return;
+      }
+      throw createError(
+        import_http_errors2.default.UnsupportedMediaType,
+        "Media type %v is not supported.",
+        mediaType
       );
-    this._handler = routeDef.handler;
-    if (routeDef.preHandler != null) {
-      const preHandlerHooks = Array.isArray(routeDef.preHandler) ? routeDef.preHandler : [routeDef.preHandler];
-      preHandlerHooks.forEach((hook) => {
-        this._hookRegistry.addHook(HOOK_NAME.PRE_HANDLER, hook);
+    }
+    const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
+    return fetchRequestBody(req, bodyBytesLimit).then((rawBody) => {
+      if (rawBody != null) return parser(rawBody);
+      return rawBody;
+    });
+  }
+};
+function parseJsonBody(input) {
+  if (typeof input !== "string") return void 0;
+  try {
+    return JSON.parse(input);
+  } catch (error) {
+    if (process.env["DEBUG"] || process.env["NODE_ENV"] === "development")
+      console.warn(error);
+    throw createError(import_http_errors2.default.BadRequest, "Unable to parse request body.");
+  }
+}
+
+// src/parsers/query-parser.js
+var import_querystring = __toESM(require("querystring"), 1);
+var QueryParser = class extends DebuggableService {
+  /**
+   * Parse
+   *
+   * @param {import('http').IncomingMessage} req
+   * @returns {object}
+   */
+  parse(req) {
+    const queryStr = req.url.replace(/^[^?]*\??/, "");
+    const query = queryStr ? import_querystring.default.parse(queryStr) : {};
+    const queryKeys = Object.keys(query);
+    if (queryKeys.length) {
+      queryKeys.forEach((key) => {
+        this.debug("The query %v has the value %v.", key, query[key]);
       });
+    } else {
+      this.debug(
+        "The request %s %v has no query.",
+        req.method,
+        getRequestPathname(req)
+      );
     }
-    if (routeDef.postHandler != null) {
-      const postHandlerHooks = Array.isArray(routeDef.postHandler) ? routeDef.postHandler : [routeDef.postHandler];
-      postHandlerHooks.forEach((hook) => {
-        this._hookRegistry.addHook(HOOK_NAME.POST_HANDLER, hook);
+    return query;
+  }
+};
+
+// src/parsers/cookie-parser.js
+var CookieParser = class extends DebuggableService {
+  /**
+   * Parse
+   *
+   * @param {import('http').IncomingMessage} req
+   * @returns {object}
+   */
+  parse(req) {
+    const cookieString = req.headers["cookie"] || "";
+    const cookie = parseCookie(cookieString);
+    const cookieKeys = Object.keys(cookie);
+    if (cookieKeys.length) {
+      cookieKeys.forEach((key) => {
+        this.debug("The cookie %v has the value %v.", key, cookie[key]);
       });
+    } else {
+      this.debug(
+        "The request %s %v has no cookie.",
+        req.method,
+        getRequestPathname(req)
+      );
     }
+    return cookie;
   }
+};
+
+// src/parsers/request-parser.js
+var import_http2 = require("http");
+var RequestParser = class extends DebuggableService {
   /**
-   * Handle request.
+   * Parse.
    *
-   * @param {RequestContext} context
-   * @returns {*}
+   * @param {IncomingMessage} req
+   * @returns {Promise<object>|object}
    */
-  handle(context) {
-    const requestPath = getRequestPathname(context.req);
-    debug(
-      "Invoking the Route handler for the request %s %v.",
-      this.method.toUpperCase(),
-      requestPath
-    );
-    return this._handler(context);
+  parse(req) {
+    if (!(req instanceof import_http2.IncomingMessage))
+      throw new Errorf(
+        "The first argument of RequestParser.parse should be an instance of IncomingMessage, but %v given.",
+        req
+      );
+    const data = {};
+    const promises = [];
+    const parsedQuery = this.getService(QueryParser).parse(req);
+    if (isPromise(parsedQuery)) {
+      promises.push(parsedQuery.then((v) => data.query = v));
+    } else {
+      data.query = parsedQuery;
+    }
+    const parsedCookie = this.getService(CookieParser).parse(req);
+    if (isPromise(parsedCookie)) {
+      promises.push(parsedCookie.then((v) => data.cookie = v));
+    } else {
+      data.cookie = parsedCookie;
+    }
+    const parsedBody = this.getService(BodyParser).parse(req);
+    if (isPromise(parsedBody)) {
+      promises.push(parsedBody.then((v) => data.body = v));
+    } else {
+      data.body = parsedBody;
+    }
+    data.headers = JSON.parse(JSON.stringify(req.headers));
+    return promises.length ? Promise.all(promises).then(() => data) : data;
   }
 };
 
 // src/senders/data-sender.js
-var DataSender = class extends Service2 {
+var DataSender = class extends DebuggableService {
   /**
    * Send.
    *
@@ -2726,7 +2984,7 @@ var DataSender = class extends Service2 {
 var import_util = require("util");
 var import_statuses = __toESM(require_statuses(), 1);
 var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
-var ErrorSender = class extends Service2 {
+var ErrorSender = class extends DebuggableService {
   /**
    * Handle.
    *
@@ -2796,261 +3054,117 @@ var ErrorSender = class extends Service2 {
   }
 };
 
-// src/parsers/body-parser.js
-var import_http_errors2 = __toESM(require_http_errors(), 1);
-
-// src/router-options.js
-var RouterOptions = class extends Service2 {
-  /**
-   * Request body bytes limit.
-   *
-   * @type {number}
-   * @private
-   */
-  _requestBodyBytesLimit = 512e3;
-  // 512kb
-  /**
-   * Getter of request body bytes limit.
-   *
-   * @returns {number}
-   */
-  get requestBodyBytesLimit() {
-    return this._requestBodyBytesLimit;
-  }
-  /**
-   * Set request body bytes limit.
-   *
-   * @param {number} input
-   * @returns {RouterOptions}
-   */
-  setRequestBodyBytesLimit(input) {
-    if (typeof input !== "number" || input < 0)
-      throw new Errorf(
-        'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v given.',
-        input
-      );
-    this._requestBodyBytesLimit = input;
-    return this;
-  }
-};
-
-// src/parsers/body-parser.js
-var METHODS_WITH_BODY = ["POST", "PUT", "PATCH", "DELETE"];
-var UNPARSABLE_MEDIA_TYPES = ["multipart/form-data"];
-var BodyParser = class extends Service2 {
+// src/request-context.js
+var RequestContext = class {
   /**
-   * Parsers.
+   * Service container.
    *
-   * @type {{[mime: string]: Function}}
+   * @type {import('@e22m4u/js-service').ServiceContainer}
    */
-  _parsers = {
-    "text/plain": (v) => String(v),
-    "application/json": parseJsonBody
-  };
+  container;
   /**
-   * Set parser.
+   * Request.
    *
-   * @param {string} mediaType
-   * @param {Function} parser
-   * @returns {this}
+   * @type {import('http').IncomingMessage}
    */
-  defineParser(mediaType, parser) {
-    if (!mediaType || typeof mediaType !== "string")
-      throw new Errorf(
-        'The parameter "mediaType" of BodyParser.defineParser should be a non-empty String, but %v given.',
-        mediaType
-      );
-    if (!parser || typeof parser !== "function")
-      throw new Errorf(
-        'The parameter "parser" of BodyParser.defineParser should be a Function, but %v given.',
-        parser
-      );
-    this._parsers[mediaType] = parser;
-    return this;
-  }
+  req;
   /**
-   * Has parser.
+   * Response.
    *
-   * @param {string} mediaType
-   * @returns {boolean}
+   * @type {import('http').ServerResponse}
    */
-  hasParser(mediaType) {
-    if (!mediaType || typeof mediaType !== "string")
-      throw new Errorf(
-        'The parameter "mediaType" of BodyParser.hasParser should be a non-empty String, but %v given.',
-        mediaType
-      );
-    return Boolean(this._parsers[mediaType]);
-  }
+  res;
   /**
-   * Delete parser.
+   * Query.
    *
-   * @param {string} mediaType
-   * @returns {this}
+   * @type {object}
    */
-  deleteParser(mediaType) {
-    if (!mediaType || typeof mediaType !== "string")
-      throw new Errorf(
-        'The parameter "mediaType" of BodyParser.deleteParser should be a non-empty String, but %v given.',
-        mediaType
-      );
-    const parser = this._parsers[mediaType];
-    if (!parser) throw new Errorf("The parser of %v is not found.", mediaType);
-    delete this._parsers[mediaType];
-    return this;
-  }
+  query = {};
   /**
-   * Parse.
+   * Path parameters.
    *
-   * @param {import('http').IncomingMessage} req
-   * @returns {Promise<*>|undefined}
-   */
-  parse(req) {
-    if (!METHODS_WITH_BODY.includes(req.method.toUpperCase())) {
-      this.debug(
-        "Body parsing was skipped for the %s request.",
-        req.method.toUpperCase()
-      );
-      return;
-    }
-    const contentType = (req.headers["content-type"] || "").replace(
-      /^([^;]+);.*$/,
-      "$1"
-    );
-    if (!contentType) {
-      this.debug(
-        "Body parsing was skipped because the request has no content type."
-      );
-      return;
-    }
-    const { mediaType } = parseContentType(contentType);
-    if (!mediaType)
-      throw createError(
-        import_http_errors2.default.BadRequest,
-        'Unable to parse the "content-type" header.'
-      );
-    const parser = this._parsers[mediaType];
-    if (!parser) {
-      if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
-        this.debug("Body parsing was skipped for %v.", mediaType);
-        return;
-      }
-      throw createError(
-        import_http_errors2.default.UnsupportedMediaType,
-        "Media type %v is not supported.",
-        mediaType
-      );
-    }
-    const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
-    return fetchRequestBody(req, bodyBytesLimit).then((rawBody) => {
-      if (rawBody != null) return parser(rawBody);
-      return rawBody;
-    });
-  }
-};
-function parseJsonBody(input) {
-  if (typeof input !== "string") return void 0;
-  try {
-    return JSON.parse(input);
-  } catch (error) {
-    if (process.env["DEBUG"] || process.env["NODE_ENV"] === "development")
-      console.warn(error);
-    throw createError(import_http_errors2.default.BadRequest, "Unable to parse request body.");
+   * @type {object}
+   */
+  params = {};
+  /**
+   * Parsed body.
+   *
+   * @type {*}
+   */
+  body;
+  /**
+   * Headers.
+   *
+   * @type {object}
+   */
+  headers = {};
+  /**
+   * Parsed cookie.
+   *
+   * @type {object}
+   */
+  cookie = {};
+  /**
+   * Method.
+   *
+   * @returns {string}
+   */
+  get method() {
+    return this.req.method.toUpperCase();
   }
-}
-
-// src/parsers/query-parser.js
-var import_querystring = __toESM(require("querystring"), 1);
-var QueryParser = class extends Service2 {
   /**
-   * Parse
+   * Path.
    *
-   * @param {import('http').IncomingMessage} req
-   * @returns {object}
+   * @returns {string}
    */
-  parse(req) {
-    const queryStr = req.url.replace(/^[^?]*\??/, "");
-    const query = queryStr ? import_querystring.default.parse(queryStr) : {};
-    const queryKeys = Object.keys(query);
-    if (queryKeys.length) {
-      queryKeys.forEach((key) => {
-        this.debug("The query %v has the value %v.", key, query[key]);
-      });
-    } else {
-      this.debug(
-        "The request %s %v has no query.",
-        req.method,
-        getRequestPathname(req)
-      );
-    }
-    return query;
+  get path() {
+    return this.req.url;
   }
-};
-
-// src/parsers/cookie-parser.js
-var CookieParser = class extends Service2 {
   /**
-   * Parse
+   * Pathname.
    *
-   * @param {import('http').IncomingMessage} req
-   * @returns {object}
+   * @type {string|undefined}
+   * @private
    */
-  parse(req) {
-    const cookieString = req.headers["cookie"] || "";
-    const cookie = parseCookie(cookieString);
-    const cookieKeys = Object.keys(cookie);
-    if (cookieKeys.length) {
-      cookieKeys.forEach((key) => {
-        this.debug("The cookie %v has the value %v.", key, cookie[key]);
-      });
-    } else {
-      this.debug(
-        "The request %s %v has no cookie.",
-        req.method,
-        getRequestPathname(req)
-      );
-    }
-    return cookie;
+  _pathname = void 0;
+  /**
+   * Pathname.
+   *
+   * @returns {string}
+   */
+  get pathname() {
+    if (this._pathname != null) return this._pathname;
+    this._pathname = getRequestPathname(this.req);
+    return this._pathname;
   }
-};
-
-// src/parsers/request-parser.js
-var import_http2 = require("http");
-var RequestParser = class extends Service2 {
   /**
-   * Parse.
+   * Constructor.
    *
-   * @param {IncomingMessage} req
-   * @returns {Promise<object>|object}
+   * @param {ServiceContainer} container
+   * @param {import('http').IncomingMessage} request
+   * @param {import('http').ServerResponse} response
    */
-  parse(req) {
-    if (!(req instanceof import_http2.IncomingMessage))
+  constructor(container, request, response) {
+    if (!(container instanceof ServiceContainer))
       throw new Errorf(
-        "The first argument of RequestParser.parse should be an instance of IncomingMessage, but %v given.",
-        req
+        'The parameter "container" of RequestContext.constructor should be an instance of ServiceContainer, but %v given.',
+        container
+      );
+    this.container = container;
+    if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
+      throw new Errorf(
+        'The parameter "request" of RequestContext.constructor should be an instance of IncomingMessage, but %v given.',
+        request
       );
-    const data = {};
-    const promises = [];
-    const parsedQuery = this.getService(QueryParser).parse(req);
-    if (isPromise(parsedQuery)) {
-      promises.push(parsedQuery.then((v) => data.query = v));
-    } else {
-      data.query = parsedQuery;
-    }
-    const parsedCookie = this.getService(CookieParser).parse(req);
-    if (isPromise(parsedCookie)) {
-      promises.push(parsedCookie.then((v) => data.cookie = v));
-    } else {
-      data.cookie = parsedCookie;
     }
-    const parsedBody = this.getService(BodyParser).parse(req);
-    if (isPromise(parsedBody)) {
-      promises.push(parsedBody.then((v) => data.body = v));
-    } else {
-      data.body = parsedBody;
+    this.req = request;
+    if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
+      throw new Errorf(
+        'The parameter "response" of RequestContext.constructor should be an instance of ServerResponse, but %v given.',
+        response
+      );
     }
-    data.headers = JSON.parse(JSON.stringify(req.headers));
-    return promises.length ? Promise.all(promises).then(() => data) : data;
+    this.res = response;
   }
 };
 
@@ -3329,7 +3443,7 @@ var PathTrie = class {
 };
 
 // src/route-registry.js
-var RouteRegistry = class extends Service2 {
+var RouteRegistry = class extends DebuggableService {
   /**
    * Constructor.
    *
@@ -3407,122 +3521,8 @@ var RouteRegistry = class extends Service2 {
   }
 };
 
-// src/request-context.js
-var RequestContext = class {
-  /**
-   * Service container.
-   *
-   * @type {import('@e22m4u/js-service').ServiceContainer}
-   */
-  container;
-  /**
-   * Request.
-   *
-   * @type {import('http').IncomingMessage}
-   */
-  req;
-  /**
-   * Response.
-   *
-   * @type {import('http').ServerResponse}
-   */
-  res;
-  /**
-   * Query.
-   *
-   * @type {object}
-   */
-  query = {};
-  /**
-   * Path parameters.
-   *
-   * @type {object}
-   */
-  params = {};
-  /**
-   * Parsed body.
-   *
-   * @type {*}
-   */
-  body;
-  /**
-   * Headers.
-   *
-   * @type {object}
-   */
-  headers = {};
-  /**
-   * Parsed cookie.
-   *
-   * @type {object}
-   */
-  cookie = {};
-  /**
-   * Method.
-   *
-   * @returns {string}
-   */
-  get method() {
-    return this.req.method.toUpperCase();
-  }
-  /**
-   * Path.
-   *
-   * @returns {string}
-   */
-  get path() {
-    return this.req.url;
-  }
-  /**
-   * Pathname.
-   *
-   * @type {string|undefined}
-   * @private
-   */
-  _pathname = void 0;
-  /**
-   * Pathname.
-   *
-   * @returns {string}
-   */
-  get pathname() {
-    if (this._pathname != null) return this._pathname;
-    this._pathname = getRequestPathname(this.req);
-    return this._pathname;
-  }
-  /**
-   * Constructor.
-   *
-   * @param {ServiceContainer} container
-   * @param {import('http').IncomingMessage} request
-   * @param {import('http').ServerResponse} response
-   */
-  constructor(container, request, response) {
-    if (!(container instanceof ServiceContainer))
-      throw new Errorf(
-        'The parameter "container" of RequestContext.constructor should be an instance of ServiceContainer, but %v given.',
-        container
-      );
-    this.container = container;
-    if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
-      throw new Errorf(
-        'The parameter "request" of RequestContext.constructor should be an instance of IncomingMessage, but %v given.',
-        request
-      );
-    }
-    this.req = request;
-    if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
-      throw new Errorf(
-        'The parameter "response" of RequestContext.constructor should be an instance of ServerResponse, but %v given.',
-        response
-      );
-    }
-    this.res = response;
-  }
-};
-
 // src/trie-router.js
-var TrieRouter = class extends Service2 {
+var TrieRouter = class extends DebuggableService {
   /**
    * Define route.
    *

+ 3 - 3
src/service.d.ts → src/debuggable-service.d.ts

@@ -1,10 +1,10 @@
 import {Debugger} from './utils/index.js';
-import {Service as BaseService} from '@e22m4u/js-service';
+import {Service} from '@e22m4u/js-service';
 
 /**
- * Service.
+ * Debuggable service.
  */
-declare class Service extends BaseService {
+declare class DebuggableService extends Service {
   /**
    * Debug.
    *

+ 3 - 3
src/service.js → src/debuggable-service.js

@@ -1,12 +1,12 @@
+import {Service} from '@e22m4u/js-service';
 import {toCamelCase} from './utils/index.js';
 import {createDebugger} from './utils/index.js';
 import {ServiceContainer} from '@e22m4u/js-service';
-import {Service as BaseService} from '@e22m4u/js-service';
 
 /**
- * Service.
+ * Debuggable service.
  */
-export class Service extends BaseService {
+export class DebuggableService extends Service {
   /**
    * Debug.
    *

+ 3 - 3
src/service.spec.js → src/debuggable-service.spec.js

@@ -1,10 +1,10 @@
 import {expect} from './chai.js';
-import {Service} from './service.js';
+import {DebuggableService} from './debuggable-service.js';
 
-describe('Service', function () {
+describe('DebuggableService', function () {
   describe('constructor', function () {
     it('sets the debugger to the "debug" property', function () {
-      const service = new Service();
+      const service = new DebuggableService();
       expect(service.debug).to.be.instanceof(Function);
     });
   });

+ 2 - 2
src/hooks/hook-invoker.d.ts

@@ -1,13 +1,13 @@
 import {Route} from '../route.js';
 import {ServerResponse} from 'http';
-import {Service} from '../service.js';
 import {ValueOrPromise} from '../types.js';
 import {HOOK_NAME} from './hook-registry.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Hook invoker.
  */
-export declare class HookInvoker extends Service {
+export declare class HookInvoker extends DebuggableService {
   /**
    * Invoke and continue until value received.
    *

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

@@ -1,15 +1,15 @@
 import {Route} from '../route.js';
-import {Service} from '../service.js';
 import {Errorf} from '@e22m4u/js-format';
 import {isPromise} from '../utils/index.js';
 import {HOOK_NAME} from './hook-registry.js';
 import {HookRegistry} from './hook-registry.js';
 import {isResponseSent} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Hook invoker.
  */
-export class HookInvoker extends Service {
+export class HookInvoker extends DebuggableService {
   /**
    * Invoke and continue until value received.
    *

+ 2 - 2
src/hooks/hook-registry.d.ts

@@ -1,5 +1,5 @@
 import {Callable} from '../types.js';
-import {Service} from '../service.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Hook type.
@@ -17,7 +17,7 @@ export type RouterHook<T = unknown> = Callable<T>;
 /**
  * Hook registry.
  */
-export declare class HookRegistry extends Service {
+export declare class HookRegistry extends DebuggableService {
   /**
    * Add hook.
    *

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

@@ -1,5 +1,5 @@
-import {Service} from '../service.js';
 import {Errorf} from '@e22m4u/js-format';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Router hook.
@@ -17,7 +17,7 @@ export const HOOK_NAME = {
 /**
  * Hook registry.
  */
-export class HookRegistry extends Service {
+export class HookRegistry extends DebuggableService {
   /**
    * Hooks.
    *

+ 8 - 0
src/index.d.ts

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

+ 5 - 5
src/index.js

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

+ 2 - 2
src/parsers/body-parser.d.ts

@@ -1,6 +1,6 @@
 import {IncomingMessage} from 'http';
-import {Service} from '../service.js';
 import {ValueOrPromise} from '../types.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Method names to be parsed.
@@ -20,7 +20,7 @@ export type BodyParserFunction = <T = unknown>(input: string) => T;
 /**
  * Body parser.
  */
-export declare class BodyParser extends Service {
+export declare class BodyParser extends DebuggableService {
   /**
    * Define parser.
    *

+ 2 - 2
src/parsers/body-parser.js

@@ -1,9 +1,9 @@
 import HttpErrors from 'http-errors';
-import {Service} from '../service.js';
 import {Errorf} from '@e22m4u/js-format';
 import {createError} from '../utils/index.js';
 import {RouterOptions} from '../router-options.js';
 import {fetchRequestBody} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 import {parseContentType} from '../utils/parse-content-type.js';
 
 /**
@@ -23,7 +23,7 @@ export const UNPARSABLE_MEDIA_TYPES = ['multipart/form-data'];
 /**
  * Body parser.
  */
-export class BodyParser extends Service {
+export class BodyParser extends DebuggableService {
   /**
    * Parsers.
    *

+ 3 - 9
src/parsers/cookie-parser.d.ts

@@ -1,17 +1,11 @@
 import {IncomingMessage} from 'http';
-import {Service} from '../service.js';
-
-/**
- * Parsed cookie.
- */
-export type ParsedCookie = {
-  [key: string]: string | undefined;
-};
+import {ParsedCookie} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Cookie parser.
  */
-export declare class CookieParser extends Service {
+export declare class CookieParser extends DebuggableService {
   /**
    * Parse.
    *

+ 2 - 2
src/parsers/cookie-parser.js

@@ -1,11 +1,11 @@
-import {Service} from '../service.js';
 import {parseCookie} from '../utils/index.js';
 import {getRequestPathname} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Cookie parser.
  */
-export class CookieParser extends Service {
+export class CookieParser extends DebuggableService {
   /**
    * Parse
    *

+ 2 - 2
src/parsers/query-parser.d.ts

@@ -1,5 +1,5 @@
 import {IncomingMessage} from 'http';
-import {Service} from '../service.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Parsed query.
@@ -11,7 +11,7 @@ export type ParsedQuery = {
 /**
  * Query parser.
  */
-export declare class QueryParser extends Service {
+export declare class QueryParser extends DebuggableService {
   /**
    * Parse.
    *

+ 2 - 2
src/parsers/query-parser.js

@@ -1,11 +1,11 @@
 import querystring from 'querystring';
-import {Service} from '../service.js';
 import {getRequestPathname} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Query parser.
  */
-export class QueryParser extends Service {
+export class QueryParser extends DebuggableService {
   /**
    * Parse
    *

+ 3 - 3
src/parsers/request-parser.d.ts

@@ -1,8 +1,8 @@
 import {IncomingMessage} from 'http';
-import {Service} from '../service.js';
 import {ValueOrPromise} from '../types.js';
 import {ParsedQuery} from './query-parser.js';
-import {ParsedCookie} from './cookie-parser.js';
+import {ParsedCookie} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Parsed headers.
@@ -24,7 +24,7 @@ type ParsedRequestData = {
 /**
  * Request parser.
  */
-export declare class RequestParser extends Service {
+export declare class RequestParser extends DebuggableService {
   /**
    * Parse.
    *

+ 2 - 2
src/parsers/request-parser.js

@@ -1,15 +1,15 @@
 import {IncomingMessage} from 'http';
-import {Service} from '../service.js';
 import {Errorf} from '@e22m4u/js-format';
 import {isPromise} from '../utils/index.js';
 import {BodyParser} from './body-parser.js';
 import {QueryParser} from './query-parser.js';
 import {CookieParser} from './cookie-parser.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Request parser.
  */
-export class RequestParser extends Service {
+export class RequestParser extends DebuggableService {
   /**
    * Parse.
    *

+ 1 - 1
src/request-context.d.ts

@@ -1,7 +1,7 @@
 import {ServerResponse} from 'http';
 import {IncomingMessage} from 'http';
+import {ParsedCookie} from './utils/index.js';
 import {ParsedQuery} from './parsers/index.js';
-import {ParsedCookie} from './parsers/index.js';
 import {ParsedHeaders} from './parsers/index.js';
 import {ServiceContainer} from '@e22m4u/js-service';
 

+ 2 - 2
src/route-registry.d.ts

@@ -1,8 +1,8 @@
 import {Route} from './route.js';
-import {Service} from './service.js';
 import {IncomingMessage} from 'http';
 import {RouteDefinition} from './route.js';
 import {ServiceContainer} from '@e22m4u/js-service';
+import {DebuggableService} from './debuggable-service.js';
 
 /**
  * Resolved route.
@@ -15,7 +15,7 @@ export type ResolvedRoute = {
 /**
  * Route registry.
  */
-export declare class RouteRegistry extends Service {
+export declare class RouteRegistry extends DebuggableService {
   /**
    * Constructor.
    *

+ 2 - 2
src/route-registry.js

@@ -1,8 +1,8 @@
 import {Route} from './route.js';
-import {Service} from './service.js';
 import {Errorf} from '@e22m4u/js-format';
 import {PathTrie} from '@e22m4u/js-path-trie';
 import {ServiceContainer} from '@e22m4u/js-service';
+import {DebuggableService} from './debuggable-service.js';
 
 /**
  * @typedef {{
@@ -14,7 +14,7 @@ import {ServiceContainer} from '@e22m4u/js-service';
 /**
  * Route registry.
  */
-export class RouteRegistry extends Service {
+export class RouteRegistry extends DebuggableService {
   /**
    * Constructor.
    *

+ 2 - 2
src/router-options.d.ts

@@ -1,9 +1,9 @@
-import {Service} from './service.js';
+import {DebuggableService} from './debuggable-service.js';
 
 /**
  * Router options.
  */
-export declare class RouterOptions extends Service {
+export declare class RouterOptions extends DebuggableService {
   /**
    * Request body bytes limit.
    */

+ 2 - 2
src/router-options.js

@@ -1,10 +1,10 @@
-import {Service} from './service.js';
 import {Errorf} from '@e22m4u/js-format';
+import {DebuggableService} from './debuggable-service.js';
 
 /**
  * Router options.
  */
-export class RouterOptions extends Service {
+export class RouterOptions extends DebuggableService {
   /**
    * Request body bytes limit.
    *

+ 2 - 2
src/senders/data-sender.d.ts

@@ -1,10 +1,10 @@
-import {Service} from '../service.js';
 import {ServerResponse} from 'http';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Data sender.
  */
-export declare class DataSender extends Service {
+export declare class DataSender extends DebuggableService {
   /**
    * Send.
    *

+ 2 - 2
src/senders/data-sender.js

@@ -1,11 +1,11 @@
-import {Service} from '../service.js';
 import {format} from '@e22m4u/js-format';
 import {isReadableStream} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Data sender.
  */
-export class DataSender extends Service {
+export class DataSender extends DebuggableService {
   /**
    * Send.
    *

+ 2 - 2
src/senders/error-sender.d.ts

@@ -1,11 +1,11 @@
 import {ServerResponse} from 'http';
 import {IncomingMessage} from 'http';
-import {Service} from '../service.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Error sender.
  */
-export declare class ErrorSender extends Service {
+export declare class ErrorSender extends DebuggableService {
   /**
    * Send.
    *

+ 2 - 2
src/senders/error-sender.js

@@ -1,7 +1,7 @@
 import {inspect} from 'util';
-import {Service} from '../service.js';
 import getStatusMessage from 'statuses';
 import {getRequestPathname} from '../utils/index.js';
+import {DebuggableService} from '../debuggable-service.js';
 
 /**
  * Exposed error properties.
@@ -13,7 +13,7 @@ export const EXPOSED_ERROR_PROPERTIES = ['code', 'details'];
 /**
  * Error sender.
  */
-export class ErrorSender extends Service {
+export class ErrorSender extends DebuggableService {
   /**
    * Handle.
    *

+ 2 - 2
src/trie-router.d.ts

@@ -1,14 +1,14 @@
 import {Route} from './route.js';
-import {Service} from './service.js';
 import {RequestListener} from 'http';
 import {RouteDefinition} from './route.js';
 import {HOOK_NAME} from './hooks/index.js';
 import {RouterHook} from './hooks/index.js';
+import {DebuggableService} from './debuggable-service.js';
 
 /**
  * Trie router.
  */
-export declare class TrieRouter extends Service {
+export declare class TrieRouter extends DebuggableService {
   /**
    * Define route.
    *

+ 2 - 2
src/trie-router.js

@@ -1,4 +1,3 @@
-import {Service} from './service.js';
 import {isPromise} from './utils/index.js';
 import {HOOK_NAME} from './hooks/index.js';
 import {HookInvoker} from './hooks/index.js';
@@ -9,11 +8,12 @@ import {RequestParser} from './parsers/index.js';
 import {RouteRegistry} from './route-registry.js';
 import {RequestContext} from './request-context.js';
 import {ServiceContainer} from '@e22m4u/js-service';
+import {DebuggableService} from './debuggable-service.js';
 
 /**
  * Trie router.
  */
-export class TrieRouter extends Service {
+export class TrieRouter extends DebuggableService {
   /**
    * Define route.
    *