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
This commit is contained in:
2025-05-08 19:03:18 +02:00
parent 56633cd95b
commit 8235680904
10 changed files with 71 additions and 66 deletions

View File

@@ -1,20 +1,20 @@
import { import {
IContext, IContext,
IHandler,
IHttpKernel, IHttpKernel,
IHttpKernelConfig, IHttpKernelConfig,
IInternalRoute, IInternalRoute,
IMiddleware,
IRouteBuilder, IRouteBuilder,
IRouteDefinition, IRouteDefinition,
isHandler,
isMiddleware,
} from './Interfaces/mod.ts'; } from './Interfaces/mod.ts';
import { import {
DeepPartial, DeepPartial,
Handler,
HTTP_404_NOT_FOUND, HTTP_404_NOT_FOUND,
HTTP_500_INTERNAL_SERVER_ERROR, HTTP_500_INTERNAL_SERVER_ERROR,
HttpStatusTextMap, HttpStatusTextMap,
isHandler,
isMiddleware,
Middleware,
} from './Types/mod.ts'; } from './Types/mod.ts';
import { RouteBuilder } from './RouteBuilder.ts'; import { RouteBuilder } from './RouteBuilder.ts';
import { createEmptyContext, normalizeError } from './Utils/mod.ts'; import { createEmptyContext, normalizeError } from './Utils/mod.ts';
@@ -151,8 +151,8 @@ export class HttpKernel<TContext extends IContext = IContext>
*/ */
private async executePipeline( private async executePipeline(
ctx: TContext, ctx: TContext,
middleware: IMiddleware<TContext>[], middleware: Middleware<TContext>[],
handler: IHandler<TContext>, handler: Handler<TContext>,
): Promise<Response> { ): Promise<Response> {
const handleInternalError = (ctx: TContext, err?: unknown) => const handleInternalError = (ctx: TContext, err?: unknown) =>
this.cfg.httpErrorHandlers[HTTP_500_INTERNAL_SERVER_ERROR]( this.cfg.httpErrorHandlers[HTTP_500_INTERNAL_SERVER_ERROR](

View File

@@ -1,6 +1,4 @@
import { HttpMethod } from '../Types/mod.ts'; import { Handler, HttpMethod, Middleware } from '../Types/mod.ts';
import { IHandler } from './IHandler.ts';
import { IMiddleware } from './IMiddleware.ts';
import { IContext, IRouteMatcher } from './mod.ts'; import { IContext, IRouteMatcher } from './mod.ts';
/** /**
@@ -32,10 +30,10 @@ export interface IInternalRoute<TContext extends IContext = IContext> {
/** /**
* An ordered list of middleware functions to be executed before the handler. * An ordered list of middleware functions to be executed before the handler.
*/ */
middlewares: IMiddleware<TContext>[]; middlewares: Middleware<TContext>[];
/** /**
* The final handler that generates the HTTP response after all middleware has run. * The final handler that generates the HTTP response after all middleware has run.
*/ */
handler: IHandler<TContext>; handler: Handler<TContext>;
} }

View File

@@ -1,6 +1,5 @@
import { IHandler } from './IHandler.ts'; import { Handler, Middleware } from '../Types/mod.ts';
import { IInternalRoute } from './IInternalRoute.ts'; import { IInternalRoute } from './IInternalRoute.ts';
import { IMiddleware } from './IMiddleware.ts';
import { IRouteDefinition } from './IRouteDefinition.ts'; import { IRouteDefinition } from './IRouteDefinition.ts';
import { IContext } from './mod.ts'; import { IContext } from './mod.ts';
@@ -8,7 +7,7 @@ export interface IRouteBuilderFactory<TContext extends IContext = IContext> {
new ( new (
registerRoute: (route: IInternalRoute<TContext>) => void, registerRoute: (route: IInternalRoute<TContext>) => void,
def: IRouteDefinition, def: IRouteDefinition,
mws?: IMiddleware<TContext>[], mws?: Middleware<TContext>[],
): IRouteBuilder<TContext>; ): IRouteBuilder<TContext>;
} }
@@ -25,7 +24,7 @@ export interface IRouteBuilder<TContext extends IContext = IContext> {
* @returns The route builder for further chaining. * @returns The route builder for further chaining.
*/ */
middleware( middleware(
mw: IMiddleware<TContext>, mw: Middleware<TContext>,
): IRouteBuilder<TContext>; ): IRouteBuilder<TContext>;
/** /**
@@ -35,6 +34,6 @@ export interface IRouteBuilder<TContext extends IContext = IContext> {
* @param handler - The function to execute when this route is matched. * @param handler - The function to execute when this route is matched.
*/ */
handle( handle(
handler: IHandler<TContext>, handler: Handler<TContext>,
): void; ): void;
} }

View File

@@ -1,14 +1,10 @@
// deno-coverage-ignore-file // deno-coverage-ignore-file
export type { IContext } from './IContext.ts'; 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 { IHttpErrorHandlers } from './IHttpErrorHandlers.ts';
export type { IHttpKernel } from './IHttpKernel.ts'; export type { IHttpKernel } from './IHttpKernel.ts';
export type { IHttpKernelConfig } from './IHttpKernelConfig.ts'; export type { IHttpKernelConfig } from './IHttpKernelConfig.ts';
export type { IInternalRoute } from './IInternalRoute.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 type { IRouteBuilder, IRouteBuilderFactory } from './IRouteBuilder.ts';
export { export {
isDynamicRouteDefinition, isDynamicRouteDefinition,

View File

@@ -1,12 +1,6 @@
import { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts'; import { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts';
import { import { IContext, IRouteBuilder, IRouteDefinition } from './Interfaces/mod.ts';
IContext, import { Handler, Middleware, RegisterRoute } from './Types/mod.ts';
IHandler,
IMiddleware,
IRouteBuilder,
IRouteDefinition,
} from './Interfaces/mod.ts';
import { RegisterRoute } from './Types/mod.ts';
import { createRouteMatcher } from './Utils/createRouteMatcher.ts'; import { createRouteMatcher } from './Utils/createRouteMatcher.ts';
/** /**
@@ -27,7 +21,7 @@ export class RouteBuilder<TContext extends IContext = IContext>
constructor( constructor(
private readonly registerRoute: RegisterRoute<TContext>, private readonly registerRoute: RegisterRoute<TContext>,
private readonly def: IRouteDefinition, private readonly def: IRouteDefinition,
private readonly mws: IMiddleware<TContext>[] = [], private readonly mws: Middleware<TContext>[] = [],
private readonly matcherFactory: IRouteMatcherFactory = private readonly matcherFactory: IRouteMatcherFactory =
createRouteMatcher, createRouteMatcher,
) {} ) {}
@@ -42,7 +36,7 @@ export class RouteBuilder<TContext extends IContext = IContext>
* @returns A new `RouteBuilder` instance for continued chaining. * @returns A new `RouteBuilder` instance for continued chaining.
*/ */
middleware( middleware(
mw: IMiddleware<TContext>, mw: Middleware<TContext>,
): IRouteBuilder<TContext> { ): IRouteBuilder<TContext> {
return new RouteBuilder<TContext>( return new RouteBuilder<TContext>(
this.registerRoute, this.registerRoute,
@@ -60,7 +54,7 @@ export class RouteBuilder<TContext extends IContext = IContext>
* @param handler - The final request handler for this route. * @param handler - The final request handler for this route.
*/ */
handle( handle(
handler: IHandler<TContext>, handler: Handler<TContext>,
): void { ): void {
const matcher = this.matcherFactory(this.def); const matcher = this.matcherFactory(this.def);
this.registerRoute({ this.registerRoute({

View File

@@ -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. * 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`. * @template TContext The specific context type for this handler, including typed `state`, `params`, and `query`.
*/ */
export interface IHandler<TContext extends IContext = IContext> { type Handler<TContext extends IContext = IContext> = (
/** ctx: TContext,
* Handles the request and generates a response. ) => Promise<Response>;
*
* @param ctx - The complete request context, including request metadata, route and query parameters, /**
* and mutable state populated during the middleware phase. * Represents a handler function with an associated name.
* @returns A `Promise` resolving to an HTTP `Response` to be sent to the client. *
*/ * This is useful for debugging, logging, or when you need to reference
(ctx: TContext): Promise<Response>; * the handler by name in your application.
} *
* @template TContext The specific context type for this handler, including typed `state`, `params`, and `query`.
*/
type NamedHandler<TContext extends IContext = IContext> =
& Handler<TContext>
& { name?: string };
export type { NamedHandler as Handler };
/** /**
* Type guard to determine whether a given value is a valid `IHandler` function. * Type guard to determine whether a given value is a valid `IHandler` function.
@@ -42,7 +49,7 @@ export interface IHandler<TContext extends IContext = IContext> {
*/ */
export function isHandler<TContext extends IContext = IContext>( export function isHandler<TContext extends IContext = IContext>(
value: unknown, value: unknown,
): value is IHandler<TContext> { ): value is Handler<TContext> {
return ( return (
typeof value === 'function' && typeof value === 'function' &&
value.length === 1 // ctx value.length === 1 // ctx

View File

@@ -1,4 +1,4 @@
import { IContext } from './IContext.ts'; import { IContext } from '../Interfaces/IContext.ts';
/** /**
* Represents a middleware function in the HTTP request pipeline. * 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. * @template TContext The specific context type for this middleware, including state, params, and query information.
*/ */
export interface IMiddleware<TContext extends IContext = IContext> { type Middleware<TContext extends IContext = IContext> = (
/** ctx: TContext,
* Handles the request processing at this middleware stage. next: () => Promise<Response>,
* ) => Promise<Response>;
* @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. * Represents a middleware function with an associated name.
*/ *
(ctx: TContext, next: () => Promise<Response>): Promise<Response>; * 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<TContext extends IContext = IContext> =
& Middleware<TContext>
& { name?: string };
export type { NamedMiddleware as Middleware };
/** /**
* Type guard to verify whether a given value is a valid `IMiddleware` function. * Type guard to verify whether a given value is a valid `IMiddleware` function.
@@ -35,7 +43,7 @@ export interface IMiddleware<TContext extends IContext = IContext> {
*/ */
export function isMiddleware<TContext extends IContext = IContext>( export function isMiddleware<TContext extends IContext = IContext>(
value: unknown, value: unknown,
): value is IMiddleware<TContext> { ): value is Middleware<TContext> {
return ( return (
typeof value === 'function' && typeof value === 'function' &&
value.length === 2 // ctx, next value.length === 2 // ctx, next

View File

@@ -1,6 +1,8 @@
// deno-coverage-ignore-file // deno-coverage-ignore-file
export type { DeepPartial } from './DeepPartial.ts'; export type { DeepPartial } from './DeepPartial.ts';
export { isHandler } from './Handler.ts';
export type { Handler } from './Handler.ts';
export type { HttpErrorHandler } from './HttpErrorHandler.ts'; export type { HttpErrorHandler } from './HttpErrorHandler.ts';
export { isHttpMethod, validHttpMethods } from './HttpMethod.ts'; export { isHttpMethod, validHttpMethods } from './HttpMethod.ts';
export type { HttpMethod } from './HttpMethod.ts'; export type { HttpMethod } from './HttpMethod.ts';
@@ -34,6 +36,8 @@ export {
validHttpStatusCodes, validHttpStatusCodes,
} from './HttpStatusCode.ts'; } from './HttpStatusCode.ts';
export type { HttpStatusCode } 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 { Params } from './Params.ts';
export type { Query } from './Query.ts'; export type { Query } from './Query.ts';
export type { RegisterRoute } from './RegisterRoute.ts'; export type { RegisterRoute } from './RegisterRoute.ts';

View File

@@ -35,7 +35,9 @@ export function createRouteMatcher(
// 3b. Extract route params // 3b. Extract route params
const params: Params = {}; const params: Params = {};
for (const [key, value] of Object.entries(result.pathname.groups)) { 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 // 3c. Extract query parameters – keep duplicates as arrays

View File

@@ -4,17 +4,14 @@ import {
assertNotEquals, assertNotEquals,
assertThrows, assertThrows,
} from 'https://deno.land/std@0.204.0/assert/mod.ts'; } from 'https://deno.land/std@0.204.0/assert/mod.ts';
import { import { IInternalRoute, IRouteDefinition } from '../Interfaces/mod.ts';
IHandler,
IInternalRoute,
IMiddleware,
IRouteDefinition,
} from '../Interfaces/mod.ts';
import { RouteBuilder } from '../mod.ts'; import { RouteBuilder } from '../mod.ts';
import { Handler, Middleware } from '../Types/mod.ts';
// Dummy objects // Dummy objects
const dummyHandler: IHandler = async () => new Response('ok'); // deno-lint-ignore require-await
const dummyMiddleware: IMiddleware = async (_, next) => await next(); const dummyHandler: Handler = async () => new Response('ok');
const dummyMiddleware: Middleware = async (_, next) => await next();
const dummyDef: IRouteDefinition = { method: 'GET', path: '/hello' }; const dummyDef: IRouteDefinition = { method: 'GET', path: '/hello' };
const dummyMatcher = () => ({ params: {} }); const dummyMatcher = () => ({ params: {} });
@@ -39,8 +36,8 @@ Deno.test('middleware: middleware is chained immutably', () => {
}); });
Deno.test('middleware: preserves order of middleware', () => { Deno.test('middleware: preserves order of middleware', () => {
const mw1: IMiddleware = async (_, next) => await next(); const mw1: Middleware = async (_, next) => await next();
const mw2: IMiddleware = async (_, next) => await next(); const mw2: Middleware = async (_, next) => await next();
let result: IInternalRoute | null = null as IInternalRoute | null; let result: IInternalRoute | null = null as IInternalRoute | null;