@akala/core
Version:
180 lines • 6.8 kB
JavaScript
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