Reflect and map typed argument shape in typescript?

[亡魂溺海] 提交于 2019-12-13 01:28:30

问题


context

I am attempting to build a codegen tool. I enjoy using GraphQL, however, when I own the full stack, it seems a bit silly that my front-end calls my back-end with stringly defined gql queries. GQL is strongly typed, thus I should be able to provide strong typed queries and responses.

problem

I don't know how to build an interface such that I can recursively reflect and map an argument from an input type to a target type. Specifically, I want to map my query request type to a gql query response type.

const query: QueryRequest<Food.IFood> = {
  name: true // true ==> implies inclusion in response
}
const res = await client.food(query)
console.log(res.name) // PASS - should compile
console.log(res.nodeId) // FAIL - should not compile. `nodeId` was not present in query

// Food.IFood is a TS interface, representative of my GQL schema.  GQL => TS interfaces is a solved codegen problem already
// ref: https://github.com/cdaringe/gql-ts-client-codegen/blob/master/src/__tests__/fixture/namespace.ts
  • QueryRequest<T> maps my Food.IFood interface (incompletely) into a new type, where keys map to bools, indicating GQL field inclusion
  • However, each client method would need to sniff the passed QueryRequest<T> for explicit shape, and somehow map that explicit shape on to, essentially, a Partial<Food.IFood>.
    • Cleary I don't want a Partial--a Partial is ambiguous as to which fields are present. I want the client's response to have explicit field membership, as a function of the input.

I understand that the above description of my GQL client is largely over-simplified and hand waiving other complexities required to be compliant with all GQL features. That's fine and good. My main objective in this post is strictly to see if there's a way to do this reflected type mapping.

I have begun sketching out a hard-coded target client.ts file for what I'd like a potential output to look like here: https://github.com/cdaringe/gql-ts-client-codegen/blob/master/src/target.ts

Any input would be appreciated! Thanks.


回答1:


Unfortunately what you want is basically to constrain the type of a variable while at the same time get the compiler to infer the type for that variable. That in not unfortunately possible directly.

The only way to achieve the desired behavior is to use a function. Functions can have generic type parameters that have constraints placed on them but the final type parameter will be inferred from the actual object literal passed in:

type QueryRequest<T, K extends keyof T> = {
    keys: Record<K, boolean>
} 

function buildQueryRequest<T>() {
    return function <P extends keyof T> (o:Partial<Record<P, boolean>>) : QueryRequest<T, P>{
        return null!;
    }
}


interface IFood {
    name: string;
    nodeId: number;
}


type QueryResult<T, K extends keyof T> = Pick<T, K>
declare class Client {
    food<K extends keyof IFood>(q: QueryRequest<IFood, K>) : Promise<QueryResult<IFood, K>>
}

(async function (client: Client) {
    const query = buildQueryRequest<IFood>()({
        name: true // true ==> implies inclusion in response
    })
    const res = await client.food(query)
    console.log(res.name) // PASS - should compile
    console.log(res.nodeId) // error
})

buildQueryRequest is a function that returns a function (ie a curried function) in order to allow for the first argument to be specified and the second one to be inferred,



来源:https://stackoverflow.com/questions/54055578/reflect-and-map-typed-argument-shape-in-typescript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!