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 {
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<TContext extends IContext = IContext>
*/
private async executePipeline(
ctx: TContext,
middleware: IMiddleware<TContext>[],
handler: IHandler<TContext>,
middleware: Middleware<TContext>[],
handler: Handler<TContext>,
): Promise<Response> {
const handleInternalError = (ctx: TContext, err?: unknown) =>
this.cfg.httpErrorHandlers[HTTP_500_INTERNAL_SERVER_ERROR](

View File

@@ -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<TContext extends IContext = IContext> {
/**
* 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.
*/
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 { IMiddleware } from './IMiddleware.ts';
import { IRouteDefinition } from './IRouteDefinition.ts';
import { IContext } from './mod.ts';
@@ -8,7 +7,7 @@ export interface IRouteBuilderFactory<TContext extends IContext = IContext> {
new (
registerRoute: (route: IInternalRoute<TContext>) => void,
def: IRouteDefinition,
mws?: IMiddleware<TContext>[],
mws?: Middleware<TContext>[],
): IRouteBuilder<TContext>;
}
@@ -25,7 +24,7 @@ export interface IRouteBuilder<TContext extends IContext = IContext> {
* @returns The route builder for further chaining.
*/
middleware(
mw: IMiddleware<TContext>,
mw: Middleware<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.
*/
handle(
handler: IHandler<TContext>,
handler: Handler<TContext>,
): void;
}

View File

@@ -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,

View File

@@ -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<TContext extends IContext = IContext>
constructor(
private readonly registerRoute: RegisterRoute<TContext>,
private readonly def: IRouteDefinition,
private readonly mws: IMiddleware<TContext>[] = [],
private readonly mws: Middleware<TContext>[] = [],
private readonly matcherFactory: IRouteMatcherFactory =
createRouteMatcher,
) {}
@@ -42,7 +36,7 @@ export class RouteBuilder<TContext extends IContext = IContext>
* @returns A new `RouteBuilder` instance for continued chaining.
*/
middleware(
mw: IMiddleware<TContext>,
mw: Middleware<TContext>,
): IRouteBuilder<TContext> {
return new RouteBuilder<TContext>(
this.registerRoute,
@@ -60,7 +54,7 @@ export class RouteBuilder<TContext extends IContext = IContext>
* @param handler - The final request handler for this route.
*/
handle(
handler: IHandler<TContext>,
handler: Handler<TContext>,
): void {
const matcher = this.matcherFactory(this.def);
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.
@@ -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<TContext extends IContext = IContext> {
/**
* 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<Response>;
}
type Handler<TContext extends IContext = IContext> = (
ctx: TContext,
) => Promise<Response>;
/**
* 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<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.
@@ -42,7 +49,7 @@ export interface IHandler<TContext extends IContext = IContext> {
*/
export function isHandler<TContext extends IContext = IContext>(
value: unknown,
): value is IHandler<TContext> {
): value is Handler<TContext> {
return (
typeof value === 'function' &&
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.
@@ -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<TContext extends IContext = IContext> {
/**
* 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<Response>): Promise<Response>;
}
type Middleware<TContext extends IContext = IContext> = (
ctx: TContext,
next: () => Promise<Response>,
) => Promise<Response>;
/**
* 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<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.
@@ -35,7 +43,7 @@ export interface IMiddleware<TContext extends IContext = IContext> {
*/
export function isMiddleware<TContext extends IContext = IContext>(
value: unknown,
): value is IMiddleware<TContext> {
): value is Middleware<TContext> {
return (
typeof value === 'function' &&
value.length === 2 // ctx, next

View File

@@ -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';

View File

@@ -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

View File

@@ -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;