Open Pajn opened 5 years ago
Agreed, this is very almost perfect, except for that point... ill have a quick play and see if I can crack it..
@Pajn I can maybe see how mapped types can be used to pick the first level properties based on the query, but I can't see a solution on how to make it work with an arbitrarily nested GraphQL query/response.
Possibly there is a way to have at least some N-levels-down solution...
Okay, looked at conditional types. This shows more promise
It can definitely be done. I had to head out but will take a look at it tomorrow or the day after if you dont work it out before hand :)
@mikecann this is what I have so far. Appears to be working recursively.
interface User {
id: string
name: string
email: string | null
posts: Post[]
}
interface Post {
id: string
title: string
description: string | null
author: User
}
interface UserRequest {
id?: boolean | number
name?: boolean | number
email?: boolean | number
posts?: PostRequest
}
interface PostRequest {
id?: boolean | number
title?: boolean | number
description?: boolean | number
author?: UserRequest
}
type UserPartial<R extends UserRequest> =
(R extends { id: boolean | number } ? { id: string } : {}) &
(R extends { name: boolean | number } ? { name: string } : {}) &
(R extends { email: boolean | number } ? { email: string | null } : {}) &
(R extends { posts: PostRequest } ? { posts: PostPartial<R['posts']>[] } : {})
type PostPartial<R extends PostRequest> =
(R extends { id: boolean | number } ? { id: string } : {}) &
(R extends { title: boolean | number } ? { title: string } : {}) &
(R extends { description: boolean | number } ? { description: string | null } : {}) &
(R extends { author: UserRequest } ? { author: UserPartial<R['author']> } : {})
const query = <T>(r: T extends UserRequest ? UserRequest : T): UserPartial<T> => {
throw new Error('')
}
Oh lol, you beat me too it, this is what I just came up with:
// The main grapQL type
type List = {
name: string;
age: number;
something: {
foo: "bar"
}
};
type Req<T> = { [P in keyof T]?: number | boolean } & {
__typename?: boolean | number;
__scalar?: boolean | number;
};
function makeExecute<U>() {
return function execute<T extends Req<U> >(
request: T & Req<U>,
defaultValue?: U
): { [P in keyof T]: P extends keyof U ? U[P] : never } {
throw "put implementation here";
}
}
const execute = makeExecute<List>();
const x = execute({ name: 1, something: 1 });
Ill see if I can make mine recursive with your types..
interface User {
id: string
name: string
email: string | null
posts: Post[]
}
interface Post {
id: string
title: string
description: string | null
author: User
}
type RequestProp<T> = T extends string ? number | boolean : Req<T>;
type Req<T> = { [P in keyof T]?: RequestProp<T[P]> } & {
__typename?: boolean | number;
__scalar?: boolean | number;
};
function makeExecute<U>() {
return function execute<T extends Req<U> >(
request: T & Req<U>,
defaultValue?: U
): { [P in keyof T]: P extends keyof U ? U[P] : never } {
throw "put implementation here";
}
}
const x = makeExecute<Post>()({ title: 1, author: { id: 1 } });
const y = makeExecute<User>()({ });
gotta go to bed now, but its almost there, just needs to handle arrays as part of that conditional.
Oh man I must have been tired, because the above doesnt work for recursive types..
Here this solves it recursively, it makes your brain bleed a little trying to work it out tho..
type Scalar = string | number | boolean;
interface User {
id: string;
name: string;
age: number;
email: string | null;
posts: Post[];
}
interface Post {
id: string;
title: string;
description: string | null;
author: User;
}
type Selectable = number | boolean;
type RequestSelctionField<TFieldValue> = TFieldValue extends Scalar
? Selectable
: TFieldValue extends (infer X)[]
? RequestSelection<X>
: RequestSelection<TFieldValue>;
type RequestSelection<TObject> = {
[P in keyof TObject]?: RequestSelctionField<TObject[P]>
} & {
__typename?: Selectable;
__scalar?: Selectable;
};
type ReturnProp<
TObject,
TRequestSelection,
TField extends keyof TObject,
TObjectValue = TObject[TField]
> = TObjectValue extends Scalar
? TObjectValue
: TField extends keyof TRequestSelection
? TObjectValue extends (infer X)[]
? Returned<X, TRequestSelection[TField]>[]
: Returned<TObject[TField], TRequestSelection[TField]>
: never;
type Returned<TObject, TRequestSelection> = {
[TField in keyof TRequestSelection]: TField extends keyof TObject
? ReturnProp<TObject, TRequestSelection, TField>
: never
};
function makeExecute<TObject>() {
return function execute<TRequest extends RequestSelection<TObject>>(
request: TRequest & RequestSelection<TObject>,
defaultValue?: TObject
): Returned<TObject, TRequest> {
throw "put implementation here";
};
}
const x = makeExecute<Post>()({
title: 1,
author: { id: 1, age: 1, posts: { id: 1 } }
});
const xx = x.author.posts[0].
BTW using the above one could make a very nice fluent syntax using just a bit more typing and proxy objects.
type QueryArgsMap = {
markTagSuggestions: QueryMarkTagSuggestionsArgs;
marks: QueryMarksArgs;
mark: QueryMarkArgs;
};
type QueryArgs<P, T> = P extends keyof QueryArgsMap ? QueryArgsMap[P] : never;
type Executable<TObject> = {
execute: <TRequest extends RequestSelection<TObject>>(
request: TRequest & RequestSelection<TObject>
) => Returned<TObject, TRequest>;
};
type MakeQueryable<T> = {
[P in keyof T]: (variables: QueryArgs<P, T>) => Executable<T[P]>
};
type Root = {
query: MakeQueryable<Query>;
};
let root: Root = {} as any;
const resp = root.query.marks({ input: { query: "foo" } }).execute({ totalCount: 1, marks: { id: 1 } });
resp.marks[0].
The above is using the schema from one of my own projects but will work with any object type.
... thinking about it more one could just export GraphQL AST much like https://github.com/apollographql/graphql-tag does, then it could be consumed by Apollo Client and you wouldnt have to write your own client with caching and all other other features that Apollo supports..
maybe https://github.com/prisma/nexus already does most of this..
Here this solves it recursively, it makes your brain bleed a little trying to work it out tho..
do you think your solution can fit the __scalar
logic and return types that are unions/interfaces, not specific objects? the solution I'm currently considering relies on pre-generating the conditional types like these
export type UserPartial<
R extends UserRequest,
F1 extends UserRequest = _FR<R['friends'], UserRequest>,
F2 extends PostRequest = _FR<R['posts'], PostRequest>,
F3 extends PetRequest = _FR<R['pets'], PetRequest>
> = (R extends Required<Pick<R, 'id'>> ? { id: ID } : {}) &
(R extends Required<Pick<R, 'username'>> ? { username: String } : {}) &
(R extends Required<Pick<R, 'email'>> ? { email: String } : {}) &
(R extends Required<Pick<R, 'wasEmployed'>> ? { wasEmployed: Boolean | null } : {}) &
(R extends Required<Pick<R, 'friends'>> ? { friends: UserPartial<F1>[] | null } : {}) &
(R extends Required<Pick<R, 'posts'>> ? { posts: PostPartial<F2>[] } : {}) &
(R extends Required<Pick<R, 'pets'>> ? { pets: PetPartial<F3>[] } : {})
It also does not yet implements __scalar
or unions/interfaces, but at least I can see how it can potentially be implemented, since all the information is there during the generation process. not sure how can that information can be extracted in your solution, if it only relies on inference. Maybe only pre-generate the lists of scalar props. Curious.
BTW using the above one could make a very nice fluent syntax using just a bit more typing and proxy objects.
not exactly sure what you have there. is the goal to get the same API as it is now, but with less pre-generated ts code?
you wouldnt have to write your own client with caching and all other other features that Apollo supports..
Currently, you can also use Apollo client here easily, inside the fetching mechanism
@helios1138 Awesome. I think you should go with your technique if you can get it to work.
I spent half a day trying to come up with a clever non / minimal codegen solution when I should have been working on my project and although theoretically I could make it work I just dont have the time for it right now.
BTW I even spent a coupple of hours on a pair programming session with a TS evangelist who works closely with the compiler team on this problem :) Its a really fun problem to solve, wish I had a bit more time on it.
For now tho, go with your solution if it works. Im keen to use it in my project!
Hello,
I'm very interested by this client and I think this part is the last missing piece. 😍 I very much agree with @mikecann on this
... thinking about it more one could just export GraphQL AST much like https://github.com/apollographql/graphql-tag does, then it could be consumed by Apollo Client and you wouldnt have to write your own client with caching and all other other features that Apollo supports..
I'd like a fully typed equivalent of graphql-tag to pass to any client (in my case I'd use react-query).
It's like in CSS in JS, they all went from the css
taged template to a typed object alternative, and I feel that we're very close to it here.
How can I help?
Graphql Zeus uses a generic type MapType
to select some fields DST
from an interface SRC
type MapType<SRC extends Anify<DST>, DST> = ...
View the code here
We can simply copy paste that code and have the feature right now
It would be very nice if the result type only included the selected fields. I imagine this could be solvable by using a combination of mapped types and conditional types.
Have you looked into this at and have some experience on the possibility?