import type { Request, Response } from "express"; type MayBePromise = T | Promise; export type Empty = Record; export type DecoratorResultTrue = { result: true; type?: string; info?: string; }; export type DecoratorResultFalse = { result: false; type: string; info: string; } export type DecoratorResult = DecoratorResultTrue | DecoratorResultFalse; export type DecoratorStack = DecoratorResult[]; export interface ProcDecorator { (inCtx: In & Partial, req: Request, res: Response) : MayBePromise<[DecoratorResultTrue, Out] | [DecoratorResultFalse, In & Partial]>; } export interface PostProcDecorator { (ctx: T, preResult: DecoratorResult, req: Request, res: Response, isValidRoute: boolean): MayBePromise<[DecoratorResult, T]>; } export interface ProcDecoratorRunner { (inCtx: In, req: Request, res: Response): MayBePromise<[DecoratorStack, Out]>; } export interface PostProcDecoratorRunner { (ctx: T, preResult: DecoratorStack, req: Request, res: Response, isValidRoute: boolean): MayBePromise<[DecoratorStack, T]>; } // eslint-disable-next-line @typescript-eslint/no-explicit-any type PlainDecorator = ProcDecorator; // eslint-disable-next-line @typescript-eslint/no-explicit-any type PlainPostDecorator = PostProcDecorator; export type ProcDecoratorGenerator = ProcDecorator; export type ProcDecoratorPrePostGenerator = [ProcDecorator, PostProcDecorator]; export type ProcDecoratorChain = readonly ((() => PlainDecorator) | (() => [PlainDecorator, PlainPostDecorator]))[]; export type ResolveChain = T extends undefined ? Empty : T extends ProcDecoratorChain ? Resolve> : never; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ParseInType = T extends ProcDecorator ? object extends A ? A : never : never; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ParseOutType = T extends ProcDecorator ? B : never; export const EmptyProcDecorator: readonly [ProcDecoratorRunner, PostProcDecoratorRunner] = [ async (ctx) => [[], ctx], async (ctx, stack) => [stack, ctx] ]; export function declProcDecorators(...decorators: T) { type OutType = Resolve>; const preDecorator: PlainDecorator[] = []; const postDecorator: (PlainPostDecorator | undefined)[] = []; if (decorators) { for (const procGen of decorators) { const proc = procGen(); if (Array.isArray(proc)) { preDecorator.push(proc[0]); postDecorator.push(proc[1]); } else { preDecorator.push(proc); postDecorator.push(undefined); } } } const packedDecorators: readonly [ProcDecoratorRunner, PostProcDecoratorRunner] = [ async (ctx, req, res) => { let rctx = ctx as unknown as OutType; const decoratorStack: DecoratorStack = []; if (!preDecorator.length) { return [decoratorStack, rctx]; } for (const [idx, proc] of preDecorator.entries()) { try { const [stackResult, newCtx] = await proc(rctx, req, res); decoratorStack.push(stackResult); if (stackResult.result) { rctx = newCtx; continue; } return [decoratorStack, newCtx]; } catch (e) { while (decoratorStack.length > idx) { decoratorStack.pop(); } decoratorStack.push({ result: false, type: 'PreThrow', info: `internal error: ${e}`, }); return [decoratorStack, rctx]; } } return [decoratorStack, rctx]; }, async (ctx, stack, req, res) => { if (!postDecorator.length) { return [stack, ctx]; } let isValidRoute = stack.length === postDecorator.length && stack.every(v => v.result); for (let idx = stack.length - 1; idx >= 0; idx--) { const preStackResult = stack[idx] as DecoratorResult; if (!preStackResult.result) { continue; } const proc = postDecorator[idx]; if (!proc) { if (isValidRoute) { stack.pop(); } continue; } try { const [postStackResult, nextCtx] = await proc(ctx, preStackResult, req, res, isValidRoute); if (postStackResult.result) { if (isValidRoute) { stack.pop(); } ctx = nextCtx; continue; } isValidRoute = false; stack[idx] = postStackResult; ctx = nextCtx; } catch (e) { isValidRoute = false; stack[idx] = { result: false, type: 'PostThrow', info: `internal error: ${e}`, }; } } return [stack, ctx]; }, ] as const; return packedDecorators; } type Compose2 = B extends C ? ProcDecorator : never; type PD1 = () => ProcDecorator; // eslint-disable-next-line @typescript-eslint/no-explicit-any type PD2 = () => [ProcDecorator, PostProcDecorator]; export type PackChain = T extends readonly [] ? ProcDecorator : T extends readonly [PD1] ? ProcDecorator : T extends readonly [PD2] ? ProcDecorator : T extends readonly [PD1, PD1, ... infer R] ? PackChain<[() => Compose2, ...R]> : T extends readonly [PD2, PD1, ... infer R] ? PackChain<[() => Compose2, ...R]> : T extends readonly [PD1, PD2, ... infer R] ? PackChain<[() => Compose2, ...R]> : T extends readonly [PD2, PD2, ... infer R] ? PackChain<[() => Compose2, ...R]> : never; type Resolve = T extends ProcDecorator ? Empty extends A ? B : never : never;