#How to make this behavior at the type level?

1 messages · Page 1 of 1 (latest)

lilac ingot
#

Hey, everybody.
Please help me, I've already broken my head.

There is a function build which is described by interfaces Build and Request. The function accepts a request configuration object
to the server interface PresetConfig.

interface PresetConfig<TResult = any> {
  method: string,
  then?: (value: AxiosResponse<TResult>) => any;
}

interface Build {
  <
    TResult,
    TConfig = PresetConfig<TResult>
  >(config: TConfig): Request<TResult, TConfig>
}

interface Request<TResult, TConfig> {
  (): TConfig extends PresetConfig<TResult>
    ? TConfig['then'] extends Function
      ? Promise<ReturnType<TConfig['then']>>
      : AxiosPromise<TResult>
    : never
}

export const build: Build = (config) => () => {
  return new Promise(res => {
    setTimeout(res, 1000)
  })
}

It's used like this:

import { build } from "@presets/Preset"

type Post = {
  "userId": number,
  "id": number,
  "title": string,
  "body": string
}

const posts = build<Post>({
  then: ({ data }) => data.body, // #1
  method: "GET"
})

posts().then(data => data) // #2

Now back to the core of the question.

I want to get the following type-level behavior.

When #1 function is declared then #2 data should be of type string, if #1 is not declared then #2 data AxiosResponse<Post>.

Please help me. Thanks.

fresh pulsar
#

you can't partially infer type parameters. when you call build<Post> with just one parameter explicitly set, the other is always going to be set to its default value (PresetConfig<TResult>) and not be inferred

#

i would personally avoid this kind of type-parameter-as-assertion pattern anyway, even though i know axios uses it. IMO it's better to truthfully treat data as unknown when you first receive it from the server (since the server could respond with anything), and then either validate it or assert it in the normal way (using as) to turn it into a Post

#

but if you really want this style you'll have to break up the type parameter list, maybe using a class or a higher-order function. i can show an example in a bit

fresh pulsar
edgy remnantBOT
#
mkantor#0

Preview:ts ... interface Build { <TResult>(): <TConfig extends PresetConfig<TResult>>( config: TConfig ) => Request<TResult, TConfig> } ...