UNPKG

@akala/core

Version:
180 lines 6.8 kB
import { MiddlewareCompositeAsync } from "./middlewares/composite-async.js"; import ErrorWithStatus, { HttpStatusCode } from "./errorWithStatus.js"; import { RouterAsync } from "./router/router-async.js"; /** * Handles URL routing and middleware processing for different protocol, host, and path components * @template T - Tuple type representing middleware context parameters [URL, ...unknown[], Partial<TResult>] * @template TResult - Result type for middleware processing (default: object) */ export class UrlHandler { noAssign; /** * Composite middleware stack for protocol-specific processing */ protocol; /** * Composite middleware stack for host-specific processing */ host; /** * Router for path-based routing */ router; /** * Creates a new URL handler instance */ constructor(noAssign = false) { this.noAssign = noAssign; this.protocol = new MiddlewareCompositeAsync('protocols'); this.host = new MiddlewareCompositeAsync('domains'); this.router = new RouterAsync('path'); } /** * warning ! the second parameter needs to be not null as we will assign properties to it. * Processes the URL through protocol, host, and path routing middleware * @param context - Middleware context parameters * @returns Promise resolving to the final TResult object */ /** * Processes the URL through protocol, host, and path routing middleware * @param context - Middleware context parameters * @returns Promise resolving to the final TResult object */ process(...context) { return this.handle(...context).then(v => { throw v; }, (result) => this.noAssign ? result : context[context.length - 1]); } /** * Adds a protocol handler middleware * @param protocol Protocol to handle (colon will be automatically stripped if present) * @param action Async handler function for protocol processing * @returns Registered protocol middleware instance */ useProtocol(protocol, action) { const handler = new UrlHandler.Protocol(protocol); this.protocol.useMiddleware(handler); return handler.use((...context) => action(...context).then(result => { if (!this.noAssign && typeof result !== 'undefined') if (context[context.length - 1]) return Object.assign(context[context.length - 1], result); else return context[context.length - 1] = result; else if (this.noAssign) return result; })); } /** * Adds a host handler middleware * @param host Hostname to match * @param action Async handler function for host processing * @returns Registered host middleware instance */ useHost(host, action) { const handler = new UrlHandler.Host(host); this.host.useMiddleware(handler); return handler.use((...context) => action(...context).then(result => { if (!this.noAssign && typeof result !== 'undefined') Object.assign(context[context.length - 1] || {}, result); else if (this.noAssign) return result; })); } /** * Handles the URL processing pipeline * @param context - Middleware context parameters * @returns Promise that resolves when handling fails or rejects with the final result */ async handle(...context) { let error = await this.protocol.handle(...context); while (error === 'loop') error = await this.handle(...context); if (error) return error; error = await this.host.handle(...context); if (error) return error; let params; error = await this.router.handle({ path: context[0].pathname, get params() { if (params) return params; if (context[0].search) return params; return params = Object.fromEntries(Array.from(context[0].searchParams.keys()).map(k => { const values = context[0].searchParams.getAll(k); if (values.length == 1) return [k, values[0]]; return [k, values]; })); } }, ...context); if (error) return error; return new ErrorWithStatus(HttpStatusCode.NotFound, `${context[0]} is not supported`); } } /** * Namespace containing Protocol and Host middleware classes */ (function (UrlHandler) { /** * Middleware for handling specific protocols * @template T - Middleware context type extending [URL, ...unknown[]] */ class Protocol extends MiddlewareCompositeAsync { protocol; /** * @param protocol - The protocol to handle (automatically strips trailing colon if present) */ constructor(protocol) { super(); this.protocol = protocol; if (protocol.endsWith(':')) this.protocol = protocol.substring(0, protocol.length - 1); } /** * Handles protocol matching and processing * @param context - Middleware context parameters * @returns Promise resolving to middleware error status or undefined */ async handle(...context) { if (context[0].protocol == this.protocol + ':') { return super.handle(...context); } else if (context[0].protocol.startsWith(this.protocol + '+')) { return super.handle(...context).then(error => error, () => { context[0].protocol = context[0].protocol.substring(this.protocol.length + 2); return 'loop'; }); } return; } } UrlHandler.Protocol = Protocol; /** * Middleware for handling specific hosts * @template T - Middleware context type extending [URL, ...unknown[]] */ class Host extends MiddlewareCompositeAsync { host; /** * @param host - The host name to match */ constructor(host) { super(); this.host = host; } /** * Handles host matching * @param context - Middleware context parameters * @returns Promise resolving to middleware error status or undefined */ handle(...context) { if (context[0].host === this.host) { return super.handle(...context); } return; } } UrlHandler.Host = Host; })(UrlHandler || (UrlHandler = {})); //# sourceMappingURL=url-handler.js.map