0

I am using typebox for generating types and schemas for a REST-API. I would like to organize REST-resources like this:

{
  querySchema: TSchema;
  responseSchema: TSchema;
  get: (query : QueryType) => ResponseType;
}

Where QueryType = Static<typeof querySchema> etc.

Right now, I have a Type that takes typeof querySchema as template arguments:

type Resource<Q extends TSchema, R extends TSchema> = 
{
  querySchema: Q;
  responseSchema: R;
  get: (query: Satic<Q>) => Static<R>
}

Is there a way to avoid having to use template types? I would like to have something like this:

type Resource = 
{
  querySchema: infer Q; // <-- infer Q from querySchema
  responseSchema: infer R;
  get: (query: Satic<Q>) => Static<R>
}

I have tried using a function as a "wrapper" around the type with no success:

const resource = <T>(
  r : T extends Resource<infer Q, infer R> ?
    Resource<Q, R> : 
    never,
) => r;

resource({ querySchema, responseSchema, ... }) // querySchema is not assignable to type 'never'.

Is there a way to automatically infer the template arguments in Typescript?

Edit:

The behavior I am looking for is to be able to do this:

const users = resource(
    {
        // querySchema from typebox
        querySchema: Type.Object(...),

        /* this is where I would like TS to understand that q is of
         * type Static<typeof querySchema>
         * 
         */
        get: (q) => { ... } 
    }
)
l'arbre
  • 719
  • 2
  • 10
  • 29

1 Answers1

1

You had the right idea with the wrapper function, no need to have a conditional type. TypeScript will first infer from the type of the parameters and checks if it matches the generic, so this is how we will infer our Q and R

EDIT 3/24/2022 - This actually works now.

const resource = <Q extends TSchema, R extends TSchema>(r: {
  querySchema: Q
  responseSchema: R
  get: (query: Static<Q>) => Static<R>;
}) => r;

resource({ querySchema, responseSchema, get })

This will now infer based on the value of querySchema/responseSchema

Cody Duong
  • 2,292
  • 4
  • 18
  • Thanks for the playground! Sadly, it seems to stop working once ```Static``` depends on T (which it obviously does in reality). Do you know why that is? – l'arbre Mar 22 '22 at 09:53
  • @l'arbre Do you mean have the `get` key/value inferred from `querySchema` and `responseSchema`? In order to do that you'll have to move the get type definition to outside of Resource and into the resource wrapper function. I have attached a new playground link demoing this. – Cody Duong Mar 22 '22 at 18:03
  • No, not exactly. I would like to define get: () => ... within the object so that I can construct a rest resource in one go with type safety and autocomplete in my IDE. – l'arbre Mar 24 '22 at 07:56
  • @l'arbre I have appended a new playground link, you can't have reverse inferences, but since TypeBox handily exports most of its types, we can piggyback off that to write our own type inference system. – Cody Duong Mar 24 '22 at 11:37
  • Thanks for the elaborate example but I think I may not have explained my issue well enough. I will add an edit to the question due to space limitations. – l'arbre Mar 24 '22 at 14:08
  • OK, I got it now. My best guess is that the inferencing fails once you start to go across multiple layers. But I have edited and posted a fix, that I have tested locally with the actual TypeBox definitions and seems good. – Cody Duong Mar 24 '22 at 14:53
  • YES! This worked. Could not be simpler. – l'arbre Mar 24 '22 at 16:07