UNPKG

@akala/core

Version:
143 lines (131 loc) 6.07 kB
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); } }