From 8235680904c7f30f25b98b835d48376431108e91 Mon Sep 17 00:00:00 2001 From: "Max P." Date: Thu, 8 May 2025 19:03:18 +0200 Subject: [PATCH] refactor(types): unify handler and middleware definitions - Consolidates `Handler` and `Middleware` types under `Types` module - Replaces `IHandler` and `IMiddleware` interfaces with typed functions - Simplifies imports and improves code organization - Enhances debugging with named handlers and middlewares --- src/HttpKernel.ts | 12 +++---- src/Interfaces/IInternalRoute.ts | 8 ++--- src/Interfaces/IRouteBuilder.ts | 9 +++--- src/Interfaces/mod.ts | 4 --- src/RouteBuilder.ts | 16 +++------- .../IHandler.ts => Types/Handler.ts} | 31 +++++++++++------- .../IMiddleware.ts => Types/Middleware.ts} | 32 ++++++++++++------- src/Types/mod.ts | 4 +++ src/Utils/createRouteMatcher.ts | 4 ++- src/__tests__/RouteBuilder.test.ts | 17 ++++------ 10 files changed, 71 insertions(+), 66 deletions(-) rename src/{Interfaces/IHandler.ts => Types/Handler.ts} (68%) rename src/{Interfaces/IMiddleware.ts => Types/Middleware.ts} (63%) diff --git a/src/HttpKernel.ts b/src/HttpKernel.ts index ea1c99e..25ababb 100644 --- a/src/HttpKernel.ts +++ b/src/HttpKernel.ts @@ -1,20 +1,20 @@ import { IContext, - IHandler, IHttpKernel, IHttpKernelConfig, IInternalRoute, - IMiddleware, IRouteBuilder, IRouteDefinition, - isHandler, - isMiddleware, } from './Interfaces/mod.ts'; import { DeepPartial, + Handler, HTTP_404_NOT_FOUND, HTTP_500_INTERNAL_SERVER_ERROR, HttpStatusTextMap, + isHandler, + isMiddleware, + Middleware, } from './Types/mod.ts'; import { RouteBuilder } from './RouteBuilder.ts'; import { createEmptyContext, normalizeError } from './Utils/mod.ts'; @@ -151,8 +151,8 @@ export class HttpKernel */ private async executePipeline( ctx: TContext, - middleware: IMiddleware[], - handler: IHandler, + middleware: Middleware[], + handler: Handler, ): Promise { const handleInternalError = (ctx: TContext, err?: unknown) => this.cfg.httpErrorHandlers[HTTP_500_INTERNAL_SERVER_ERROR]( diff --git a/src/Interfaces/IInternalRoute.ts b/src/Interfaces/IInternalRoute.ts index c2a8804..7565a4a 100644 --- a/src/Interfaces/IInternalRoute.ts +++ b/src/Interfaces/IInternalRoute.ts @@ -1,6 +1,4 @@ -import { HttpMethod } from '../Types/mod.ts'; -import { IHandler } from './IHandler.ts'; -import { IMiddleware } from './IMiddleware.ts'; +import { Handler, HttpMethod, Middleware } from '../Types/mod.ts'; import { IContext, IRouteMatcher } from './mod.ts'; /** @@ -32,10 +30,10 @@ export interface IInternalRoute { /** * An ordered list of middleware functions to be executed before the handler. */ - middlewares: IMiddleware[]; + middlewares: Middleware[]; /** * The final handler that generates the HTTP response after all middleware has run. */ - handler: IHandler; + handler: Handler; } diff --git a/src/Interfaces/IRouteBuilder.ts b/src/Interfaces/IRouteBuilder.ts index 9d2e0a4..24ee1a4 100644 --- a/src/Interfaces/IRouteBuilder.ts +++ b/src/Interfaces/IRouteBuilder.ts @@ -1,6 +1,5 @@ -import { IHandler } from './IHandler.ts'; +import { Handler, Middleware } from '../Types/mod.ts'; import { IInternalRoute } from './IInternalRoute.ts'; -import { IMiddleware } from './IMiddleware.ts'; import { IRouteDefinition } from './IRouteDefinition.ts'; import { IContext } from './mod.ts'; @@ -8,7 +7,7 @@ export interface IRouteBuilderFactory { new ( registerRoute: (route: IInternalRoute) => void, def: IRouteDefinition, - mws?: IMiddleware[], + mws?: Middleware[], ): IRouteBuilder; } @@ -25,7 +24,7 @@ export interface IRouteBuilder { * @returns The route builder for further chaining. */ middleware( - mw: IMiddleware, + mw: Middleware, ): IRouteBuilder; /** @@ -35,6 +34,6 @@ export interface IRouteBuilder { * @param handler - The function to execute when this route is matched. */ handle( - handler: IHandler, + handler: Handler, ): void; } diff --git a/src/Interfaces/mod.ts b/src/Interfaces/mod.ts index c1d0e58..7c235d9 100644 --- a/src/Interfaces/mod.ts +++ b/src/Interfaces/mod.ts @@ -1,14 +1,10 @@ // deno-coverage-ignore-file export type { IContext } from './IContext.ts'; -export { isHandler } from './IHandler.ts'; -export type { IHandler } from './IHandler.ts'; export type { IHttpErrorHandlers } from './IHttpErrorHandlers.ts'; export type { IHttpKernel } from './IHttpKernel.ts'; export type { IHttpKernelConfig } from './IHttpKernelConfig.ts'; export type { IInternalRoute } from './IInternalRoute.ts'; -export { isMiddleware } from './IMiddleware.ts'; -export type { IMiddleware } from './IMiddleware.ts'; export type { IRouteBuilder, IRouteBuilderFactory } from './IRouteBuilder.ts'; export { isDynamicRouteDefinition, diff --git a/src/RouteBuilder.ts b/src/RouteBuilder.ts index 872e488..c6e6e8d 100644 --- a/src/RouteBuilder.ts +++ b/src/RouteBuilder.ts @@ -1,12 +1,6 @@ import { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts'; -import { - IContext, - IHandler, - IMiddleware, - IRouteBuilder, - IRouteDefinition, -} from './Interfaces/mod.ts'; -import { RegisterRoute } from './Types/mod.ts'; +import { IContext, IRouteBuilder, IRouteDefinition } from './Interfaces/mod.ts'; +import { Handler, Middleware, RegisterRoute } from './Types/mod.ts'; import { createRouteMatcher } from './Utils/createRouteMatcher.ts'; /** @@ -27,7 +21,7 @@ export class RouteBuilder constructor( private readonly registerRoute: RegisterRoute, private readonly def: IRouteDefinition, - private readonly mws: IMiddleware[] = [], + private readonly mws: Middleware[] = [], private readonly matcherFactory: IRouteMatcherFactory = createRouteMatcher, ) {} @@ -42,7 +36,7 @@ export class RouteBuilder * @returns A new `RouteBuilder` instance for continued chaining. */ middleware( - mw: IMiddleware, + mw: Middleware, ): IRouteBuilder { return new RouteBuilder( this.registerRoute, @@ -60,7 +54,7 @@ export class RouteBuilder * @param handler - The final request handler for this route. */ handle( - handler: IHandler, + handler: Handler, ): void { const matcher = this.matcherFactory(this.def); this.registerRoute({ diff --git a/src/Interfaces/IHandler.ts b/src/Types/Handler.ts similarity index 68% rename from src/Interfaces/IHandler.ts rename to src/Types/Handler.ts index 80b9a53..0a67a6e 100644 --- a/src/Interfaces/IHandler.ts +++ b/src/Types/Handler.ts @@ -1,4 +1,4 @@ -import { IContext } from './IContext.ts'; +import { IContext } from '../Interfaces/mod.ts'; /** * Represents a final request handler responsible for producing an HTTP response. @@ -11,16 +11,23 @@ import { IContext } from './IContext.ts'; * * @template TContext The specific context type for this handler, including typed `state`, `params`, and `query`. */ -export interface IHandler { - /** - * Handles the request and generates a response. - * - * @param ctx - The complete request context, including request metadata, route and query parameters, - * and mutable state populated during the middleware phase. - * @returns A `Promise` resolving to an HTTP `Response` to be sent to the client. - */ - (ctx: TContext): Promise; -} +type Handler = ( + ctx: TContext, +) => Promise; + +/** + * Represents a handler function with an associated name. + * + * This is useful for debugging, logging, or when you need to reference + * the handler by name in your application. + * + * @template TContext The specific context type for this handler, including typed `state`, `params`, and `query`. + */ +type NamedHandler = + & Handler + & { name?: string }; + +export type { NamedHandler as Handler }; /** * Type guard to determine whether a given value is a valid `IHandler` function. @@ -42,7 +49,7 @@ export interface IHandler { */ export function isHandler( value: unknown, -): value is IHandler { +): value is Handler { return ( typeof value === 'function' && value.length === 1 // ctx diff --git a/src/Interfaces/IMiddleware.ts b/src/Types/Middleware.ts similarity index 63% rename from src/Interfaces/IMiddleware.ts rename to src/Types/Middleware.ts index b7b38a4..0001af4 100644 --- a/src/Interfaces/IMiddleware.ts +++ b/src/Types/Middleware.ts @@ -1,4 +1,4 @@ -import { IContext } from './IContext.ts'; +import { IContext } from '../Interfaces/IContext.ts'; /** * Represents a middleware function in the HTTP request pipeline. @@ -13,16 +13,24 @@ import { IContext } from './IContext.ts'; * * @template TContext The specific context type for this middleware, including state, params, and query information. */ -export interface IMiddleware { - /** - * Handles the request processing at this middleware stage. - * - * @param ctx - The full request context, containing request, params, query, and typed state. - * @param next - A continuation function that executes the next middleware or handler in the pipeline. - * @returns A `Promise` resolving to an HTTP `Response`, either from this middleware or downstream. - */ - (ctx: TContext, next: () => Promise): Promise; -} +type Middleware = ( + ctx: TContext, + next: () => Promise, +) => Promise; + +/** + * Represents a middleware function with an associated name. + * + * This is useful for debugging, logging, or when you need to reference + * the middleware by name in your application. + * + * @template TContext The specific context type for this middleware, including state, params, and query information. + */ +type NamedMiddleware = + & Middleware + & { name?: string }; + +export type { NamedMiddleware as Middleware }; /** * Type guard to verify whether a given value is a valid `IMiddleware` function. @@ -35,7 +43,7 @@ export interface IMiddleware { */ export function isMiddleware( value: unknown, -): value is IMiddleware { +): value is Middleware { return ( typeof value === 'function' && value.length === 2 // ctx, next diff --git a/src/Types/mod.ts b/src/Types/mod.ts index b86f3e9..c159495 100644 --- a/src/Types/mod.ts +++ b/src/Types/mod.ts @@ -1,6 +1,8 @@ // deno-coverage-ignore-file export type { DeepPartial } from './DeepPartial.ts'; +export { isHandler } from './Handler.ts'; +export type { Handler } from './Handler.ts'; export type { HttpErrorHandler } from './HttpErrorHandler.ts'; export { isHttpMethod, validHttpMethods } from './HttpMethod.ts'; export type { HttpMethod } from './HttpMethod.ts'; @@ -34,6 +36,8 @@ export { validHttpStatusCodes, } from './HttpStatusCode.ts'; export type { HttpStatusCode } from './HttpStatusCode.ts'; +export { isMiddleware } from './Middleware.ts'; +export type { Middleware } from './Middleware.ts'; export type { Params } from './Params.ts'; export type { Query } from './Query.ts'; export type { RegisterRoute } from './RegisterRoute.ts'; diff --git a/src/Utils/createRouteMatcher.ts b/src/Utils/createRouteMatcher.ts index 97f5477..ea3775f 100644 --- a/src/Utils/createRouteMatcher.ts +++ b/src/Utils/createRouteMatcher.ts @@ -35,7 +35,9 @@ export function createRouteMatcher( // 3b. Extract route params const params: Params = {}; for (const [key, value] of Object.entries(result.pathname.groups)) { - params[key] = value ?? ''; // null → empty string + if (value) { + params[key] = value; + } } // 3c. Extract query parameters – keep duplicates as arrays diff --git a/src/__tests__/RouteBuilder.test.ts b/src/__tests__/RouteBuilder.test.ts index 7aab899..1e9cc77 100644 --- a/src/__tests__/RouteBuilder.test.ts +++ b/src/__tests__/RouteBuilder.test.ts @@ -4,17 +4,14 @@ import { assertNotEquals, assertThrows, } from 'https://deno.land/std@0.204.0/assert/mod.ts'; -import { - IHandler, - IInternalRoute, - IMiddleware, - IRouteDefinition, -} from '../Interfaces/mod.ts'; +import { IInternalRoute, IRouteDefinition } from '../Interfaces/mod.ts'; import { RouteBuilder } from '../mod.ts'; +import { Handler, Middleware } from '../Types/mod.ts'; // Dummy objects -const dummyHandler: IHandler = async () => new Response('ok'); -const dummyMiddleware: IMiddleware = async (_, next) => await next(); +// deno-lint-ignore require-await +const dummyHandler: Handler = async () => new Response('ok'); +const dummyMiddleware: Middleware = async (_, next) => await next(); const dummyDef: IRouteDefinition = { method: 'GET', path: '/hello' }; const dummyMatcher = () => ({ params: {} }); @@ -39,8 +36,8 @@ Deno.test('middleware: middleware is chained immutably', () => { }); Deno.test('middleware: preserves order of middleware', () => { - const mw1: IMiddleware = async (_, next) => await next(); - const mw2: IMiddleware = async (_, next) => await next(); + const mw1: Middleware = async (_, next) => await next(); + const mw2: Middleware = async (_, next) => await next(); let result: IInternalRoute | null = null as IInternalRoute | null;