Отражать и отображать типизированную форму аргумента в машинописном тексте?

контекст

Я пытаюсь создать инструмент для создания кода. Мне нравится использовать GraphQL, однако, когда я владею полным стеком, кажется немного глупым, что мой интерфейс вызывает мой сервер со строго определенными запросами gql. GQL строго типизирован, поэтому я могу предоставлять строго типизированные запросы и ответы.

проблема

Я не знаю, как создать такой интерфейс, чтобы я мог рекурсивно отражать и отображать аргумент из типа ввода в целевой тип. В частности, я хочу сопоставить тип моего запроса запроса с типом ответа запроса gql.

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> отображает мой Food.IFood интерфейс (не полностью) в новый тип, где ключи сопоставляются с bools, что указывает на включение поля GQL
  • 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.

Я понимаю, что приведенное выше описание моего клиента GQL в значительной степени упрощено и исключает другие сложности, необходимые для совместимости со всеми функциями GQL. Это нормально и хорошо. Моя главная цель в этом посте - строго посмотреть, есть ли способ сделать это отображение отраженного типа.

Я начал набрасывать жестко запрограммированный файл target client.ts для того, чтобы потенциальный результат выглядел здесь: https://github.com/cdaringe/gql-ts-client-codegen/blob/master/src/target.ts

Любой вклад будет оценен! Спасибо.


person cdaringe    schedule 05.01.2019    source источник


Ответы (1)


К сожалению, вы хотите ограничить тип переменной и в то же время заставить компилятор определить тип этой переменной. К сожалению, это невозможно напрямую.

Единственный способ добиться желаемого поведения - использовать функцию. Функции могут иметь параметры универсального типа, на которые наложены ограничения, но последний параметр типа будет выведен из фактического литерала объекта, переданного в:

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 - это функция, которая возвращает функцию (т.е. каррированную функцию), чтобы можно было указать первый аргумент и вывести второй,

person Titian Cernicova-Dragomir    schedule 06.01.2019
comment
привет @titian, спасибо за отзыв. Вдохновленный вашим постом, я сделал более компактную попытку. Я близок, однако, в моем impl, QueryResult все еще не полностью ограничен - он все еще терпит неудачу, потому что он считает, что все ключи от Food.IQuery доступны для результата. У меня есть пример, сжатый до ~ 40LOC. не возражаете взглянуть? github.com/cdaringe/cdaringe client-codegen / blob / - person cdaringe; 11.03.2019
comment
@cdaringe Сейчас я путешествую, и этот комментарий, скорее всего, потеряюсь ... Напишите мне в gitter, и я посмотрю завтра. То же имя пользователя и в gitter :-) - person Titian Cernicova-Dragomir; 11.03.2019
comment
@cdaringe Я думаю, вам нужно использовать function exec <P extends QueryRequest<P>> (query: P) - person Titian Cernicova-Dragomir; 12.03.2019