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. <Mail@MPassarello.de>
This commit is contained in:
2025-05-07 12:29:49 +02:00
parent 82a6877485
commit a236fa7c97
22 changed files with 277 additions and 85 deletions

1
src/Errors/mod.ts Normal file
View File

@@ -0,0 +1 @@
export { InvalidHttpMethodError } from './InvalidHttpMethodError.ts';

View File

@@ -7,9 +7,10 @@ import {
IRouteBuilder, IRouteBuilder,
IRouteBuilderFactory, IRouteBuilderFactory,
IRouteDefinition, IRouteDefinition,
ResponseDecorator,
} from './Interfaces/mod.ts'; } from './Interfaces/mod.ts';
import { RouteBuilder } from './RouteBuilder.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, * 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 * 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. * of custom response decorators and route builder factories for maximum flexibility and testability.
*/ */
export class HttpKernel implements IHttpKernel { export class HttpKernel<TContext extends IContext = IContext>
implements IHttpKernel<TContext> {
/** /**
* The list of internally registered routes, each with method, matcher, middleware, and handler. * 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 decorateResponse: ResponseDecorator = (res) => res,
private readonly routeBuilderFactory: IRouteBuilderFactory = private readonly routeBuilderFactory: IRouteBuilderFactory =
RouteBuilder, RouteBuilder,
) {} ) {
this.handle = this.handle.bind(this);
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
public route(definition: IRouteDefinition): IRouteBuilder { public route<_TContext extends IContext = TContext>(
definition: IRouteDefinition,
): IRouteBuilder {
return new this.routeBuilderFactory( return new this.routeBuilderFactory(
this.registerRoute.bind(this), this.registerRoute.bind(this),
definition, definition,
@@ -49,8 +55,9 @@ export class HttpKernel implements IHttpKernel {
/** /**
* @inheritdoc * @inheritdoc
*/ */ public async handle<_TContext extends IContext = TContext>(
public handle = async (request: Request): Promise<Response> => { request: Request,
): Promise<Response> {
const url = new URL(request.url); const url = new URL(request.url);
const method = request.method.toUpperCase(); const method = request.method.toUpperCase();
@@ -58,11 +65,12 @@ export class HttpKernel implements IHttpKernel {
if (route.method !== method) continue; if (route.method !== method) continue;
const match = route.matcher(url, request); const match = route.matcher(url, request);
if (match) { if (match) {
const ctx: IContext = { const ctx: _TContext = {
req: request, req: request,
params: match.params, params: match.params,
query: parseQuery(url.searchParams),
state: {}, state: {},
}; } as _TContext;
return await this.executePipeline( return await this.executePipeline(
ctx, ctx,
route.middlewares, route.middlewares,
@@ -72,7 +80,7 @@ export class HttpKernel implements IHttpKernel {
} }
return new Response('Not Found', { status: 404 }); return new Response('Not Found', { status: 404 });
}; }
/** /**
* Registers a finalized route by pushing it into the internal route list. * Registers a finalized route by pushing it into the internal route list.

View File

@@ -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, * This context object encapsulates all relevant runtime data for a request,
* the path parameters extracted from the matched route, * including the original request, path parameters, query parameters,
* and a mutable state object for sharing information across middlewares and handlers. * 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. * The original HTTP request object as received by Deno.
* Contains all standard fields like headers, method, body, etc. * 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. * These parameters are considered read-only and are set by the router.
*/ */
params: Record<string, string>; 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<string, unknown>; 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;
} }

View File

@@ -1,16 +1,23 @@
import { IContext } from './IContext.ts'; 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 * The handler is the terminal stage of the middleware pipeline and is responsible
* a valid HTTP `Response`. It has access to all data injected into the * for processing the incoming request and generating the final `Response`.
* request context, including path parameters and any state added by middleware. *
* 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<TContext extends IContext = IContext> {
/** /**
* @param ctx - The complete request context, including parameters and middleware state. * Handles the request and generates a response.
* @returns A promise resolving to an HTTP `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<Response>; (ctx: TContext): Promise<Response>;
} }

View File

@@ -1,33 +1,50 @@
import { IContext } from './IContext.ts';
import { IRouteBuilder } from './IRouteBuilder.ts'; import { IRouteBuilder } from './IRouteBuilder.ts';
import { IRouteDefinition } from './IRouteDefinition.ts'; import { IRouteDefinition } from './IRouteDefinition.ts';
/** /**
* Defines the core interface for the HTTP kernel, responsible for route registration, * Defines the core interface for an HTTP kernel instance, responsible for
* middleware orchestration, and request dispatching. * 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<TContext extends IContext = IContext> {
/** /**
* 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) * Returns a route builder that allows chaining middleware and assigning a final handler.
* and advanced matcher-based routes for flexible URL structures. * 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 * @param definition - A route definition containing the HTTP method and either a path
* and either a path pattern or custom matcher function. * pattern (e.g., `/users/:id`) or a custom matcher function.
* @returns A builder interface to attach middleware and define the handler. * @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, * Handles an incoming HTTP request by matching it to a route, executing its middleware,
* executing any associated middleware in order, and invoking the final route handler. * 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. * @returns A promise resolving to the final HTTP response.
*/ */
handle(request: Request): Promise<Response>; handle<_TContext extends IContext = TContext>(
request: Request,
): Promise<Response>;
} }

View File

@@ -1,5 +1,7 @@
import { HttpMethod } from '../Types/mod.ts';
import { IHandler } from './IHandler.ts'; import { IHandler } from './IHandler.ts';
import { IMiddleware } from './IMiddleware.ts'; import { IMiddleware } from './IMiddleware.ts';
import { IContext } from './mod.ts';
/** /**
* Represents an internally registered route within the HttpKernel. * 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 * Contains all data required to match an incoming request and dispatch it
* through the associated middleware chain and final handler. * through the associated middleware chain and final handler.
*/ */
export interface IInternalRoute { export interface IInternalRoute<TContext extends IContext = IContext> {
/** /**
* The HTTP method (e.g. 'GET', 'POST') that this route responds to. * The HTTP method (e.g. 'GET', 'POST') that this route responds to.
* The method should always be in uppercase. * The method should always be in uppercase.
*/ */
method: string; method: HttpMethod;
/** /**
* A matcher function used to determine whether this route matches a given request. * 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. * An ordered list of middleware functions to be executed before the handler.
*/ */
middlewares: IMiddleware[]; middlewares: IMiddleware<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; handler: IHandler<TContext>;
} }

View File

@@ -3,18 +3,23 @@ import { IContext } from './IContext.ts';
/** /**
* Represents a middleware function in the HTTP request pipeline. * Represents a middleware function in the HTTP request pipeline.
* *
* Middleware can perform tasks such as logging, authentication, validation, * Middleware is a core mechanism to intercept, observe, or modify the request lifecycle.
* or response transformation. It receives the current request context and * It can be used for tasks such as logging, authentication, input validation,
* a `next()` function to delegate control to the next middleware or final handler. * metrics collection, or response transformation.
* *
* To stop the request pipeline, a middleware can return a `Response` directly * Each middleware receives a fully-typed request context and a `next()` function
* without calling `next()`. * 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<TContext extends IContext = IContext> {
/** /**
* @param ctx - The request context, containing the request, path parameters, and shared state. * Handles the request processing at this middleware stage.
* @param next - A function that continues the middleware pipeline. Returns the final `Response`. *
* @returns A promise resolving to an HTTP `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.
*/ */
(ctx: IContext, next: () => Promise<Response>): Promise<Response>; (ctx: TContext, next: () => Promise<Response>): Promise<Response>;
} }

View File

@@ -2,6 +2,7 @@ import { IHandler } from './IHandler.ts';
import { IInternalRoute } from './IInternalRoute.ts'; import { IInternalRoute } from './IInternalRoute.ts';
import { IMiddleware } from './IMiddleware.ts'; import { IMiddleware } from './IMiddleware.ts';
import { IRouteDefinition } from './IRouteDefinition.ts'; import { IRouteDefinition } from './IRouteDefinition.ts';
import { IContext } from './mod.ts';
export interface IRouteBuilderFactory { export interface IRouteBuilderFactory {
new ( new (
@@ -15,7 +16,7 @@ export interface IRouteBuilderFactory {
* Provides a fluent API to build a single route configuration by chaining * Provides a fluent API to build a single route configuration by chaining
* middleware and setting the final request handler. * middleware and setting the final request handler.
*/ */
export interface IRouteBuilder { export interface IRouteBuilder<TContext extends IContext = IContext> {
/** /**
* Adds a middleware to the current route. * Adds a middleware to the current route.
* Middleware will be executed in the order of registration. * Middleware will be executed in the order of registration.
@@ -23,7 +24,9 @@ export interface IRouteBuilder {
* @param mw - A middleware function. * @param mw - A middleware function.
* @returns The route builder for further chaining. * @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. * 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. * @param handler - The function to execute when this route is matched.
*/ */
handle(handler: IHandler): void; handle<_TContext extends IContext = TContext>(
handler: IHandler<_TContext>,
): void;
} }

View File

@@ -1,3 +1,4 @@
import { HttpMethod } from '../Types/mod.ts';
import { IRouteMatcher } from './IRouteMatcher.ts'; import { IRouteMatcher } from './IRouteMatcher.ts';
/** /**
@@ -10,7 +11,7 @@ export interface IStaticRouteDefinition {
/** /**
* The HTTP method this route should match (e.g. "GET", "POST"). * 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 * 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"). * 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. * A custom matcher function that receives the parsed URL and raw request.

View File

@@ -1,13 +1,12 @@
export type { IContext } from './IContext.ts'; export type { IContext } from './IContext.ts';
export type { IMiddleware } from './IMiddleware.ts';
export type { IHandler } from './IHandler.ts'; export type { IHandler } from './IHandler.ts';
export type { IHttpKernel } from './IHttpKernel.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 { IRouteBuilder, IRouteBuilderFactory } from './IRouteBuilder.ts';
export type { export type {
IDynamicRouteDefinition, IDynamicRouteDefinition,
IRouteDefinition, IRouteDefinition,
IStaticRouteDefinition, IStaticRouteDefinition,
} from './IRouteDefinition.ts'; } from './IRouteDefinition.ts';
export type { IInternalRoute } from './IInternalRoute.ts'; export type { IRouteMatcher, IRouteMatcherFactory } from './IRouteMatcher.ts';
export type { IRouteMatcher } from './IRouteMatcher.ts';
export type { ResponseDecorator } from './ResponseDecorator.ts';

View File

@@ -1,12 +1,13 @@
import { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts'; import { IRouteMatcherFactory } from './Interfaces/IRouteMatcher.ts';
import { import {
IContext,
IHandler, IHandler,
IInternalRoute,
IMiddleware, IMiddleware,
IRouteBuilder, IRouteBuilder,
IRouteDefinition, IRouteDefinition,
} from './Interfaces/mod.ts'; } 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, * 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. * This builder is stateless and immutable; each chained call returns a new instance.
*/ */
export class RouteBuilder implements IRouteBuilder { export class RouteBuilder<TContext extends IContext = IContext>
implements IRouteBuilder<TContext> {
/** /**
* Constructs a new instance of the route builder. * 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). * @param mws - The list of middleware functions collected so far (default: empty).
*/ */
constructor( constructor(
private readonly registerRoute: (route: IInternalRoute) => void, private readonly registerRoute: RegisterRoute<TContext>,
private readonly def: IRouteDefinition, private readonly def: IRouteDefinition,
private readonly mws: IMiddleware[] = [], private readonly mws: IMiddleware<TContext>[] = [],
private readonly matcherFactory: IRouteMatcherFactory = private readonly matcherFactory: IRouteMatcherFactory =
createRouteMatcher, createRouteMatcher,
) {} ) {}
@@ -39,11 +41,14 @@ export class RouteBuilder implements IRouteBuilder {
* @param mw - A middleware function to be executed before the handler. * @param mw - A middleware function to be executed before the handler.
* @returns A new `RouteBuilder` instance for continued chaining. * @returns A new `RouteBuilder` instance for continued chaining.
*/ */
middleware(mw: IMiddleware): IRouteBuilder { middleware<_TContext extends IContext = TContext>(
return new RouteBuilder(this.registerRoute, this.def, [ mw: IMiddleware<_TContext>,
...this.mws, ): IRouteBuilder<_TContext> {
mw, 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. * @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); const matcher = this.matcherFactory(this.def);
this.registerRoute({ this.registerRoute({
method: this.def.method.toUpperCase(), method: this.def.method,
matcher, matcher,
middlewares: this.mws, middlewares: this.mws,
handler, handler: handler as unknown as IHandler<TContext>,
}); });
} }
} }

52
src/Types/HttpMethod.ts Normal file
View File

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

10
src/Types/Params.ts Normal file
View File

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

12
src/Types/Query.ts Normal file
View File

@@ -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<string, string | string[]>;

9
src/Types/State.ts Normal file
View File

@@ -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<string, unknown>;

7
src/Types/mod.ts Normal file
View File

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

View File

@@ -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<TContext extends IContext = IContext> = (
route: IInternalRoute<TContext>,
) => void;

View File

@@ -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. * Creates a matcher function from a given route definition.

2
src/Utils/mod.ts Normal file
View File

@@ -0,0 +1,2 @@
export { createRouteMatcher } from './createRouteMatcher.ts';
export { parseQuery } from './parseQuery.ts';

View File

@@ -15,7 +15,7 @@ import { RouteBuilder } from '../mod.ts';
// Dummy objects // Dummy objects
const dummyHandler: IHandler = async () => new Response('ok'); const dummyHandler: IHandler = async () => new Response('ok');
const dummyMiddleware: IMiddleware = async (_, next) => await next(); const dummyMiddleware: IMiddleware = async (_, next) => await next();
const dummyDef: IRouteDefinition = { method: 'get', path: '/hello' }; const dummyDef: IRouteDefinition = { method: 'GET', path: '/hello' };
const dummyMatcher = () => ({ params: {} }); const dummyMatcher = () => ({ params: {} });
Deno.test('middleware: single middleware is registered correctly', () => { Deno.test('middleware: single middleware is registered correctly', () => {
@@ -57,7 +57,7 @@ Deno.test('middleware: preserves order of middleware', () => {
Deno.test('handle: uppercases method', () => { Deno.test('handle: uppercases method', () => {
let result: IInternalRoute | null = null as IInternalRoute | null; 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); .handle(dummyHandler);
assertEquals(result?.method, 'POST'); assertEquals(result?.method, 'POST');
@@ -74,8 +74,18 @@ Deno.test('handle: works with no middleware', async () => {
const request = new Request('http://localhost'); const request = new Request('http://localhost');
const res1 = await route?.handler({ req: request, params: {}, state: {} }); const res1 = await route?.handler({
const res2 = await dummyHandler({ req: request, params: {}, state: {} }); req: request,
params: {},
state: {},
query: {},
});
const res2 = await dummyHandler({
req: request,
params: {},
state: {},
query: {},
});
assertEquals(res1?.status, res2?.status); assertEquals(res1?.status, res2?.status);
assertEquals(await res1?.text(), await res2?.text()); assertEquals(await res1?.text(), await res2?.text());

View File

@@ -1,4 +1,4 @@
// deno-coverage-ignore-file // deno-coverage-ignore-file
export { HttpKernel } from './HttpKernel.ts'; export { HttpKernel } from './HttpKernel.ts';
export { RouteBuilder } from './RouteBuilder.ts'; export { RouteBuilder } from './RouteBuilder.ts';
export { createRouteMatcher } from './Utils.ts'; export { createRouteMatcher } from './Utils/createRouteMatcher.ts';