@strpc ready
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { AnyAPI, Callable, DefAPINamespace, DeleteAPI, GetAPI, HeadAPI, HttpMethod, InferError, InferQuery, InferResponse, InvalidResponse, PatchAPI, PostAPI, PutAPI, recoveryMethod } from '@strpc/def/types';
|
||||
import type { Empty, PostProcDecoratorRunner, ProcDecoratorRunner } from './proc_decorator.js';
|
||||
import type { ZodType } from 'zod';
|
||||
|
||||
export type APINamespace = {
|
||||
[key: string]: APINamespace
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| iAPI_GET<any, any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| iAPI_POST<any, any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| iAPI_PUT<any, any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| iAPI_DELETE<any, any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| iAPI_PATCH<any, any>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| iAPI_HEAD<any, any>
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AnyAPIExecuter = APIExecuter<any, any>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnyAnyAPI = AnyAPI<any, any, any>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
interface APIExecuter<T extends AnyAnyAPI, CTX extends object> {
|
||||
(query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response): Promise<InferResponse<T> | InferError<T> | true>;
|
||||
httpMethod: HttpMethod;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
argValidator?: ZodType<InferQuery<T>>;
|
||||
preDecorator: ProcDecoratorRunner<CTX, Empty>;
|
||||
postDecorator: PostProcDecoratorRunner<CTX>;
|
||||
}
|
||||
|
||||
export interface iAPI_GET<T extends AnyAnyAPI, CTX extends object> extends APIExecuter<T, CTX> {
|
||||
httpMethod: 'get';
|
||||
}
|
||||
|
||||
export interface iAPI_POST<T extends AnyAnyAPI, CTX extends object> extends APIExecuter<T, CTX> {
|
||||
httpMethod: 'post';
|
||||
}
|
||||
|
||||
export interface iAPI_PUT<T extends AnyAnyAPI, CTX extends object> extends APIExecuter<T, CTX> {
|
||||
httpMethod: 'put';
|
||||
}
|
||||
|
||||
export interface iAPI_DELETE<T extends AnyAnyAPI, CTX extends object> extends APIExecuter<T, CTX> {
|
||||
httpMethod: 'delete';
|
||||
}
|
||||
|
||||
export interface iAPI_PATCH<T extends AnyAnyAPI, CTX extends object> extends APIExecuter<T, CTX> {
|
||||
httpMethod: 'patch';
|
||||
}
|
||||
|
||||
export interface iAPI_HEAD<T extends AnyAnyAPI, CTX extends object> extends APIExecuter<T, CTX> {
|
||||
httpMethod: 'head';
|
||||
}
|
||||
|
||||
|
||||
/** API의 반환형. ValidResponse | InvalidResponse | true */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type APIReturnType<T extends AnyAnyAPI> = Promise<InferResponse<T> | InferError<T> | true>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function generateAPI<T extends AnyAnyAPI, CTX extends object>(httpMethod: HttpMethod,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
argValidator: ZodType<InferQuery<T>> | undefined,
|
||||
preDecorator: ProcDecoratorRunner<CTX, Empty>,
|
||||
postDecorator: PostProcDecoratorRunner<CTX>,
|
||||
callback: (query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response) => Promise<InferResponse<T> | InferError<T> | true>,
|
||||
): APIExecuter<T, CTX> {
|
||||
return Object.assign(
|
||||
callback,
|
||||
{
|
||||
httpMethod,
|
||||
argValidator,
|
||||
preDecorator,
|
||||
postDecorator,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function GET<T extends GetAPI<any, any, any>>(argValidator?: ZodType<InferQuery<T>>) {
|
||||
return <CTX extends object>([preDecorator, postDecorator]: readonly [ProcDecoratorRunner<CTX, Empty>, PostProcDecoratorRunner<CTX>]) => {
|
||||
return (callback: (query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response) => Promise<InferResponse<T> | InferError<T> | true>) => {
|
||||
return generateAPI<T, CTX>(
|
||||
'get',
|
||||
argValidator,
|
||||
preDecorator,
|
||||
postDecorator,
|
||||
callback,
|
||||
) as iAPI_GET<T, CTX>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function POST<T extends PostAPI<any, any, any>>(argValidator?: ZodType<InferQuery<T>>) {
|
||||
return <CTX extends object>([preDecorator, postDecorator]: readonly [ProcDecoratorRunner<CTX, Empty>, PostProcDecoratorRunner<CTX>]) => {
|
||||
return (callback: (query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response) => Promise<InferResponse<T> | InferError<T> | true>) => {
|
||||
return generateAPI<T, CTX>(
|
||||
'post',
|
||||
argValidator,
|
||||
preDecorator,
|
||||
postDecorator,
|
||||
callback,
|
||||
) as iAPI_POST<T, CTX>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function PUT<T extends PutAPI<any, any, any>>(argValidator?: ZodType<InferQuery<T>>) {
|
||||
return <CTX extends object>([preDecorator, postDecorator]: readonly [ProcDecoratorRunner<CTX, Empty>, PostProcDecoratorRunner<CTX>]) => {
|
||||
return (callback: (query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response) => Promise<InferResponse<T> | InferError<T> | true>) => {
|
||||
return generateAPI<T, CTX>(
|
||||
'put',
|
||||
argValidator,
|
||||
preDecorator,
|
||||
postDecorator,
|
||||
callback,
|
||||
) as iAPI_PUT<T, CTX>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function DELETE<T extends DeleteAPI<any, any, any>, Z extends ZodType<InferQuery<T>>>(argValidator?: Z) {
|
||||
return <CTX extends object>([preDecorator, postDecorator]: readonly [ProcDecoratorRunner<CTX, Empty>, PostProcDecoratorRunner<CTX>]) => {
|
||||
return (callback: (query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response) => Promise<InferResponse<T> | InferError<T> | true>) => {
|
||||
return generateAPI<T, CTX>(
|
||||
'delete',
|
||||
argValidator,
|
||||
preDecorator,
|
||||
postDecorator,
|
||||
callback,
|
||||
) as iAPI_DELETE<T, CTX>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function PATCH<T extends PatchAPI<any, any, any>>(argValidator?: ZodType<InferQuery<T>>) {
|
||||
return <CTX extends object>([preDecorator, postDecorator]: readonly [ProcDecoratorRunner<CTX, Empty>, PostProcDecoratorRunner<CTX>]) => {
|
||||
return (callback: (query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response) => Promise<InferResponse<T> | InferError<T> | true>) => {
|
||||
return generateAPI<T, CTX>(
|
||||
'patch',
|
||||
argValidator,
|
||||
preDecorator,
|
||||
postDecorator,
|
||||
callback,
|
||||
) as iAPI_PATCH<T, CTX>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function HEAD<T extends HeadAPI<any, any, any>>(argValidator?: ZodType<InferQuery<T>>) {
|
||||
return <CTX extends object>([preDecorator, postDecorator]: readonly [ProcDecoratorRunner<CTX, Empty>, PostProcDecoratorRunner<CTX>]) => {
|
||||
return (callback: (query: InferQuery<T>, ctx: CTX, expressReq: Request, expressRes: Response) => Promise<InferResponse<T> | InferError<T> | true>) => {
|
||||
return generateAPI<T, CTX>(
|
||||
'head',
|
||||
argValidator,
|
||||
preDecorator,
|
||||
postDecorator,
|
||||
callback,
|
||||
) as iAPI_HEAD<T, CTX>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function raiseError(reason: string, recovery?: recoveryMethod): InvalidResponse {
|
||||
if (recovery) {
|
||||
return {
|
||||
result: false,
|
||||
reason,
|
||||
recovery,
|
||||
};
|
||||
}
|
||||
return {
|
||||
result: false,
|
||||
reason,
|
||||
};
|
||||
}
|
||||
|
||||
export type APIServerType<T extends Callable> =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
T extends GetAPI<infer Q, infer R, infer E> ? iAPI_GET<GetAPI<Q, R, E>, any> :
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
T extends PostAPI<infer Q, infer R, infer E> ? iAPI_POST<PostAPI<Q, R, E>, any> :
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
T extends PutAPI<infer Q, infer R, infer E> ? iAPI_PUT<PutAPI<Q, R, E>, any> :
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
T extends DeleteAPI<infer Q, infer R, infer E> ? iAPI_DELETE<DeleteAPI<Q, R, E>, any> :
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
T extends PatchAPI<infer Q, infer R, infer E> ? iAPI_PATCH<PatchAPI<Q, R, E>, any> :
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
T extends HeadAPI<infer Q, infer R, infer E> ? iAPI_HEAD<HeadAPI<Q, R, E>, any> :
|
||||
|
||||
never;
|
||||
export type APINamespaceType<T extends DefAPINamespace> = {
|
||||
[K in keyof T]:
|
||||
T[K] extends Callable ? APIServerType<T[K]> :
|
||||
T[K] extends DefAPINamespace ? APINamespaceType<T[K]> :
|
||||
never;
|
||||
};
|
||||
@@ -0,0 +1,161 @@
|
||||
import { Router, type Request, type Response } from 'express';
|
||||
import type { AnyAPIExecuter, APINamespace } from './defs.js';
|
||||
import type { RawArgType } from '@strpc/def';
|
||||
import type { SafeParseReturnType, ZodType, z } from 'zod';
|
||||
import { flatten } from 'lodash-es';
|
||||
|
||||
const PRINT_API_CALL = true;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async function parseParam<Q extends RawArgType>(req: Request, argValidator?: ZodType<Q>): Promise<SafeParseReturnType<Q, Q>> {
|
||||
if (!argValidator) {
|
||||
return {
|
||||
success: true,
|
||||
data: req.query as Q
|
||||
}
|
||||
}
|
||||
|
||||
const query = req.query;
|
||||
return await argValidator.safeParseAsync(query);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async function parseBody<Q extends RawArgType>(req: Request, argValidator?: ZodType<Q>): Promise<SafeParseReturnType<Q, Q>> {
|
||||
if (!argValidator) {
|
||||
return {
|
||||
success: true,
|
||||
data: req.body as Q
|
||||
}
|
||||
}
|
||||
|
||||
const query = req.body;
|
||||
return await argValidator.safeParseAsync(query);
|
||||
}
|
||||
|
||||
async function apiRun<Q extends RawArgType>(query: Q, req: Request, res: Response, api: AnyAPIExecuter): Promise<void> {
|
||||
const [preResult, ctx] = await api.preDecorator({}, req, res);
|
||||
if (!preResult.every((v) => v.result)) {
|
||||
const lastErr = preResult.pop() as typeof preResult[0];
|
||||
|
||||
const [postResult,] = await api.postDecorator(ctx, preResult, req, res, false);
|
||||
const postErrors = postResult.filter((obj) => !obj.result);
|
||||
if (postErrors.length) {
|
||||
//회수조차 불가능?
|
||||
if (PRINT_API_CALL) {
|
||||
console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, false, 'preDecorator', lastErr.type, lastErr.info]));
|
||||
}
|
||||
res.json({
|
||||
result: false,
|
||||
path: req.path,
|
||||
reason: lastErr.info,
|
||||
recovery: lastErr.recovery,
|
||||
detail: {
|
||||
type: 'decorator',
|
||||
preDecorator: [lastErr.type, lastErr.info],
|
||||
postDecorator: flatten(postErrors.map((obj) => [obj.type, obj.info])),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (PRINT_API_CALL) {
|
||||
console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, false, 'preDecorator', lastErr.type, lastErr.info]));
|
||||
}
|
||||
res.json({
|
||||
result: false,
|
||||
path: req.path,
|
||||
reason: lastErr.info,
|
||||
recovery: lastErr.recovery,
|
||||
detail: {
|
||||
type: 'decorator',
|
||||
preDecorator: [lastErr.type, lastErr.info],
|
||||
},
|
||||
})
|
||||
return;
|
||||
}
|
||||
const result = await api(query, ctx, req, res);
|
||||
const [postResult,] = await api.postDecorator(ctx, preResult, req, res, true);
|
||||
const postErrors = postResult.filter((obj) => !obj.result);
|
||||
if (postErrors.length) {
|
||||
const lastErr = postErrors.pop() as typeof postErrors[0];
|
||||
if (PRINT_API_CALL) {
|
||||
console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, false, 'postDecorator', lastErr.type, lastErr.info]));
|
||||
}
|
||||
if (result === true) {
|
||||
//NOTE: 이미 api에서 response를 보낸 특이 케이스.
|
||||
return;
|
||||
}
|
||||
res.json({
|
||||
result: false,
|
||||
path: req.path,
|
||||
reason: lastErr?.info,
|
||||
recovery: lastErr?.recovery,
|
||||
detail: {
|
||||
type: 'decorator',
|
||||
preDecorator: flatten(postErrors.map((obj) => [obj.type, obj.info])),
|
||||
},
|
||||
originalResult: result,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (PRINT_API_CALL) {
|
||||
if(result === true){
|
||||
console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, true, 'passThrough']));
|
||||
}
|
||||
else{
|
||||
let tmp_result = true;
|
||||
let tmp_reason = undefined;
|
||||
if('result' in result){
|
||||
tmp_result = result.result;
|
||||
}
|
||||
if('reason' in result){
|
||||
tmp_reason = result.reason;
|
||||
}
|
||||
console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, tmp_result, tmp_reason]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (result !== true) {
|
||||
res.json(result);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAPISystem<N extends APINamespace, Q extends AnyAPIExecuter>(api: N | Q): Router {
|
||||
const router = Router();
|
||||
|
||||
if (typeof api === 'function') {
|
||||
throw 'root api cannot be function';
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(api)) {
|
||||
const rkey = `/${key}`;
|
||||
if (typeof value !== 'function') {
|
||||
router.use(rkey, buildAPISystem(value));
|
||||
|
||||
continue;
|
||||
}
|
||||
const executer = value;
|
||||
|
||||
if (!executer.httpMethod) {
|
||||
throw new Error('APIExecuter.reqType is not defined');
|
||||
}
|
||||
|
||||
const parser = executer.httpMethod === 'get' ? parseParam : parseBody;
|
||||
router[executer.httpMethod](rkey, async (req, res) => {
|
||||
const queryResult = await parser(req, executer.argValidator);
|
||||
if (!queryResult.success) {
|
||||
res.json({
|
||||
result: false,
|
||||
reason: `invalid parameter: ${queryResult.error.message}`,
|
||||
error: queryResult.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await apiRun(queryResult.data, req, res, executer);
|
||||
});
|
||||
}
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './defs.js';
|
||||
export * as generator from './generator.js';
|
||||
export * as proc_decorator from './proc_decorator.js';
|
||||
@@ -0,0 +1,187 @@
|
||||
import type { recoveryMethod } from "@strpc/def";
|
||||
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;
|
||||
recovery?: recoveryMethod;
|
||||
};
|
||||
export type DecoratorResultFalse = {
|
||||
result: false;
|
||||
type: string;
|
||||
info: string;
|
||||
recovery?: recoveryMethod;
|
||||
}
|
||||
export type DecoratorResult = DecoratorResultTrue | DecoratorResultFalse;
|
||||
export type DecoratorStack = DecoratorResult[];
|
||||
|
||||
export interface ProcDecorator<Out extends object, In extends object = Empty> {
|
||||
(inCtx: In & Partial<Out>, req: Request, res: Response)
|
||||
: MayBePromise<[DecoratorResultTrue, Out]>
|
||||
| MayBePromise<[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 extends object> {
|
||||
(inCtx: In & Partial<Out>, 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<B extends object, A extends object> = ProcDecorator<B & A, A>;
|
||||
export type ProcDecoratorPrePostGenerator<B extends object, A extends object> = [ProcDecorator<B & A, A>, PostProcDecorator<B & A>];
|
||||
|
||||
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 = ParseOutType<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) {
|
||||
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 object, A extends object, D extends object, C> = 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<B, A, D, C>, ...R]> :
|
||||
T extends readonly [PD2<infer B, infer A>, PD1<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<B, A, D, C>, ...R]> :
|
||||
T extends readonly [PD1<infer B, infer A>, PD2<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<B, A, D, C>, ...R]> :
|
||||
T extends readonly [PD2<infer B, infer A>, PD2<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<B, A, D, C>, ...R]> :
|
||||
never;
|
||||
|
||||
type Resolve<T> = T extends ProcDecorator<infer B, infer A> ? Empty extends A ? B : never : never;
|
||||
Reference in New Issue
Block a user