@akala/core
Version:
143 lines (131 loc) • 6.07 kB
text/typescript
import { type MiddlewarePromise, NotHandled, type SpecialNextParam, type MiddlewareAsync } from '../middlewares/shared.js';
import { MiddlewareCompositeAsync } from '../middlewares/composite-async.js';
import { type Routable, type RouteBuilderArguments } from "./route.js";
import { match, parse, type UriTemplate } from '../uri-template/index.js';
/**
* Represents an asynchronous route handler for URI template matching in middleware chains.
*
* This middleware class processes routes using URI templates and manages asynchronous middleware execution.
*
* @template T - The context type containing the routable request and parameters.
* @template TSpecialNextParam - The type for special next parameters (defaults to {@link SpecialNextParam})
*/
export class MiddlewareRouteAsync<T extends [Routable, ...unknown[]], TSpecialNextParam extends SpecialNextParam = SpecialNextParam> extends MiddlewareCompositeAsync<T, TSpecialNextParam>
{
/**
* Creates an instance of MiddlewareRouteAsync with a route path or URI template.
* @template T - The context type containing the routable request and parameters.
* @template TSpecialNextParam - The type for special next parameters (defaults to SpecialNextParam)
* @param {string | UriTemplate} route - The route path or URI template defining the route.
*/
constructor(route: string | UriTemplate)
{
super(route.toString());
this.routePath = Array.isArray(route) ? route : parse(route);
}
/**
* Parsed URI template used for route matching
*
* @type {UriTemplate}
*/
routePath!: UriTemplate;
/**
* Creates a new route configuration chain with optional parameters.
*
* @function route
* @param {...RouteBuilderArguments} args - Route configuration parameters (method, path, etc.)
* @returns {MiddlewareRouteAsync<T, TSpecialNextParam>} Newly created route instance
*/
route(...args: RouteBuilderArguments): MiddlewareRouteAsync<T, TSpecialNextParam>
{
return new MiddlewareRouteAsync<T, TSpecialNextParam>(...args);
}
/**
* Optional predicate to determine route applicability
*
* A function that checks if the route should handle the current request
*
* @type {(x: T[0]) => boolean}
*/
isApplicable?: (x: T[0]) => boolean;
/**
* Executes the route's middleware chain asynchronously.
*
* @param {...T} context - Execution context containing the routable request and parameters
* @returns {MiddlewarePromise<TSpecialNextParam>} Resolution promise indicating completion
*/
async handle(...context: T): MiddlewarePromise<TSpecialNextParam>
{
const req = context[0];
const isMatch = match(req.path, this.routePath);
if (isMatch && (!this.isApplicable || this.isApplicable(req)))
{
const oldPath = req.path;
const c = oldPath[oldPath.length - isMatch.remainder.length];
if (c && c !== '/')
return NotHandled;
req.path = isMatch.remainder || '/';
const oldParams = req.params;
req.params = isMatch.variables;
try
{
return await super.handle(...context);
}
finally
{
req.params = oldParams;
req.path = oldPath;
}
}
return NotHandled;
}
/**
* Adds middleware to the route chain, supporting route definitions and middleware functions.
*
* @function useMiddleware
* @param {string | UriTemplate | MiddlewareAsync<T, TSpecialNextParam>} routeOrMiddleware -
* Either a route definition (string/UriTemplate) or a middleware function
* @param {...MiddlewareAsync<T, TSpecialNextParam>} middlewares - Additional middleware functions
* @returns {this} Current middleware instance for method chaining
*/
public useMiddleware(...middlewares: MiddlewareAsync<T, TSpecialNextParam>[]): this;
public useMiddleware(
routeOrMiddleware: string | UriTemplate | MiddlewareAsync<T, TSpecialNextParam>,
...middlewares: MiddlewareAsync<T, TSpecialNextParam>[]
): this;
public useMiddleware(routeOrMiddleware: string | UriTemplate | MiddlewareAsync<T, TSpecialNextParam>, ...middlewares: MiddlewareAsync<T, TSpecialNextParam>[]): this
{
if (typeof routeOrMiddleware === 'string' || Array.isArray(routeOrMiddleware))
{
const routed = new MiddlewareRouteAsync<T, TSpecialNextParam>(routeOrMiddleware);
routed.useMiddleware(...middlewares);
super.useMiddleware(routed);
}
else
super.useMiddleware(routeOrMiddleware, ...middlewares);
return this;
}
/**
* Registers route handlers or middleware functions.
*
* @function use
* @param {(string | UriTemplate | ((...args: T) => Promise<unknown>))} routeOrHandler -
* Route definition (string/UriTemplate) or handler function
* @param {...((...args: T) => Promise<unknown>)} handlers - Additional handler functions
* @returns {this} Current middleware instance for method chaining
*/
public use(routeOrHandler: string | UriTemplate, ...handlers: ((...args: T) => Promise<unknown>)[]): this;
public use(...handlers: ((...args: T) => Promise<unknown>)[]): this;
public use(routeOrHandler: string | UriTemplate | ((...args: T) => Promise<unknown>), ...handlers: ((...args: T) => Promise<unknown>)[]): this
{
if (typeof routeOrHandler == 'undefined')
throw new Error('At least 1 route or middleware needs to be provided.');
if (typeof routeOrHandler === 'string' || Array.isArray(routeOrHandler))
{
const routed = new MiddlewareRouteAsync<T, TSpecialNextParam>(routeOrHandler);
routed.use(...handlers);
return super.useMiddleware(routed);
}
return super.use(routeOrHandler, ...handlers);
}
}