From a236fa7c97ae49e6baf560d4ca92c6e83702b3ec Mon Sep 17 00:00:00 2001 From: "Max P." Date: Wed, 7 May 2025 12:29:49 +0200 Subject: [PATCH] feat(http): enhance type safety and extend route context - Refactor HttpKernel and related interfaces to support generic contexts. - Add typed query parameters, route params, and state to IContext. - Introduce HttpMethod type for stricter HTTP method validation. - Update RouteBuilder and middleware to handle generic contexts. - Improve test cases to verify compatibility with new types. Signed-off-by: Max P. --- src/Errors/mod.ts | 1 + src/HttpKernel.ts | 26 ++++++---- src/Interfaces/IContext.ts | 42 +++++++++++---- src/Interfaces/IHandler.ts | 23 +++++--- src/Interfaces/IHttpKernel.ts | 49 +++++++++++------ src/Interfaces/IInternalRoute.ts | 10 ++-- src/Interfaces/IMiddleware.ts | 25 +++++---- src/Interfaces/IRouteBuilder.ts | 11 ++-- src/Interfaces/IRouteDefinition.ts | 5 +- src/Interfaces/mod.ts | 7 ++- src/RouteBuilder.ts | 33 +++++++----- src/Types/HttpMethod.ts | 52 +++++++++++++++++++ src/Types/Params.ts | 10 ++++ src/Types/Query.ts | 12 +++++ .../ResponseDecorator.ts | 0 src/Types/State.ts | 9 ++++ src/Types/mod.ts | 7 +++ src/Types/registerRoute.ts | 16 ++++++ src/{Utils.ts => Utils/createRouteMatcher.ts} | 2 +- src/Utils/mod.ts | 2 + src/__tests__/RouteBuilder.test.ts | 18 +++++-- src/mod.ts | 2 +- 22 files changed, 277 insertions(+), 85 deletions(-) create mode 100644 src/Errors/mod.ts create mode 100644 src/Types/HttpMethod.ts create mode 100644 src/Types/Params.ts create mode 100644 src/Types/Query.ts rename src/{Interfaces => Types}/ResponseDecorator.ts (100%) create mode 100644 src/Types/State.ts create mode 100644 src/Types/mod.ts create mode 100644 src/Types/registerRoute.ts rename src/{Utils.ts => Utils/createRouteMatcher.ts} (96%) create mode 100644 src/Utils/mod.ts diff --git a/src/Errors/mod.ts b/src/Errors/mod.ts new file mode 100644 index 0000000..8fac76f --- /dev/null +++ b/src/Errors/mod.ts @@ -0,0 +1 @@ +export { InvalidHttpMethodError } from './InvalidHttpMethodError.ts'; diff --git a/src/HttpKernel.ts b/src/HttpKernel.ts index fe66dbc..ed00acc 100644 --- a/src/HttpKernel.ts +++ b/src/HttpKernel.ts @@ -7,9 +7,10 @@ import { IRouteBuilder, IRouteBuilderFactory, IRouteDefinition, - ResponseDecorator, } from './Interfaces/mod.ts'; import { RouteBuilder } from './RouteBuilder.ts'; +import { ResponseDecorator } from './Types/mod.ts'; +import { parseQuery } from './Utils/mod.ts'; /** * The central HTTP kernel responsible for managing route definitions, @@ -18,7 +19,8 @@ import { RouteBuilder } from './RouteBuilder.ts'; * This class supports a fluent API for route registration and allows the injection * of custom response decorators and route builder factories for maximum flexibility and testability. */ -export class HttpKernel implements IHttpKernel { +export class HttpKernel + implements IHttpKernel { /** * The list of internally registered routes, each with method, matcher, middleware, and handler. */ @@ -35,12 +37,16 @@ export class HttpKernel implements IHttpKernel { private readonly decorateResponse: ResponseDecorator = (res) => res, private readonly routeBuilderFactory: IRouteBuilderFactory = RouteBuilder, - ) {} + ) { + this.handle = this.handle.bind(this); + } /** * @inheritdoc */ - public route(definition: IRouteDefinition): IRouteBuilder { + public route<_TContext extends IContext = TContext>( + definition: IRouteDefinition, + ): IRouteBuilder { return new this.routeBuilderFactory( this.registerRoute.bind(this), definition, @@ -49,8 +55,9 @@ export class HttpKernel implements IHttpKernel { /** * @inheritdoc - */ - public handle = async (request: Request): Promise => { + */ public async handle<_TContext extends IContext = TContext>( + request: Request, + ): Promise { const url = new URL(request.url); const method = request.method.toUpperCase(); @@ -58,11 +65,12 @@ export class HttpKernel implements IHttpKernel { if (route.method !== method) continue; const match = route.matcher(url, request); if (match) { - const ctx: IContext = { + const ctx: _TContext = { req: request, params: match.params, + query: parseQuery(url.searchParams), state: {}, - }; + } as _TContext; return await this.executePipeline( ctx, route.middlewares, @@ -72,7 +80,7 @@ export class HttpKernel implements IHttpKernel { } return new Response('Not Found', { status: 404 }); - }; + } /** * Registers a finalized route by pushing it into the internal route list. diff --git a/src/Interfaces/IContext.ts b/src/Interfaces/IContext.ts index ae874ba..3b63d33 100644 --- a/src/Interfaces/IContext.ts +++ b/src/Interfaces/IContext.ts @@ -1,11 +1,22 @@ +import { Params, Query, State } from '../Types/mod.ts'; + /** - * Represents the per-request context passed through the middleware pipeline and to the final handler. + * Represents the complete context for a single HTTP request, + * passed through the middleware pipeline and to the final route handler. * - * This context object encapsulates the original HTTP request, - * the path parameters extracted from the matched route, - * and a mutable state object for sharing information across middlewares and handlers. + * This context object encapsulates all relevant runtime data for a request, + * including the original request, path parameters, query parameters, + * and a shared, mutable application state. + * + * @template TState Structured per-request state shared across middlewares and handlers. + * @template TParams Parsed URL path parameters, typically derived from route templates. + * @template TQuery Parsed query string parameters, preserving multi-value semantics. */ -export interface IContext { +export interface IContext< + TState extends State = State, + TParams extends Params = Params, + TQuery extends Query = Query, +> { /** * The original HTTP request object as received by Deno. * Contains all standard fields like headers, method, body, etc. @@ -18,14 +29,25 @@ export interface IContext { * * These parameters are considered read-only and are set by the router. */ - params: Record; + params: TParams; /** - * A shared, mutable object used to pass arbitrary data between middlewares and handlers. + * Query parameters extracted from the request URL's search string. * - * Use this field to attach validated user info, auth state, logging context, etc. + * Values may occur multiple times (e.g., `?tag=ts&tag=deno`), and are therefore + * represented as either a string or an array of strings, depending on occurrence. * - * Each key should be well-named to avoid collisions across layers. + * Use this field to access filters, flags, pagination info, or similar modifiers. */ - state: Record; + query: TQuery; + + /** + * A typed, mutable object used to pass structured data between middlewares and handlers. + * + * This object is ideal for sharing validated input, user identity, trace information, + * or other contextual state throughout the request lifecycle. + * + * Type-safe access to fields is ensured by the generic `TState` type. + */ + state: TState; } diff --git a/src/Interfaces/IHandler.ts b/src/Interfaces/IHandler.ts index 4231fa0..d9e255d 100644 --- a/src/Interfaces/IHandler.ts +++ b/src/Interfaces/IHandler.ts @@ -1,16 +1,23 @@ import { IContext } from './IContext.ts'; /** - * Represents a final request handler responsible for generating a response. + * Represents a final request handler responsible for producing an HTTP response. * - * The handler is the last step in the middleware pipeline and must return - * a valid HTTP `Response`. It has access to all data injected into the - * request context, including path parameters and any state added by middleware. + * The handler is the terminal stage of the middleware pipeline and is responsible + * for processing the incoming request and generating the final `Response`. + * + * It receives the fully-typed request context, which includes the original request, + * parsed route parameters, query parameters, and any shared state populated by prior middleware. + * + * @template TContext The specific context type for this handler, including typed `state`, `params`, and `query`. */ -export interface IHandler { +export interface IHandler { /** - * @param ctx - The complete request context, including parameters and middleware state. - * @returns A promise resolving to an HTTP `Response`. + * 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: IContext): Promise; + (ctx: TContext): Promise; } diff --git a/src/Interfaces/IHttpKernel.ts b/src/Interfaces/IHttpKernel.ts index 4bc3b77..fb26fdd 100644 --- a/src/Interfaces/IHttpKernel.ts +++ b/src/Interfaces/IHttpKernel.ts @@ -1,33 +1,50 @@ +import { IContext } from './IContext.ts'; import { IRouteBuilder } from './IRouteBuilder.ts'; import { IRouteDefinition } from './IRouteDefinition.ts'; /** - * Defines the core interface for the HTTP kernel, responsible for route registration, - * middleware orchestration, and request dispatching. + * Defines the core interface for an HTTP kernel instance, responsible for + * registering routes, orchestrating middleware pipelines, and dispatching + * incoming HTTP requests to the appropriate handler. + * + * The kernel operates on a generic `IContext` type, which encapsulates the + * request, typed state, route parameters, and query parameters for each request. + * + * @template TContext The default context type for all registered routes and handlers. */ -export interface IHttpKernel { +export interface IHttpKernel { /** - * Registers a new route with a static path pattern or a dynamic matcher. + * Registers a new route using a static path or custom matcher. * - * This method accepts both conventional route definitions (with path templates) - * and advanced matcher-based routes for flexible URL structures. + * Returns a route builder that allows chaining middleware and assigning a final handler. + * This method is context-generic, allowing temporary overrides for specific routes + * if their context differs from the kernel-wide default. * - * Returns a route builder that allows chaining middleware and assigning a handler. + * @template _TContext Optional context override for the specific route being registered. + * Defaults to the kernel's generic `TContext`. * - * @param definition - A static or dynamic route definition, including the HTTP method - * and either a path pattern or custom matcher function. - * @returns A builder interface to attach middleware and define the handler. + * @param definition - A route definition containing the HTTP method and either a path + * pattern (e.g., `/users/:id`) or a custom matcher function. + * @returns A fluent builder interface for attaching middleware and setting the handler. */ - route(definition: IRouteDefinition): IRouteBuilder; + route<_TContext extends IContext = TContext>( + definition: IRouteDefinition, + ): IRouteBuilder; // IRouteBuilder<_TContext> /** - * Handles an incoming HTTP request by matching it against registered routes, - * executing any associated middleware in order, and invoking the final route handler. + * Handles an incoming HTTP request by matching it to a route, executing its middleware, + * and invoking the final handler. Automatically populates route parameters (`ctx.params`), + * query parameters (`ctx.query`), and initializes an empty mutable state (`ctx.state`). * - * This method serves as the main entry point to integrate with `Deno.serve`. + * This method is typically passed directly to `Deno.serve()` as the request handler. * - * @param request - The incoming HTTP request object. + * @template _TContext Optional override for the context type of the current request. + * Useful for testing or simulated requests. Defaults to the kernel’s `TContext`. + * + * @param request - The incoming HTTP request to dispatch. * @returns A promise resolving to the final HTTP response. */ - handle(request: Request): Promise; + handle<_TContext extends IContext = TContext>( + request: Request, + ): Promise; } diff --git a/src/Interfaces/IInternalRoute.ts b/src/Interfaces/IInternalRoute.ts index b8bc2ba..1c91acf 100644 --- a/src/Interfaces/IInternalRoute.ts +++ b/src/Interfaces/IInternalRoute.ts @@ -1,5 +1,7 @@ +import { HttpMethod } from '../Types/mod.ts'; import { IHandler } from './IHandler.ts'; import { IMiddleware } from './IMiddleware.ts'; +import { IContext } from './mod.ts'; /** * Represents an internally registered route within the HttpKernel. @@ -7,12 +9,12 @@ import { IMiddleware } from './IMiddleware.ts'; * Contains all data required to match an incoming request and dispatch it * through the associated middleware chain and final handler. */ -export interface IInternalRoute { +export interface IInternalRoute { /** * The HTTP method (e.g. 'GET', 'POST') that this route responds to. * The method should always be in uppercase. */ - method: string; + method: HttpMethod; /** * A matcher function used to determine whether this route matches a given request. @@ -33,10 +35,10 @@ export interface IInternalRoute { /** * An ordered list of middleware functions to be executed before the handler. */ - middlewares: IMiddleware[]; + middlewares: IMiddleware[]; /** * The final handler that generates the HTTP response after all middleware has run. */ - handler: IHandler; + handler: IHandler; } diff --git a/src/Interfaces/IMiddleware.ts b/src/Interfaces/IMiddleware.ts index c8ee818..c91bbbe 100644 --- a/src/Interfaces/IMiddleware.ts +++ b/src/Interfaces/IMiddleware.ts @@ -3,18 +3,23 @@ import { IContext } from './IContext.ts'; /** * Represents a middleware function in the HTTP request pipeline. * - * Middleware can perform tasks such as logging, authentication, validation, - * or response transformation. It receives the current request context and - * a `next()` function to delegate control to the next middleware or final handler. + * Middleware is a core mechanism to intercept, observe, or modify the request lifecycle. + * It can be used for tasks such as logging, authentication, input validation, + * metrics collection, or response transformation. * - * To stop the request pipeline, a middleware can return a `Response` directly - * without calling `next()`. + * Each middleware receives a fully-typed request context and a `next()` function + * to invoke the next stage of the pipeline. Middleware may choose to short-circuit + * the pipeline by returning a `Response` early. + * + * @template TContext The specific context type for this middleware, including state, params, and query information. */ -export interface IMiddleware { +export interface IMiddleware { /** - * @param ctx - The request context, containing the request, path parameters, and shared state. - * @param next - A function that continues the middleware pipeline. Returns the final `Response`. - * @returns A promise resolving to an HTTP `Response`. + * 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: IContext, next: () => Promise): Promise; + (ctx: TContext, next: () => Promise): Promise; } diff --git a/src/Interfaces/IRouteBuilder.ts b/src/Interfaces/IRouteBuilder.ts index c1917d0..48e090b 100644 --- a/src/Interfaces/IRouteBuilder.ts +++ b/src/Interfaces/IRouteBuilder.ts @@ -2,6 +2,7 @@ import { IHandler } from './IHandler.ts'; import { IInternalRoute } from './IInternalRoute.ts'; import { IMiddleware } from './IMiddleware.ts'; import { IRouteDefinition } from './IRouteDefinition.ts'; +import { IContext } from './mod.ts'; export interface IRouteBuilderFactory { new ( @@ -15,7 +16,7 @@ export interface IRouteBuilderFactory { * Provides a fluent API to build a single route configuration by chaining * middleware and setting the final request handler. */ -export interface IRouteBuilder { +export interface IRouteBuilder { /** * Adds a middleware to the current route. * Middleware will be executed in the order of registration. @@ -23,7 +24,9 @@ export interface IRouteBuilder { * @param mw - A middleware function. * @returns The route builder for further chaining. */ - middleware(mw: IMiddleware): IRouteBuilder; + middleware<_TContext extends IContext = TContext>( + mw: IMiddleware<_TContext>, + ): IRouteBuilder<_TContext>; /** * Sets the final request handler for the route. @@ -31,5 +34,7 @@ export interface IRouteBuilder { * * @param handler - The function to execute when this route is matched. */ - handle(handler: IHandler): void; + handle<_TContext extends IContext = TContext>( + handler: IHandler<_TContext>, + ): void; } diff --git a/src/Interfaces/IRouteDefinition.ts b/src/Interfaces/IRouteDefinition.ts index 39dee12..955d204 100644 --- a/src/Interfaces/IRouteDefinition.ts +++ b/src/Interfaces/IRouteDefinition.ts @@ -1,3 +1,4 @@ +import { HttpMethod } from '../Types/mod.ts'; import { IRouteMatcher } from './IRouteMatcher.ts'; /** @@ -10,7 +11,7 @@ export interface IStaticRouteDefinition { /** * The HTTP method this route should match (e.g. "GET", "POST"). */ - method: string; + method: HttpMethod; /** * A static path pattern for the route, which may include named parameters @@ -29,7 +30,7 @@ export interface IDynamicRouteDefinition { /** * The HTTP method this route should match (e.g. "GET", "POST"). */ - method: string; + method: HttpMethod; /** * A custom matcher function that receives the parsed URL and raw request. diff --git a/src/Interfaces/mod.ts b/src/Interfaces/mod.ts index 517ee2a..aeebb7e 100644 --- a/src/Interfaces/mod.ts +++ b/src/Interfaces/mod.ts @@ -1,13 +1,12 @@ export type { IContext } from './IContext.ts'; -export type { IMiddleware } from './IMiddleware.ts'; export type { IHandler } from './IHandler.ts'; export type { IHttpKernel } from './IHttpKernel.ts'; +export type { IInternalRoute } from './IInternalRoute.ts'; +export type { IMiddleware } from './IMiddleware.ts'; export type { IRouteBuilder, IRouteBuilderFactory } from './IRouteBuilder.ts'; export type { IDynamicRouteDefinition, IRouteDefinition, IStaticRouteDefinition, } from './IRouteDefinition.ts'; -export type { IInternalRoute } from './IInternalRoute.ts'; -export type { IRouteMatcher } from './IRouteMatcher.ts'; -export type { ResponseDecorator } from './ResponseDecorator.ts'; +export type { IRouteMatcher, IRouteMatcherFactory } from './IRouteMatcher.ts'; diff --git a/src/RouteBuilder.ts b/src/RouteBuilder.ts index 8be19c1..7764c85 100644 --- a/src/RouteBuilder.ts +++ b/src/RouteBuilder.ts @@ -1,12 +1,13 @@ import { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts'; import { + IContext, IHandler, - IInternalRoute, IMiddleware, IRouteBuilder, IRouteDefinition, } from './Interfaces/mod.ts'; -import { createRouteMatcher } from './Utils.ts'; +import { RegisterRoute } from './Types/mod.ts'; +import { createRouteMatcher } from './Utils/createRouteMatcher.ts'; /** * Provides a fluent builder interface for defining a single route, @@ -14,7 +15,8 @@ import { createRouteMatcher } from './Utils.ts'; * * This builder is stateless and immutable; each chained call returns a new instance. */ -export class RouteBuilder implements IRouteBuilder { +export class RouteBuilder + implements IRouteBuilder { /** * Constructs a new instance of the route builder. * @@ -23,9 +25,9 @@ export class RouteBuilder implements IRouteBuilder { * @param mws - The list of middleware functions collected so far (default: empty). */ constructor( - private readonly registerRoute: (route: IInternalRoute) => void, + private readonly registerRoute: RegisterRoute, private readonly def: IRouteDefinition, - private readonly mws: IMiddleware[] = [], + private readonly mws: IMiddleware[] = [], private readonly matcherFactory: IRouteMatcherFactory = createRouteMatcher, ) {} @@ -39,11 +41,14 @@ export class RouteBuilder implements IRouteBuilder { * @param mw - A middleware function to be executed before the handler. * @returns A new `RouteBuilder` instance for continued chaining. */ - middleware(mw: IMiddleware): IRouteBuilder { - return new RouteBuilder(this.registerRoute, this.def, [ - ...this.mws, - mw, - ]); + middleware<_TContext extends IContext = TContext>( + mw: IMiddleware<_TContext>, + ): IRouteBuilder<_TContext> { + return new RouteBuilder<_TContext>( + this.registerRoute as unknown as RegisterRoute<_TContext>, + this.def, + [...this.mws as unknown as IMiddleware<_TContext>[], mw], + ); } /** @@ -54,13 +59,15 @@ export class RouteBuilder implements IRouteBuilder { * * @param handler - The final request handler for this route. */ - handle(handler: IHandler): void { + handle<_TContext extends IContext = TContext>( + handler: IHandler<_TContext>, + ): void { const matcher = this.matcherFactory(this.def); this.registerRoute({ - method: this.def.method.toUpperCase(), + method: this.def.method, matcher, middlewares: this.mws, - handler, + handler: handler as unknown as IHandler, }); } } diff --git a/src/Types/HttpMethod.ts b/src/Types/HttpMethod.ts new file mode 100644 index 0000000..d076523 --- /dev/null +++ b/src/Types/HttpMethod.ts @@ -0,0 +1,52 @@ +/** + * A constant list of all supported HTTP methods according to RFC 7231 and RFC 5789. + * + * This array serves both as a runtime value list for validation + * and as the basis for deriving the `HttpMethod` union type. + * + * Note: The list is immutable and should not be modified at runtime. + */ +export const validHttpMethods = [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'HEAD', + 'OPTIONS', +] as const; + +/** + * A union type representing all valid HTTP methods recognized by this application. + * + * This type is derived directly from the `validHttpMethods` constant, + * ensuring type safety and consistency between type system and runtime checks. + * + * Example: + * ```ts + * const method: HttpMethod = 'POST'; // ✅ valid + * const method: HttpMethod = 'FOO'; // ❌ Type error + * ``` + */ +export type HttpMethod = typeof validHttpMethods[number]; + +/** + * Type guard to verify whether a given value is a valid HTTP method. + * + * This function checks both the type and content of the value + * and is suitable for runtime validation of inputs (e.g., from HTTP requests). + * + * Example: + * ```ts + * if (isHttpMethod(input)) { + * // input is now typed as HttpMethod + * } + * ``` + * + * @param value - The value to test (typically a string from a request). + * @returns `true` if the value is a valid `HttpMethod`, otherwise `false`. + */ +export function isHttpMethod(value: unknown): value is HttpMethod { + return typeof value === 'string' && + validHttpMethods.includes(value as HttpMethod); +} diff --git a/src/Types/Params.ts b/src/Types/Params.ts new file mode 100644 index 0000000..045e056 --- /dev/null +++ b/src/Types/Params.ts @@ -0,0 +1,10 @@ +/** + * Represents route parameters parsed from dynamic segments in the URL path. + * + * This type is typically derived from route definitions with placeholders, + * such as `/users/:id`, which would yield `{ id: "123" }`. + * + * All values are strings and should be considered read-only, as they are + * extracted by the router and should not be modified by application code. + */ +export type Params = Record; diff --git a/src/Types/Query.ts b/src/Types/Query.ts new file mode 100644 index 0000000..b9453fe --- /dev/null +++ b/src/Types/Query.ts @@ -0,0 +1,12 @@ +/** + * Represents the parsed query parameters from the request URL. + * + * Query parameters originate from the URL search string (e.g. `?filter=active&tags=ts&tags=deno`) + * and may contain single or multiple values per key. + * + * All values are expressed as strings or arrays of strings, depending on how often + * the key occurs. This structure preserves the raw semantics of the query. + * + * For normalized single-value access, prefer custom DTOs or wrapper utilities. + */ +export type Query = Record; diff --git a/src/Interfaces/ResponseDecorator.ts b/src/Types/ResponseDecorator.ts similarity index 100% rename from src/Interfaces/ResponseDecorator.ts rename to src/Types/ResponseDecorator.ts diff --git a/src/Types/State.ts b/src/Types/State.ts new file mode 100644 index 0000000..b512d9e --- /dev/null +++ b/src/Types/State.ts @@ -0,0 +1,9 @@ +/** + * Represents the per-request state object shared across the middleware pipeline. + * + * This type defines the base structure for custom state definitions, + * which can be extended with concrete fields like user data, request metadata, etc. + * + * Custom `TState` types must extend this base to ensure compatibility. + */ +export type State = Record; diff --git a/src/Types/mod.ts b/src/Types/mod.ts new file mode 100644 index 0000000..fd3b1de --- /dev/null +++ b/src/Types/mod.ts @@ -0,0 +1,7 @@ +export { isHttpMethod, validHttpMethods } from './HttpMethod.ts'; +export type { HttpMethod } from './HttpMethod.ts'; +export type { Params } from './Params.ts'; +export type { Query } from './Query.ts'; +export type { ResponseDecorator } from './ResponseDecorator.ts'; +export type { State } from './State.ts'; +export type { RegisterRoute } from './registerRoute.ts'; diff --git a/src/Types/registerRoute.ts b/src/Types/registerRoute.ts new file mode 100644 index 0000000..21af1b3 --- /dev/null +++ b/src/Types/registerRoute.ts @@ -0,0 +1,16 @@ +import { IContext } from '../Interfaces/IContext.ts'; +import { IInternalRoute } from '../Interfaces/mod.ts'; + +/** + * A type alias for the internal route registration function used by the `HttpKernel`. + * + * This function accepts a fully constructed internal route, including method, matcher, + * middleware chain, and final handler, and registers it for dispatching. + * + * Typically passed into `RouteBuilder` instances to enable fluent API chaining. + * + * @template TContext The context type associated with the route being registered. + */ +export type RegisterRoute = ( + route: IInternalRoute, +) => void; diff --git a/src/Utils.ts b/src/Utils/createRouteMatcher.ts similarity index 96% rename from src/Utils.ts rename to src/Utils/createRouteMatcher.ts index 73b5d94..3e696d8 100644 --- a/src/Utils.ts +++ b/src/Utils/createRouteMatcher.ts @@ -1,4 +1,4 @@ -import { IRouteDefinition, IRouteMatcher } from './Interfaces/mod.ts'; +import { IRouteDefinition, IRouteMatcher } from '../Interfaces/mod.ts'; /** * Creates a matcher function from a given route definition. diff --git a/src/Utils/mod.ts b/src/Utils/mod.ts new file mode 100644 index 0000000..3910113 --- /dev/null +++ b/src/Utils/mod.ts @@ -0,0 +1,2 @@ +export { createRouteMatcher } from './createRouteMatcher.ts'; +export { parseQuery } from './parseQuery.ts'; diff --git a/src/__tests__/RouteBuilder.test.ts b/src/__tests__/RouteBuilder.test.ts index 83891ab..7aab899 100644 --- a/src/__tests__/RouteBuilder.test.ts +++ b/src/__tests__/RouteBuilder.test.ts @@ -15,7 +15,7 @@ import { RouteBuilder } from '../mod.ts'; // Dummy objects const dummyHandler: IHandler = async () => new Response('ok'); const dummyMiddleware: IMiddleware = async (_, next) => await next(); -const dummyDef: IRouteDefinition = { method: 'get', path: '/hello' }; +const dummyDef: IRouteDefinition = { method: 'GET', path: '/hello' }; const dummyMatcher = () => ({ params: {} }); Deno.test('middleware: single middleware is registered correctly', () => { @@ -57,7 +57,7 @@ Deno.test('middleware: preserves order of middleware', () => { Deno.test('handle: uppercases method', () => { let result: IInternalRoute | null = null as IInternalRoute | null; - new RouteBuilder((r) => result = r, { method: 'post', path: '/x' }) + new RouteBuilder((r) => result = r, { method: 'POST', path: '/x' }) .handle(dummyHandler); assertEquals(result?.method, 'POST'); @@ -74,8 +74,18 @@ Deno.test('handle: works with no middleware', async () => { const request = new Request('http://localhost'); - const res1 = await route?.handler({ req: request, params: {}, state: {} }); - const res2 = await dummyHandler({ req: request, params: {}, state: {} }); + const res1 = await route?.handler({ + req: request, + params: {}, + state: {}, + query: {}, + }); + const res2 = await dummyHandler({ + req: request, + params: {}, + state: {}, + query: {}, + }); assertEquals(res1?.status, res2?.status); assertEquals(await res1?.text(), await res2?.text()); diff --git a/src/mod.ts b/src/mod.ts index 1ba8006..0754746 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,4 +1,4 @@ // deno-coverage-ignore-file export { HttpKernel } from './HttpKernel.ts'; export { RouteBuilder } from './RouteBuilder.ts'; -export { createRouteMatcher } from './Utils.ts'; +export { createRouteMatcher } from './Utils/createRouteMatcher.ts';