Home > other >  Dependency injection, infer object values - typescript
Dependency injection, infer object values - typescript

Time:10-10

Link to playground:

https://www.typescriptlang.org/play?target=9#code/MYewdgzgLgBAjjAvDA3gWAFA2-ArgUwCcBPAeQCMArALlUxxgDMwBGWgCgEokA GABUIgAtgEsI AHSF8EEABsAbvnYtOAGno5mAJg4BDQgHNaYXMPJFuiPoaMwA1DADMmrNrDODx0 cuFrW2NHGBY3bABfTCiMTFBIWDgCEgAxMCQYAB4AaRh8AA8ofDAAEwgYfTBiHnY4WmzAiohiMGAsgBU8wuKymABrfGIQRhgoYgAHfGH4Gv1x0Vp2xvR3eOhUJjB1TZ1t5mcYCIy4AG050QBdLTXYGQhx8AkM-QB3fVFYZi5rx9gS-FA-xKGV07DuD0g E4WhkUFwhHS 3Y-0B BK0IwMTivxgd2OyWIaVqnHYAHIkkQyFRSRjMAB6Om42QwEogWRgUmwIyiZQwYT4Ji4eTyUblCC4cYPQiwTCYIA


I've been sitting with this for a while now and I've been trying to draw some inspiration from previous answers with no luck

I basically have the an object like this:

const query = {
  fn: () => Promise<any>,
  fn2: (arg: T) => T
  fn3: (arg: T) => T   2
}

and then I'm running it in a function where i extract all of the methods and run them sequentially:

// import * as queries

const seqFn = <T keyof typeof queries>(query: T) => {
  const { fn, fn2, fn3 } = queries[api]
  const res = await fn()
  const res2 = fn2(res)
  return fn3(res2)
}

which works nicely as ts is capable of inferring the types going in and out of every function, which means i also get type safety for the return function of seqFn.

However, now I would like to extract the import and make it dependency injected instead:

const seqFn = (queries) => <T keyof typeof queries>(query: T) => {
  const { fn, fn2, fn3 } = queries[api]
  const res = await fn()
  const res2 = fn2(res)
  return fn3(res2)
}

and this is where i run into problems. I would like to keep the same behaviour as above with the inference but no matter how i type it I only manage to get the keys inferred, not the functions themselves. I've tried different variants but I always end up in something like this:

type queryTypes<K extends querymain> = {
  [key in keyof K]: {
    fn: K[key]['fn']
    decoder: K[key]['fn2']
    transformationFn: K[key]['fn3']
  }
}

type inferQueryTypes<T extends querymain> = T extends queryTypes<T>
  ? queryTypes<T>
  : never

export const queryFnn =
  <K extends querymain>(queries: inferQueryTypes<K>) =>
  async <T extends keyof inferQueryTypes<K>>(api: T) => {
    const { fn, fn2, fn3 } = queries[api]
    const response = await fn()
    const decoded = fn2(response)
    return fn3(decoded)
  }

I'm obviously doing something wrong but what exactly? Could somebody point me in the right direction?

CodePudding user response:

The function needs a generic constraint so that the general shape of K is known. This allows us to do K[T]["fn3"] for the return type of the function.

type ValidateQueries<T extends Record<string, {
    fn1: () => Promise<any>
    fn2: (...args: any[]) => any
    fn3: (...args: any[]) => any
}>> = {
  [K in keyof T]: T[K] & {
    fn1: () => Promise<any>
    fn2: (arg: Awaited<ReturnType<T[K]["fn1"]>>) => any
    fn3: (args: ReturnType<T[K]["fn2"]>) => any
  }
}

const queryFn = <
  K extends Record<string, {
    fn1: () => Promise<any>
    fn2: (...args: any[]) => any
    fn3: (...args: any[]) => any
  }>
>(q: ValidateQueries<K>) => async <T extends keyof K>(api: T): Promise<ReturnType<K[T]["fn3"]>> => {
  const { fn1, fn2, fn3 } = q[api]
  const response = await fn1()
  const decoded = fn2(response)
  return fn3(decoded)
}

We need the explicit return type because the compiler eagerly determines a "wrong" or too general type for fn3. Auto-inference would therefore lead to a return type of Promise<any>.

const res = queryFn(q)('queryObj')

queryFn({
  queryObj: {
    fn1: () => Promise.resolve(1),
    fn2: (arg: string) => arg   3, // Error
    fn3: (arg: number) => arg   1,
  }
})('queryObj')

Playground

  • Related