cobweb/src/middleware.ts

74 lines
1.7 KiB
TypeScript

/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: BSD-3-Clause
*/
import { DataStream } from "./http.ts";
import { Router } from "cobweb/routing";
export interface Context<Params = Record<string, string>> {
readonly request: Request;
readonly info: Deno.ServeHandlerInfo<Deno.NetAddr>;
readonly url: URL;
readonly method: string;
readonly params: Params;
readonly pattern: URLPatternResult;
readonly stream: DataStream;
readonly signal: AbortSignal;
readonly router: Router;
state: Map<string | symbol, unknown>;
}
export async function createContext<P = Record<string, string>>(
router: Router,
request: Request,
info: Deno.ServeHandlerInfo<Deno.NetAddr>,
pattern: URLPatternResult,
stream: DataStream,
): Promise<Context<P>> {
return {
router,
request,
info,
url: new URL(request.url),
method: request.method,
params: (pattern.pathname.groups || {}) as P,
pattern,
stream: stream,
signal: request.signal,
state: new Map(),
};
}
export interface Handler<P = Record<string, string>> {
(ctx: Context<P>): Promise<Response | void>;
}
export interface Middleware {
(ctx: Context, next: () => Promise<Response>): Promise<Response>;
}
export function compose(
middlewares: readonly Middleware[],
handler: Handler,
): Handler {
if (!middlewares.length) return handler;
return (ctx) => {
let index = -1;
async function dispatch(i: number): Promise<Response> {
if (i <= index) throw new Error("next() called multiple times");
index = i;
const fn = i < middlewares.length ? middlewares[i] : handler;
if (!fn) throw new Error("No handler found");
const result = await fn(ctx, () => dispatch(i + 1));
if (!result) throw new Error("Handler must return Response");
return result;
}
return dispatch(0);
};
}