Skip to content

Commit b47af44

Browse files
committed
feat: fully typed variable support
1 parent d7d122e commit b47af44

File tree

4 files changed

+105
-172
lines changed

4 files changed

+105
-172
lines changed
Lines changed: 32 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,48 @@
11
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
22
import gql from 'graphql-tag';
3-
import { GraphQLTypes, InputType, ValueTypes, Zeus, $ } from './index';
3+
import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index';
44

5-
const $$ = new Proxy(
6-
{},
7-
{
8-
get(target, propName, receiver) {
9-
return 'ZEUS_VAR$' + propName.toString();
10-
},
11-
},
12-
);
13-
14-
const $$ = <Name extends string>(name: Name) => {
15-
return ('ZEUS_VAR$' + name) as any as Variable<any, Name>;
5+
type Variable<T, Name extends string> = {
6+
' __zeus_name': Name;
7+
' __zeus_type': T;
168
};
179

18-
type X<K extends string> = { [Key in K]: Variable<any, K> };
10+
type QueryInputWithVariables<T> = T extends string | number | Array<any>
11+
? Variable<T, any> | T
12+
: Variable<T, any> | { [K in keyof T]: QueryInputWithVariables<T[K]> } | T;
1913

20-
type Variable<T, Name extends string> = {
21-
__zeus_name: Name;
22-
__zeus_type: T;
23-
};
14+
type QueryWithVariables<T> = T extends [infer Input, infer Output]
15+
? [QueryInputWithVariables<Input>, QueryWithVariables<Output>]
16+
: { [K in keyof T]: QueryWithVariables<T[K]> };
17+
18+
type ExtractVariables<Query> = Query extends Variable<infer VType, infer VName>
19+
? { [key in VName]: VType }
20+
: Query extends [infer Inputs, infer Outputs]
21+
? ExtractVariables<Inputs> & ExtractVariables<Outputs>
22+
: Query extends string | number | boolean
23+
? {}
24+
: UnionToIntersection<{ [K in keyof Query]: ExtractVariables<Query[K]> }[keyof Query]>;
2425

25-
type VariablizedInput<T> = T extends string | number | Array<any>
26-
? T | Variable<T, any>
27-
: T | Variable<T, any> | { [K in keyof T]: VariablizedInput<T[K]> };
26+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
2827

29-
type VariablizedQuery<T> = T extends [infer Input, infer Output]
30-
? [VariablizedInput<Input>, VariablizedQuery<Output>]
31-
: { [K in keyof T]: VariablizedQuery<T[K]> };
28+
export const $ = <Type, Name extends string>(name: Name) => {
29+
return ('ZEUS_VAR$' + name) as any as Variable<Type, Name>;
30+
};
3231

33-
type ConnValue = ValueTypes['connection'];
34-
type Test = VariablizedQuery<ConnValue>;
35-
export function tq<ResultType extends ValueTypes['query_root'], VariablesType>(
36-
query: VariablizedQuery<ResultType>,
37-
): TypedDocumentNode<InputType<GraphQLTypes['query_root'], ResultType>, VariablesType> {
32+
export function query<Z extends QueryWithVariables<ValueTypes['query_root']>>(
33+
query: Z,
34+
): TypedDocumentNode<InputType<GraphQLTypes['query_root'], Z>, ExtractVariables<Z>> {
3835
return gql(Zeus('query', query as any));
3936
}
4037

41-
export function tm<Z extends ValueTypes['mutation_root']>(
38+
export function mutation<Z extends QueryWithVariables<ValueTypes['mutation_root']>>(
4239
mutation: Z,
43-
): TypedDocumentNode<InputType<GraphQLTypes['mutation_root'], Z>, {}> {
44-
return gql(Zeus('mutation', mutation));
40+
): TypedDocumentNode<InputType<GraphQLTypes['mutation_root'], Z>, ExtractVariables<Z>> {
41+
return gql(Zeus('mutation', mutation as any));
4542
}
4643

47-
export function ts<Z extends ValueTypes['subscription_root']>(
48-
mutation: Z,
49-
): TypedDocumentNode<InputType<GraphQLTypes['subscription_root'], Z>, {}> {
50-
return gql(Zeus('mutation', mutation));
44+
export function subscription<Z extends QueryWithVariables<ValueTypes['subscription_root']>>(
45+
subscription: Z,
46+
): TypedDocumentNode<InputType<GraphQLTypes['subscription_root'], Z>, ExtractVariables<Z>> {
47+
return gql(Zeus('subscription', subscription as any));
5148
}
52-
53-
tq({
54-
aggregateBookings: [
55-
{
56-
where: {
57-
bookerName: { _eq: $$('bookerName') },
58-
},
59-
},
60-
{
61-
aggregate: {
62-
avg: {
63-
nights: true,
64-
},
65-
},
66-
},
67-
],
68-
});
69-
70-
// Example
71-
const userMemberships = tq({
72-
user: [
73-
{
74-
id: $$('id'),
75-
},
76-
{
77-
memberships: [
78-
{
79-
limit: $$('limit'),
80-
},
81-
{
82-
role: true,
83-
},
84-
],
85-
},
86-
],
87-
});
88-
89-
const mutate = tm({
90-
insertBooking: [
91-
{
92-
object: {},
93-
},
94-
{
95-
bookerName: true,
96-
},
97-
],
98-
});
Lines changed: 26 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,51 @@
1+
/* eslint-disable */
2+
/* eslint-disable */
3+
14
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
25
import gql from 'graphql-tag';
36
import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index';
47

5-
const $$ = <Type, Name extends string>(name: Name) => {
6-
return ('ZEUS_VAR$' + name) as any as Variable<Type, Name>;
7-
};
8-
98
type Variable<T, Name extends string> = {
10-
__zeus_name: Name;
11-
__zeus_type: T;
9+
' __zeus_name': Name;
10+
' __zeus_type': T;
1211
};
1312

14-
type VariablizedInput<T> = T extends string | number | Array<any>
15-
? T | Variable<T, any>
16-
: T | Variable<T, any> | { [K in keyof T]: VariablizedInput<T[K]> };
13+
type QueryInputWithVariables<T> = T extends string | number | Array<any>
14+
? Variable<T, any> | T
15+
: Variable<T, any> | { [K in keyof T]: QueryInputWithVariables<T[K]> } | T;
1716

18-
type VariablizedQuery<T> = T extends [infer Input, infer Output]
19-
? [VariablizedInput<Input>, VariablizedQuery<Output>]
20-
: { [K in keyof T]: VariablizedQuery<T[K]> };
17+
type QueryWithVariables<T> = T extends [infer Input, infer Output]
18+
? [QueryInputWithVariables<Input>, QueryWithVariables<Output>]
19+
: { [K in keyof T]: QueryWithVariables<T[K]> };
2120

2221
type ExtractVariables<Query> = Query extends Variable<infer VType, infer VName>
2322
? { [key in VName]: VType }
2423
: Query extends [infer Inputs, infer Outputs]
25-
? Intersectionize<{ inputs: ExtractVariables<Inputs>; outputs: ExtractVariables<Outputs> }>
24+
? ExtractVariables<Inputs> & ExtractVariables<Outputs>
2625
: Query extends string | number | boolean
2726
? {}
28-
: Intersectionize<{ [K in keyof Query]: ExtractVariables<Query[K]> }>;
29-
30-
type Intersectionize<ObjectKeys> = UnionToIntersection<ObjectKeys[keyof ObjectKeys]>;
27+
: UnionToIntersection<{ [K in keyof Query]: ExtractVariables<Query[K]> }[keyof Query]>;
3128

3229
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
3330

34-
// Unit testing:
35-
// type TestExtractor = ExtractVariables<{
36-
// cardById: [{ cardId: Variable<any, 'test'> }, { attack: true; defense: true }];
37-
// }>;
31+
export const $ = <Type, Name extends string>(name: Name) => {
32+
return ('ZEUS_VAR$' + name) as any as Variable<Type, Name>;
33+
};
3834

39-
export function tq<ResultType extends VariablizedQuery<ValueTypes['Query']>>(
40-
query: ResultType,
41-
): TypedDocumentNode<InputType<GraphQLTypes['Query'], ResultType>, ExtractVariables<ResultType>> {
35+
export function query<Z extends QueryWithVariables<ValueTypes['Query']>>(
36+
query: Z,
37+
): TypedDocumentNode<InputType<GraphQLTypes['Query'], Z>, ExtractVariables<Z>> {
4238
return gql(Zeus('query', query as any));
4339
}
4440

45-
export function tm<Z extends ValueTypes['Mutation']>(
41+
export function mutation<Z extends QueryWithVariables<ValueTypes['Mutation']>>(
4642
mutation: Z,
47-
): TypedDocumentNode<InputType<GraphQLTypes['Mutation'], Z>, {}> {
48-
return gql(Zeus('mutation', mutation));
43+
): TypedDocumentNode<InputType<GraphQLTypes['Mutation'], Z>, ExtractVariables<Z>> {
44+
return gql(Zeus('mutation', mutation as any));
4945
}
5046

51-
export function ts<Z extends ValueTypes['Subscription']>(
52-
mutation: Z,
53-
): TypedDocumentNode<InputType<GraphQLTypes['Subscription'], Z>, {}> {
54-
return gql(Zeus('mutation', mutation));
47+
export function subscription<Z extends QueryWithVariables<ValueTypes['Subscription']>>(
48+
subscription: Z,
49+
): TypedDocumentNode<InputType<GraphQLTypes['Subscription'], Z>, ExtractVariables<Z>> {
50+
return gql(Zeus('subscription', subscription as any));
5551
}
56-
57-
const q = tq({
58-
cardById: [
59-
{
60-
cardId: $$('test'),
61-
},
62-
{
63-
Attack: true,
64-
Defense: true,
65-
},
66-
],
67-
});
68-
69-
// Example
70-
// const userMemberships = tq({
71-
// user: [
72-
// {
73-
// id: $$('id'),
74-
// },
75-
// {
76-
// memberships: [
77-
// {
78-
// limit: $$('limit'),
79-
// },
80-
// {
81-
// role: true,
82-
// },
83-
// ],
84-
// },
85-
// ],
86-
// });
87-
88-
// const mutate = tm({
89-
// insertBooking: [
90-
// {
91-
// object: {},
92-
// },
93-
// {
94-
// bookerName: true,
95-
// },
96-
// ],
97-
// });

src/plugins/typed-document-node/index.spec.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Parser } from 'graphql-js-tree';
22
import { pluginTypedDocumentNode } from '.';
33

44
describe('plugin typed document node test', () => {
5-
it('generates correct apollo plugin from the schema', () => {
5+
it('generates correct typed document node plugin from the schema', () => {
66
const schema = `
77
type Query{
88
people: [String!]!
@@ -21,30 +21,32 @@ schema{
2121
`;
2222
const tree = Parser.parse(schema);
2323
const tdnResult = pluginTypedDocumentNode({ tree });
24+
2425
expect(tdnResult.ts).toContain(`
2526
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
2627
import gql from 'graphql-tag';
2728
import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index';
2829
`);
2930

30-
expect(tdnResult.ts).toContain(`export function tq<Z extends ValueTypes["Query"]>(
31-
query: Z | ValueTypes["Query"],
32-
): TypedDocumentNode<InputType<GraphQLTypes["Query"], Z>, {}> {
33-
return gql(Zeus("query", query));
31+
expect(tdnResult.ts).toContain(`
32+
export function query<Z extends QueryWithVariables<ValueTypes["Query"]>>(
33+
query: Z,
34+
): TypedDocumentNode<InputType<GraphQLTypes["Query"], Z>, ExtractVariables<Z>> {
35+
return gql(Zeus("query", query as any));
3436
}`);
3537

3638
expect(tdnResult.ts).toContain(`
37-
export function tm<Z extends ValueTypes["Mutation"]>(
38-
mutation: Z | ValueTypes["Mutation"],
39-
): TypedDocumentNode<InputType<GraphQLTypes["Mutation"], Z>, {}> {
40-
return gql(Zeus("mutation", mutation));
39+
export function mutation<Z extends QueryWithVariables<ValueTypes["Mutation"]>>(
40+
mutation: Z,
41+
): TypedDocumentNode<InputType<GraphQLTypes["Mutation"], Z>, ExtractVariables<Z>> {
42+
return gql(Zeus("mutation", mutation as any));
4143
}
4244
`);
4345
expect(tdnResult.ts).toContain(`
44-
export function ts<Z extends ValueTypes["Subscription"]>(
45-
subscription: Z | ValueTypes["Subscription"],
46-
): TypedDocumentNode<InputType<GraphQLTypes["Subscription"], Z>, {}> {
47-
return gql(Zeus("subscription", subscription));
46+
export function subscription<Z extends QueryWithVariables<ValueTypes["Subscription"]>>(
47+
subscription: Z,
48+
): TypedDocumentNode<InputType<GraphQLTypes["Subscription"], Z>, ExtractVariables<Z>> {
49+
return gql(Zeus("subscription", subscription as any));
4850
}
4951
`);
5052
});

src/plugins/typed-document-node/index.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { OperationType, ParserTree } from 'graphql-js-tree';
22

33
const createOperationFunction = ({ queryName, operation }: { queryName: string; operation: OperationType }) => {
4-
const firstLetter = operation[0];
4+
// const firstLetter = operation[0];
55

66
return {
77
queryName,
88
operation,
9-
ts: `export function t${firstLetter}<Z extends ValueTypes["${queryName}"]>(
10-
${operation}: Z | ValueTypes["${queryName}"],
11-
): TypedDocumentNode<InputType<GraphQLTypes["${queryName}"], Z>, {}> {
12-
return gql(Zeus("${operation}", ${operation}));
9+
ts: `export function ${operation}<Z extends QueryWithVariables<ValueTypes["${queryName}"]>>(
10+
${operation}: Z,
11+
): TypedDocumentNode<InputType<GraphQLTypes["${queryName}"], Z>, ExtractVariables<Z>> {
12+
return gql(Zeus("${operation}", ${operation} as any));
1313
}
1414
`,
1515
};
@@ -36,6 +36,33 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
3636
import gql from 'graphql-tag';
3737
import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index';
3838
39+
type Variable<T, Name extends string> = {
40+
' __zeus_name': Name;
41+
' __zeus_type': T;
42+
};
43+
44+
type QueryInputWithVariables<T> = T extends string | number | Array<any>
45+
? Variable<T, any> | T
46+
: Variable<T, any> | { [K in keyof T]: QueryInputWithVariables<T[K]> } | T;
47+
48+
type QueryWithVariables<T> = T extends [infer Input, infer Output]
49+
? [QueryInputWithVariables<Input>, QueryWithVariables<Output>]
50+
: { [K in keyof T]: QueryWithVariables<T[K]> };
51+
52+
type ExtractVariables<Query> = Query extends Variable<infer VType, infer VName>
53+
? { [key in VName]: VType }
54+
: Query extends [infer Inputs, infer Outputs]
55+
? ExtractVariables<Inputs> & ExtractVariables<Outputs>
56+
: Query extends string | number | boolean
57+
? {}
58+
: UnionToIntersection<{ [K in keyof Query]: ExtractVariables<Query[K]> }[keyof Query]>;
59+
60+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
61+
62+
export const $ = <Type, Name extends string>(name: Name) => {
63+
return ('ZEUS_VAR$' + name) as any as Variable<Type, Name>;
64+
};
65+
3966
${o.ts}
4067
`,
4168
};

0 commit comments

Comments
 (0)