Цепочка промежуточного программного обеспечения TypeScript

#typescript

#typescript

Вопрос:

Я пытаюсь создать цепочку промежуточного программного обеспечения со статической проверкой типа. Он немного имитирует декораторов.

 // Should infer type Handler<InputContext, { a: string, b: boolean }>,
// but gets Handler<InputContext, unknown>
export const myHandler = withMiddleware(
  addToContextMW(c => ({ y: 10 })),
  addToResponseMW(r => ({ b: true })),
  async ({ x, y }): Promise<MyResponse> => {
    // y: number infers fine!

    return {
        a: `x = ${x   y}`
    };
  }
);
 

Игровая площадка TypeScript с полным примером

Полный код:

 // Context is just an object with input data
declare type Handler<C, R> = (
  context: C
) => Promise<R>;

// Gets a Handler and returns a new one
declare type Middleware<C, NC, NR, R> = (
  next: Handler<NC, NR>
) => Handler<C, R>;

declare interface InputContext {
  x: number;
}

// In my case the argument with which I'll be calling handlers
// is always the same (InputContext), so I've fixed the input type
declare function withMiddleware<Res>(
  handler: Handler<InputContext, Res>
): Handler<InputContext, Res>;
declare function withMiddleware<Req, Res, Z>(
  mw1: Middleware<InputContext, Req, Res, Z>,
  handler: Handler<Req, Res>
): Handler<InputContext, Z>;
declare function withMiddleware<B, Req, Res, Y, Z>(
  mw1: Middleware<InputContext, B, Y, Z>,
  mw2: Middleware<B, Req, Res, Y>,
  handler: Handler<Req, Res>
): Handler<InputContext, Z>;

// Basic MW, does nothing, just passing the data through
const testMiddleware = <C, R>(): Middleware<C, C, R, R> =>
  (next) => (context) => next(context);

interface MyResponse {
  a: string;
}

// Should inter type Handler<InputContext, MyResponse>, but gets Handler<InputContext, unknown>
export const myHandler1 = withMiddleware(
  testMiddleware(),
  testMiddleware(),
  async ({ x }): Promise<MyResponse> => {
    // x: number from InputContext infers fine!

    return {
        a: `x = ${x}`
    };
  }
);

declare type Merge<A, B> = Omit<A, keyof B> amp; B;

// More advanced MWs, change context and/or response types.
const addToContextMW = <C, FR, R>(factory: (context: C) => FR): Middleware<C, Merge<C, FR>, R, R> =>
  (next) => (context) => next({ ...context, ...factory(context) });

const addToResponseMW = <C, FR, R>(factory: (response: R) => FR): Middleware<C, C, R, Merge<R, FR>> =>
  (next) => async (context) => {
      const res = await next(context);
      return { ...res, ...factory(res) };
  };

// Should infer type Handler<InputContext, { a: string, b: boolean }>, but gets Handler<InputContext, unknown>
export const myHandler2 = withMiddleware(
  addToContextMW(c => ({ y: 10 })),
  addToResponseMW(r => ({ b: true })),
  async ({ x, y }): Promise<MyResponse> => {
    // y: number infers fine!

    return {
        a: `x = ${x   y}`
    };
  }
);
 

Проблема в том, что, хотя типы выводятся нормально в одном направлении (от ввода к обработчику (последний аргумент)), они не выводятся в противоположном направлении. Что я делаю не так? Или это слишком много для компилятора?