1

Hot to rewrite the following javascript code in typescipt?

const handlers = {
  say (msg) {
    console.log(msg)
  },
  add (a, b) {
    return a + b
  }
}

function caller (method, ...args) {
  if (handlers[method]) return handlers[methd](...args)
  throw new Error(`${method} not found`)
}

I have more than 100 functions in handlers which separated into several files, handlers provide apis for other modules, and the caller provide a unified entrance for these apis.

Every functions has been rewrite in typescipt correctly. But when came into the function caller, I'm confused, is there any way to rewrite it and keep the type info of callees?

I've made some attempts, but none work out at the end :( The following is one of them, may be close to the right solution.

type AlterReturnType<K, T extends (...args: any[]) => any> =
  T extends () => any ? (k: K) => ReturnType<T> :
  T extends (a: infer A) => any ? (k: K, a: A) => ReturnType<T> :
  T extends (a: infer A, b: infer B) => any ? (k: K, a: A, b: B) => ReturnType<T> :
  T extends (a: infer A, b: infer B, c: infer C) => any ? (k: K, a: A, b: B, c: C) => ReturnType<T> :
  T extends (a: infer A, b: infer B, c: infer C, d: infer D) => any ? (k: K, a: A, b: B, c: C, d: D) => ReturnType<T> :
  T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E) => any ? (k: K, a: A, b: B, c: C, d: D, e: E) => ReturnType<T> :
  never

type Handlers = typeof handlers
type HandleKeys = keyof Handlers

interface IHandlerObject {
  [k: string]: (...args: any[]) => any
}

type GetCaller<T extends IHandlerObject> = {
  [k in keyof T]: AlterReturnType<k, T[k]>
}

type Caller = GetCaller<Handlers>[keyof Handlers]

const caller: Caller = function name (method: string, ...args: any[]) {
  // do know how to rewrite the following
  // @ts-ignore
  if (handlers[method]) return handlers[method](...args)
  throw new Error(`${method} not found`)
}

// TS error occurred:
//   [ts] Cannot invoke an expression whose type lacks a call signature. Type '((k: "say", a: any) => void) | ((k: "add", a: any, b: any) => any)' has no compatible call signatures.
caller('say', 'ggg')
Wing
  • 11
  • 2

1 Answers1

0

You've managed to produce a union of the correct call signatures, but what you need is an intersection. By searching src/compiler/checker.ts for calls to getIntersectionType, I found you can do that by (ab)using contravariant type inference:

type Caller1 = GetCaller<Handlers>;
type Caller =
  {[k in keyof Caller1]: (p: Caller1[k]) => void} extends
    {[n: string]: (p: infer T) => void} ? T : never;

Edit: See Transform union type to intersection type for a similar technique with more explanation.

Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75