183 lines
7.0 KiB
TypeScript
183 lines
7.0 KiB
TypeScript
import type { Request, Response } from "express";
|
|
|
|
type MayBePromise<T> = T | Promise<T>;
|
|
|
|
export type Empty = Record<string, never>;
|
|
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<Out extends object, In = Empty> {
|
|
(inCtx: In & Partial<Out>, req: Request, res: Response)
|
|
: MayBePromise<[DecoratorResultTrue, Out] | [DecoratorResultFalse, In & Partial<Out>]>;
|
|
}
|
|
|
|
export interface PostProcDecorator<T extends object> {
|
|
(ctx: T, preResult: DecoratorResult, req: Request, res: Response, isValidRoute: boolean): MayBePromise<[DecoratorResult, T]>;
|
|
}
|
|
|
|
export interface ProcDecoratorRunner<Out extends object, In> {
|
|
(inCtx: In, req: Request, res: Response): MayBePromise<[DecoratorStack, Out]>;
|
|
}
|
|
|
|
export interface PostProcDecoratorRunner<T extends object> {
|
|
(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<any, any>;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
type PlainPostDecorator = PostProcDecorator<any>;
|
|
|
|
|
|
export type ProcDecoratorGenerator<Out extends object, In = Empty> = ProcDecorator<Out & In, In>;
|
|
export type ProcDecoratorPrePostGenerator<Out extends object, In> = [ProcDecorator<Out & In, In>, PostProcDecorator<Out & In>];
|
|
|
|
export type ProcDecoratorChain = readonly ((() => PlainDecorator) | (() => [PlainDecorator, PlainPostDecorator]))[];
|
|
|
|
export type ResolveChain<T> = T extends undefined ? Empty : T extends ProcDecoratorChain ? Resolve<PackChain<T>> : never;
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export type ParseInType<T> = T extends ProcDecorator<any, infer A> ? object extends A ? A : never : never;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export type ParseOutType<T> = T extends ProcDecorator<infer B, object> ? B : never;
|
|
|
|
export const EmptyProcDecorator: readonly [ProcDecoratorRunner<Empty, Empty>, PostProcDecoratorRunner<Empty>] = [
|
|
async (ctx) => [[], ctx], async (ctx, stack) => [stack, ctx]
|
|
];
|
|
|
|
export function declProcDecorators<T extends ProcDecoratorChain>(...decorators: T) {
|
|
type OutType = Resolve<PackChain<T>>;
|
|
|
|
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<OutType, Empty>, PostProcDecoratorRunner<OutType>] = [
|
|
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<D extends object, C, B, A = Empty> = B extends C ? ProcDecorator<D & B, A> : never;
|
|
|
|
type PD1<B extends object, A extends object> = () => ProcDecorator<B, A>;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
type PD2<B extends object, A extends object> = () => [ProcDecorator<B, A>, PostProcDecorator<any>];
|
|
|
|
export type PackChain<T> =
|
|
T extends readonly [] ? ProcDecorator<Empty, Empty> :
|
|
T extends readonly [PD1<infer B, infer A>] ? ProcDecorator<B, A> :
|
|
T extends readonly [PD2<infer B, infer A>] ? ProcDecorator<B, A> :
|
|
T extends readonly [PD1<infer B, infer A>, PD1<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
|
T extends readonly [PD2<infer B, infer A>, PD1<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
|
T extends readonly [PD1<infer B, infer A>, PD2<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
|
T extends readonly [PD2<infer B, infer A>, PD2<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
|
never;
|
|
|
|
type Resolve<T> = T extends ProcDecorator<infer B, infer A> ? Empty extends A ? B : never : never; |