honion
Version:
131 lines (119 loc) • 3.42 kB
text/typescript
import { Context } from "../context";
import { Middleware, MiddlewareConstructor } from "./middleware";
const MIDDLEWARE_HOOK_BAG = "@hal-wang/honion/middlewareHooksBag";
export type MdHook<
TC extends Context,
TM extends Middleware<TC>,
U extends TM | MiddlewareConstructor<TC> | Error = any
> = (
ctx: TC,
md: U,
error?: Error
) =>
| void
| Promise<void | boolean>
| TM
| undefined
| Promise<TM | undefined>
| boolean
| Promise<boolean>;
export enum HookType {
BeforeInvoke,
AfterInvoke,
BeforeNext,
Constructor,
Error,
}
export interface HookItem<TC extends Context, TM extends Middleware<TC>> {
hook: MdHook<TC, TM>;
type: HookType;
}
export class HookMiddleware<
TC extends Context,
TM extends Middleware<TC>
> extends Middleware<TC> {
constructor(mh: MdHook<TC, TM>, type: HookType) {
super();
this.#mh = mh;
this.#type = type;
}
#mh: MdHook<TC, TM>;
#type: HookType;
async invoke(): Promise<void> {
const hooks = this.ctx.get<HookItem<TC, TM>[]>(MIDDLEWARE_HOOK_BAG) ?? [];
hooks.push({ hook: this.#mh, type: this.#type });
this.ctx.set(MIDDLEWARE_HOOK_BAG, hooks);
await this.next();
}
}
export async function execHooks(
ctx: Context,
middleware: Middleware,
type: HookType.Error,
error: Error
): Promise<boolean>;
export async function execHooks(
ctx: Context,
middleware: MiddlewareConstructor,
type: HookType.Constructor
): Promise<Middleware>;
export async function execHooks(
ctx: Context,
middleware: Middleware,
type: HookType.BeforeInvoke | HookType.BeforeNext
): Promise<boolean | void>;
export async function execHooks(
ctx: Context,
middleware: Middleware,
type: HookType.AfterInvoke
): Promise<void>;
export async function execHooks(
ctx: Context,
middleware: Middleware | MiddlewareConstructor,
type: HookType,
error?: Error
): Promise<Middleware | void | boolean> {
if (type == HookType.Constructor) {
return await execConstructorHooks(ctx, middleware as MiddlewareConstructor);
} else if (type == HookType.Error) {
return await execErrorHooks(ctx, middleware as Middleware, error as Error);
}
const hooks =
ctx.get<HookItem<Context, Middleware>[]>(MIDDLEWARE_HOOK_BAG) ?? [];
for (const hookItem of hooks.filter((h) => h.type == type)) {
const hookResult = await hookItem.hook(ctx, middleware);
if (typeof hookResult == "boolean" && !hookResult) {
return false;
}
}
}
async function execErrorHooks(
ctx: Context,
middleware: Middleware,
error: Error
): Promise<boolean> {
const hooks =
ctx.get<HookItem<Context, Middleware>[]>(MIDDLEWARE_HOOK_BAG) ?? [];
let result = false;
for (const hookItem of hooks.filter((h) => h.type == HookType.Error)) {
result = (await hookItem.hook(ctx, middleware, error)) as boolean;
if (result) break;
}
return result;
}
async function execConstructorHooks(
ctx: Context,
middleware: MiddlewareConstructor
): Promise<Middleware | undefined> {
const hooks =
ctx.get<HookItem<Context, Middleware>[]>(MIDDLEWARE_HOOK_BAG) ?? [];
let result: Middleware | undefined;
for (const hookItem of hooks.filter((h) => h.type == HookType.Constructor)) {
if (!(middleware instanceof Middleware)) {
result = (await hookItem.hook(ctx, middleware)) as Middleware;
if (result) break;
}
}
if (!result) result = new middleware();
return result;
}