From 164fac475a7b46e754eca05beadc2506041d65c6 Mon Sep 17 00:00:00 2001 From: Gjorgji Kjosev Date: Sat, 19 Mar 2022 18:10:32 +0000 Subject: [PATCH 1/7] wip: typed-document-node plugin --- .../src/zeus/typedDocumentNode.ts | 36 +++++++++ examples/typescript-node/package.json | 4 +- src/CLI/CLIClass.ts | 4 + src/CLI/index.ts | 6 ++ src/plugins/typed-document-node/index.spec.ts | 26 +++++++ src/plugins/typed-document-node/index.ts | 74 +++++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts create mode 100644 src/plugins/typed-document-node/index.spec.ts create mode 100644 src/plugins/typed-document-node/index.ts diff --git a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts new file mode 100644 index 00000000..8a933798 --- /dev/null +++ b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts @@ -0,0 +1,36 @@ +import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; +import gql from 'graphql-tag'; +import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; + +// Note: turns out the generated query does NOT have any params :| +// type ParamsType = SRC extends [infer Params, infer Result] ? Params : { [K in keyof SRC]: ParamsType }; + +// type PayloadType = MapType>; + +export function createTypedQuery( + query: Z, +): TypedDocumentNode, {}> { + const zeusQuery = Zeus('query', query); + const gqlQuery = gql(zeusQuery); + + return gqlQuery; +} + +// Example +const userMemberships = createTypedQuery({ + user: [ + { + id: 'x', + }, + { + memberships: [ + { + limit: 5, + }, + { + role: true, + }, + ], + }, + ], +}); diff --git a/examples/typescript-node/package.json b/examples/typescript-node/package.json index 13799ec0..4a1a92db 100644 --- a/examples/typescript-node/package.json +++ b/examples/typescript-node/package.json @@ -15,6 +15,8 @@ "dependencies": { "@apollo/client": "^3.4.16", "node-fetch": "^2.6.0", - "react-query": "^3.27.0" + "react-query": "^3.27.0", + "@graphql-typed-document-node/core":"^3.1.1", + "graphql-tag":"^2.12.6" } } diff --git a/src/CLI/CLIClass.ts b/src/CLI/CLIClass.ts index 65848a74..a89eb565 100644 --- a/src/CLI/CLIClass.ts +++ b/src/CLI/CLIClass.ts @@ -8,6 +8,7 @@ import { Parser } from 'graphql-js-tree'; import { pluginApollo } from '@/plugins/apollo'; import { pluginReactQuery } from '@/plugins/react-query'; import { pluginStucco } from '@/plugins/stuccoSubscriptions'; +import { pluginTypedDocumentNode } from '@/plugins/typed-document-node'; /** * basic yargs interface @@ -96,6 +97,9 @@ export class CLI { if (args.stuccoSubscriptions) { writeFileRecursive(path.join(pathToFile, 'zeus'), `stuccoSubscriptions.ts`, pluginStucco({ tree }).ts); } + if (args.typedDocumentNode) { + writeFileRecursive(path.join(pathToFile, 'zeus'), `typedDocumentNode.ts`, pluginTypedDocumentNode({ tree }).ts); + } }; } diff --git a/src/CLI/index.ts b/src/CLI/index.ts index 6df5cd01..717be740 100644 --- a/src/CLI/index.ts +++ b/src/CLI/index.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node import * as yargs from 'yargs'; +import { boolean } from 'yargs'; import { CLI } from './CLIClass'; const args = yargs .usage( @@ -41,6 +42,11 @@ zeus [path] [output_path] [options] describe: 'Generate React Query useTypedQuery module', boolean: true, }) + .option('typedDocumentNode', { + alias: 'td', + describe: 'Generate TypedDocumentNode createQuery module', + boolean: true, + }) .option('header', { alias: 'h', describe: diff --git a/src/plugins/typed-document-node/index.spec.ts b/src/plugins/typed-document-node/index.spec.ts new file mode 100644 index 00000000..6c5877cf --- /dev/null +++ b/src/plugins/typed-document-node/index.spec.ts @@ -0,0 +1,26 @@ +import { Parser } from 'graphql-js-tree'; +import { pluginTypedDocumentNode } from '.'; + +describe('plugin typed document node test', () => { + it('generates correct apollo plugin from the schema', () => { + const schema = ` +type Query{ + people: [String!]! +} +type Mutation{ + register(name: String!): String! +} +type Subscription{ + registrations: [String!]! +} +schema{ + query: Query + mutation: Mutation + subscription: Subscription +} +`; + const tree = Parser.parse(schema); + const tdnResult = pluginTypedDocumentNode({ tree }); + expect(tdnResult.ts).toContain(`TODO`); + }); +}); diff --git a/src/plugins/typed-document-node/index.ts b/src/plugins/typed-document-node/index.ts new file mode 100644 index 00000000..32b3dbbf --- /dev/null +++ b/src/plugins/typed-document-node/index.ts @@ -0,0 +1,74 @@ +import { OperationType, ParserTree } from 'graphql-js-tree'; + +const pluginApolloOps = ({ queryName, operation }: { queryName: string; operation: OperationType | 'LazyQuery' }) => { + const capitalized = operation[0].toUpperCase() + operation.slice(1); + const zeusOperation = operation === 'LazyQuery' ? OperationType.query : operation; + + return { + queryName, + operation, + ts: `export function useTyped${capitalized}( + ${operation}: Z | ValueTypes[O], + options?: ${capitalized}HookOptions>, + operationName?: string, +) { + return use${capitalized}>(gql(Zeus("${zeusOperation}",${operation}, operationName)), options); +}`, + }; +}; + +export const pluginTypedDocumentNode = ({ tree, esModule }: { tree: ParserTree; esModule?: boolean }) => { + const operationNodes = tree.nodes.filter((n) => n.type.operations); + const opsFunctions = operationNodes.flatMap((n) => + n.type.operations!.map((o) => pluginApolloOps({ queryName: n.name, operation: o })), + ); + for (const [index, o] of opsFunctions.entries()) { + if (o.operation === OperationType.query) { + opsFunctions.splice(index + 1, 0, pluginApolloOps({ queryName: o.queryName, operation: 'LazyQuery' })); + break; + } + } + const o = opsFunctions.reduce, 'ts'>>( + (a, b) => { + a.ts = [a.ts, b.ts].join('\n'); + return a; + }, + { ts: '' }, + ); + const capitalizedOps = opsFunctions.map((o) => o.operation[0].toUpperCase() + o.operation.slice(1)); + const jsDefsImports: string[] = []; + if (capitalizedOps.includes('LazyQuery')) { + jsDefsImports.push('QueryTuple'); + } + if (capitalizedOps.includes('Query')) { + jsDefsImports.push('QueryResult'); + } + if (capitalizedOps.includes('Mutation')) { + jsDefsImports.push('MutationTuple'); + } + return { + ts: `/* eslint-disable */ + +import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; +import gql from 'graphql-tag'; +import { GraphQLTypes, InputType, Selectors, ValueTypes, Zeus } from './index${esModule ? '.js' : ''}'; + +export const constructQuery = (q: T) => { + const gqlString = Zeus.query(q); + const selector = Selectors.query(q); + type InferredResponseType = InputType; + return gql(gqlString) as TypedDocumentNode; +}; + +const drawCardDocument = constructQuery({ + drawCard: { + Attack: true, + Children: true, + id: true, + }, +}); + +${o.ts} +`, + }; +}; From bc1564a12768bb6138ca9f44d4ac1d6e8de557a7 Mon Sep 17 00:00:00 2001 From: Gjorgji Kjosev Date: Sat, 19 Mar 2022 18:50:21 +0000 Subject: [PATCH 2/7] wip --- .../src/zeus/typedDocumentNode.ts | 53 +++++++++++++---- src/CLI/index.ts | 1 - src/plugins/typed-document-node/index.ts | 58 +++++-------------- 3 files changed, 55 insertions(+), 57 deletions(-) diff --git a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts index 8a933798..3c06f1db 100644 --- a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts +++ b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts @@ -2,22 +2,42 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; -// Note: turns out the generated query does NOT have any params :| -// type ParamsType = SRC extends [infer Params, infer Result] ? Params : { [K in keyof SRC]: ParamsType }; - -// type PayloadType = MapType>; - -export function createTypedQuery( - query: Z, +export function tq( + query: Z | ValueTypes['query_root'], ): TypedDocumentNode, {}> { - const zeusQuery = Zeus('query', query); - const gqlQuery = gql(zeusQuery); + return gql(Zeus('query', query)); +} + +export function tm( + mutation: Z, +): TypedDocumentNode, {}> { + return gql(Zeus('mutation', mutation)); +} - return gqlQuery; +export function ts( + mutation: Z, +): TypedDocumentNode, {}> { + return gql(Zeus('mutation', mutation)); } +tq({ + aggregateBookings: [ + { + where: { + bookerName: { _eq: '5' }, + }, + }, + { + aggregate: { + avg: { + nights: true, + }, + }, + }, + ], +}); // Example -const userMemberships = createTypedQuery({ +const userMemberships = tq({ user: [ { id: 'x', @@ -34,3 +54,14 @@ const userMemberships = createTypedQuery({ }, ], }); + +const mutate = tm({ + insertBooking: [ + { + object: {}, + }, + { + bookerName: true, + }, + ], +}); diff --git a/src/CLI/index.ts b/src/CLI/index.ts index 717be740..fb0cbd38 100644 --- a/src/CLI/index.ts +++ b/src/CLI/index.ts @@ -1,6 +1,5 @@ #!/usr/bin/env node import * as yargs from 'yargs'; -import { boolean } from 'yargs'; import { CLI } from './CLIClass'; const args = yargs .usage( diff --git a/src/plugins/typed-document-node/index.ts b/src/plugins/typed-document-node/index.ts index 32b3dbbf..e22bef9f 100644 --- a/src/plugins/typed-document-node/index.ts +++ b/src/plugins/typed-document-node/index.ts @@ -1,72 +1,40 @@ import { OperationType, ParserTree } from 'graphql-js-tree'; -const pluginApolloOps = ({ queryName, operation }: { queryName: string; operation: OperationType | 'LazyQuery' }) => { - const capitalized = operation[0].toUpperCase() + operation.slice(1); - const zeusOperation = operation === 'LazyQuery' ? OperationType.query : operation; +const createOperationFunction = ({ queryName, operation }: { queryName: string; operation: OperationType }) => { + const firstLetter = operation[0]; return { queryName, operation, - ts: `export function useTyped${capitalized}( - ${operation}: Z | ValueTypes[O], - options?: ${capitalized}HookOptions>, - operationName?: string, -) { - return use${capitalized}>(gql(Zeus("${zeusOperation}",${operation}, operationName)), options); -}`, + ts: `export function t${firstLetter}( + ${operation}: Z | ValueTypes["${queryName}"], +): TypedDocumentNode, {}> { + return gql(Zeus("${operation}", ${operation})); +} +`, }; }; export const pluginTypedDocumentNode = ({ tree, esModule }: { tree: ParserTree; esModule?: boolean }) => { const operationNodes = tree.nodes.filter((n) => n.type.operations); const opsFunctions = operationNodes.flatMap((n) => - n.type.operations!.map((o) => pluginApolloOps({ queryName: n.name, operation: o })), + n.type.operations!.map((o) => createOperationFunction({ queryName: n.name, operation: o })), ); - for (const [index, o] of opsFunctions.entries()) { - if (o.operation === OperationType.query) { - opsFunctions.splice(index + 1, 0, pluginApolloOps({ queryName: o.queryName, operation: 'LazyQuery' })); - break; - } - } - const o = opsFunctions.reduce, 'ts'>>( + + const o = opsFunctions.reduce( (a, b) => { a.ts = [a.ts, b.ts].join('\n'); return a; }, { ts: '' }, ); - const capitalizedOps = opsFunctions.map((o) => o.operation[0].toUpperCase() + o.operation.slice(1)); - const jsDefsImports: string[] = []; - if (capitalizedOps.includes('LazyQuery')) { - jsDefsImports.push('QueryTuple'); - } - if (capitalizedOps.includes('Query')) { - jsDefsImports.push('QueryResult'); - } - if (capitalizedOps.includes('Mutation')) { - jsDefsImports.push('MutationTuple'); - } + return { ts: `/* eslint-disable */ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; -import { GraphQLTypes, InputType, Selectors, ValueTypes, Zeus } from './index${esModule ? '.js' : ''}'; - -export const constructQuery = (q: T) => { - const gqlString = Zeus.query(q); - const selector = Selectors.query(q); - type InferredResponseType = InputType; - return gql(gqlString) as TypedDocumentNode; -}; - -const drawCardDocument = constructQuery({ - drawCard: { - Attack: true, - Children: true, - id: true, - }, -}); +import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; ${o.ts} `, From 3aa2175ede4279e0407509cc1ca582260d7d8e62 Mon Sep 17 00:00:00 2001 From: Gjorgji Kjosev Date: Sat, 19 Mar 2022 19:00:50 +0000 Subject: [PATCH 3/7] tests --- src/plugins/typed-document-node/index.spec.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/plugins/typed-document-node/index.spec.ts b/src/plugins/typed-document-node/index.spec.ts index 6c5877cf..9b4668ae 100644 --- a/src/plugins/typed-document-node/index.spec.ts +++ b/src/plugins/typed-document-node/index.spec.ts @@ -21,6 +21,31 @@ schema{ `; const tree = Parser.parse(schema); const tdnResult = pluginTypedDocumentNode({ tree }); - expect(tdnResult.ts).toContain(`TODO`); + expect(tdnResult.ts).toContain(` +import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; +import gql from 'graphql-tag'; +import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; +`); + + expect(tdnResult.ts).toContain(`export function tq( + query: Z | ValueTypes["Query"], +): TypedDocumentNode, {}> { + return gql(Zeus("query", query)); +}`); + + expect(tdnResult.ts).toContain(` +export function tm( + mutation: Z | ValueTypes["Mutation"], +): TypedDocumentNode, {}> { + return gql(Zeus("mutation", mutation)); +} +`); + expect(tdnResult.ts).toContain(` +export function ts( + subscription: Z | ValueTypes["Subscription"], +): TypedDocumentNode, {}> { + return gql(Zeus("subscription", subscription)); +} +`); }); }); From 7dddd5b81fee918586e832e48f06080fb7ea0be1 Mon Sep 17 00:00:00 2001 From: Gjorgji Kjosev Date: Thu, 21 Apr 2022 16:25:05 +0100 Subject: [PATCH 4/7] WIPWIP --- .../src/zeus/typedDocumentNode.ts | 47 +++++- .../src/zeus/typedDocumentNode.ts | 95 ++++++++++++ yarn.lock | 139 ++---------------- 3 files changed, 145 insertions(+), 136 deletions(-) create mode 100644 examples/typescript-node/src/zeus/typedDocumentNode.ts diff --git a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts index 3c06f1db..33b8cf3b 100644 --- a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts +++ b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts @@ -1,11 +1,41 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; -import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; +import { GraphQLTypes, InputType, ValueTypes, Zeus, $ } from './index'; -export function tq( - query: Z | ValueTypes['query_root'], -): TypedDocumentNode, {}> { - return gql(Zeus('query', query)); +// const $$ = new Proxy( +// {}, +// { +// get(target, propName, receiver) { +// return 'ZEUS_VAR$' + propName.toString(); +// }, +// }, +// ) as any as X + +const $$ = (name: Name) => { + return ('ZEUS_VAR$' + name) as any as Variable; +}; + +type X = { [Key in K]: Variable }; + +type Variable = { + __zeus_name: Name; + __zeus_type: T; +}; + +type VariablizedInput = T extends string | number | Array + ? T | Variable + : T | Variable | { [K in keyof T]: VariablizedInput }; + +type VariablizedQuery = T extends [infer Input, infer Output] + ? [VariablizedInput, VariablizedQuery] + : { [K in keyof T]: VariablizedQuery }; + +type ConnValue = ValueTypes['connection']; +type Test = VariablizedQuery; +export function tq( + query: VariablizedQuery, +): TypedDocumentNode, VariablesType> { + return gql(Zeus('query', query as any)); } export function tm( @@ -24,7 +54,7 @@ tq({ aggregateBookings: [ { where: { - bookerName: { _eq: '5' }, + bookerName: { _eq: $$('bookerName') }, }, }, { @@ -36,16 +66,17 @@ tq({ }, ], }); + // Example const userMemberships = tq({ user: [ { - id: 'x', + id: $$('id'), }, { memberships: [ { - limit: 5, + limit: $$('limit'), }, { role: true, diff --git a/examples/typescript-node/src/zeus/typedDocumentNode.ts b/examples/typescript-node/src/zeus/typedDocumentNode.ts new file mode 100644 index 00000000..d00b2e0a --- /dev/null +++ b/examples/typescript-node/src/zeus/typedDocumentNode.ts @@ -0,0 +1,95 @@ +import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; +import gql from 'graphql-tag'; +import { GraphQLTypes, InputType, ValueTypes, Zeus, $ } from './index'; + +// const $$ = new Proxy( +// {}, +// { +// get(target, propName, receiver) { +// return 'ZEUS_VAR$' + propName.toString(); +// }, +// }, +// ) as any as X + +const $$ = (name: Name) => { + return ('ZEUS_VAR$' + name) as any as Variable; +}; + +type X = { [Key in K]: Variable }; + +type Variable = { + __zeus_name: Name; + __zeus_type: T; +}; + +type VariablizedInput = T extends string | number | Array + ? T | Variable + : T | Variable | { [K in keyof T]: VariablizedInput }; + +type VariablizedQuery = T extends [infer Input, infer Output] + ? [VariablizedInput, VariablizedQuery] + : { [K in keyof T]: VariablizedQuery }; + +type ExtractVariables = C extends Variable + ? { [key in VName]: VType } + : { [K in keyof C]: ExtractVariables }; + +export function tq>( + query: ResultType, +): TypedDocumentNode, VariablesType> { + return gql(Zeus('query', query as any)); +} + +export function tm( + mutation: Z, +): TypedDocumentNode, {}> { + return gql(Zeus('mutation', mutation)); +} + +export function ts( + mutation: Z, +): TypedDocumentNode, {}> { + return gql(Zeus('mutation', mutation)); +} + +const q = tq({ + cardById: [ + { + cardId: $$('test'), + }, + { + Attack: true, + Defense: true, + }, + ], +}); + +// Example +// const userMemberships = tq({ +// user: [ +// { +// id: $$('id'), +// }, +// { +// memberships: [ +// { +// limit: $$('limit'), +// }, +// { +// role: true, +// }, +// ], +// }, +// ], +// }); + +// const mutate = tm({ +// insertBooking: [ +// { +// object: {}, +// }, +// { +// bookerName: true, +// }, +// ], +// }); diff --git a/yarn.lock b/yarn.lock index 31de5bd1..95108b4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,24 +2,6 @@ # yarn lockfile v1 -"@apollo/client@^3.4.7": - version "3.4.15" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.4.15.tgz#0aa05ff2bb54092919b501ef348ade6330911670" - integrity sha512-CnlT9i7TgHagkKQNvti81A9KcbIMqgpUPGJJL6bg5spTsB2R/5J6E7qiPcMvXuuXwR2xe4FmE4Ey4HizStb8Hg== - dependencies: - "@graphql-typed-document-node/core" "^3.0.0" - "@wry/context" "^0.6.0" - "@wry/equality" "^0.5.0" - "@wry/trie" "^0.3.0" - graphql-tag "^2.12.3" - hoist-non-react-statics "^3.3.2" - optimism "^0.16.1" - prop-types "^15.7.2" - symbol-observable "^4.0.0" - ts-invariant "^0.9.0" - tslib "^2.3.0" - zen-observable-ts "~1.1.0" - "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -1134,11 +1116,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@graphql-typed-document-node/core@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950" - integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg== - "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -1527,11 +1504,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/zen-observable@0.8.3": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" - integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== - "@typescript-eslint/eslint-plugin@^4.15.0": version "4.32.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.32.0.tgz#46d2370ae9311092f2a6f7246d28357daf2d4e89" @@ -1602,27 +1574,6 @@ "@typescript-eslint/types" "4.32.0" eslint-visitor-keys "^2.0.0" -"@wry/context@^0.6.0": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.1.tgz#c3c29c0ad622adb00f6a53303c4f965ee06ebeb2" - integrity sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw== - dependencies: - tslib "^2.3.0" - -"@wry/equality@^0.5.0": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.2.tgz#72c8a7a7d884dff30b612f4f8464eba26c080e73" - integrity sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA== - dependencies: - tslib "^2.3.0" - -"@wry/trie@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.3.1.tgz#2279b790f15032f8bcea7fc944d27988e5b3b139" - integrity sha512-WwB53ikYudh9pIorgxrkHKrQZcCqNM/Q/bDzZBffEaGUKGuHrRb3zZUT9Sh2qw9yogC7SsdRmQ1ER0pqvd3bfw== - dependencies: - tslib "^2.3.0" - JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -3491,20 +3442,13 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -graphql-js-tree@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/graphql-js-tree/-/graphql-js-tree-0.0.1.tgz#b3744b5a0680ade02d928deb4e81d0c627dd21f2" - integrity sha512-QbUkZBXAGMwWit0FyH+R7LNpj7+RGac4KaaEqI7o451wqrTzh1MC96+xZyCHuCbCzcLQZaLBh7r7MhwuIz3M7w== +graphql-js-tree@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/graphql-js-tree/-/graphql-js-tree-0.0.3.tgz#280a9e7a6acc951567470daafc904624cfa21d62" + integrity sha512-X/AhG+XRmRHCYQRLEbQxdODF16kE9w0b5lDMLJVSpcQlEum/+QeWuHky9hRGXw1apkREQdEtbZ5QXbr2t8v8/A== dependencies: graphql "^15.4.0" -graphql-tag@^2.12.3: - version "2.12.5" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f" - integrity sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ== - dependencies: - tslib "^2.1.0" - graphql@*, graphql@^15.4.0: version "15.6.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.6.0.tgz#e69323c6a9780a1a4b9ddf7e35ca8904bb04df02" @@ -3608,13 +3552,6 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hoist-non-react-statics@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" @@ -4487,7 +4424,7 @@ jest@^25.2.4: import-local "^3.0.2" jest-cli "^25.5.4" -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -4783,13 +4720,6 @@ longest@^2.0.1: resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8" integrity sha1-eB4YMpaqlPbU2RbcM10NF676I/g= -loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" @@ -5191,11 +5121,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -5284,14 +5209,6 @@ opencollective-postinstall@^2.0.2: resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== -optimism@^0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.1.tgz#7c8efc1f3179f18307b887e18c15c5b7133f6e7d" - integrity sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg== - dependencies: - "@wry/context" "^0.6.0" - "@wry/trie" "^0.3.0" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -5587,15 +5504,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.8.1" - psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -5639,7 +5547,7 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.12.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -6434,11 +6342,6 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" -symbol-observable@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" - integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== - symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -6583,13 +6486,6 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -ts-invariant@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.9.3.tgz#4b41e0a80c2530a56ce4b8fd4e14183aaac0efa8" - integrity sha512-HinBlTbFslQI0OHP07JLsSXPibSegec6r9ai5xxq/qHYCsIQbzpymLpDhAUsnXcSrDEcd0L62L8vsOEdzM0qlA== - dependencies: - tslib "^2.1.0" - ts-jest@^26.5.1: version "26.5.6" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.6.tgz#c32e0746425274e1dfe333f43cd3c800e014ec35" @@ -6628,7 +6524,7 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.1.0, tslib@^2.3.0: +tslib@^2: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -6722,10 +6618,10 @@ typescript-transform-paths@^2.0.0: dependencies: minimatch "^3.0.4" -typescript@^4.1.2: - version "4.4.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" - integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== +typescript@^4.5.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" + integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== unbox-primitive@^1.0.1: version "1.0.1" @@ -7113,16 +7009,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zen-observable-ts@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83" - integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA== - dependencies: - "@types/zen-observable" "0.8.3" - zen-observable "0.8.15" - -zen-observable@0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" - integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== From d7d122e8ef3cbed329bafedcacb003e481235e8e Mon Sep 17 00:00:00 2001 From: Gjorgji Kjosev Date: Thu, 21 Apr 2022 23:43:08 +0100 Subject: [PATCH 5/7] WIIPWIPWIP --- .../src/zeus/typedDocumentNode.ts | 16 ++++----- .../src/zeus/typedDocumentNode.ts | 36 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts index 33b8cf3b..16825414 100644 --- a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts +++ b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts @@ -2,14 +2,14 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; import { GraphQLTypes, InputType, ValueTypes, Zeus, $ } from './index'; -// const $$ = new Proxy( -// {}, -// { -// get(target, propName, receiver) { -// return 'ZEUS_VAR$' + propName.toString(); -// }, -// }, -// ) as any as X +const $$ = new Proxy( + {}, + { + get(target, propName, receiver) { + return 'ZEUS_VAR$' + propName.toString(); + }, + }, +); const $$ = (name: Name) => { return ('ZEUS_VAR$' + name) as any as Variable; diff --git a/examples/typescript-node/src/zeus/typedDocumentNode.ts b/examples/typescript-node/src/zeus/typedDocumentNode.ts index d00b2e0a..f05b87ea 100644 --- a/examples/typescript-node/src/zeus/typedDocumentNode.ts +++ b/examples/typescript-node/src/zeus/typedDocumentNode.ts @@ -1,22 +1,11 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; -import { GraphQLTypes, InputType, ValueTypes, Zeus, $ } from './index'; +import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; -// const $$ = new Proxy( -// {}, -// { -// get(target, propName, receiver) { -// return 'ZEUS_VAR$' + propName.toString(); -// }, -// }, -// ) as any as X - -const $$ = (name: Name) => { - return ('ZEUS_VAR$' + name) as any as Variable; +const $$ = (name: Name) => { + return ('ZEUS_VAR$' + name) as any as Variable; }; -type X = { [Key in K]: Variable }; - type Variable = { __zeus_name: Name; __zeus_type: T; @@ -30,13 +19,26 @@ type VariablizedQuery = T extends [infer Input, infer Output] ? [VariablizedInput, VariablizedQuery] : { [K in keyof T]: VariablizedQuery }; -type ExtractVariables = C extends Variable +type ExtractVariables = Query extends Variable ? { [key in VName]: VType } - : { [K in keyof C]: ExtractVariables }; + : Query extends [infer Inputs, infer Outputs] + ? Intersectionize<{ inputs: ExtractVariables; outputs: ExtractVariables }> + : Query extends string | number | boolean + ? {} + : Intersectionize<{ [K in keyof Query]: ExtractVariables }>; + +type Intersectionize = UnionToIntersection; + +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + +// Unit testing: +// type TestExtractor = ExtractVariables<{ +// cardById: [{ cardId: Variable }, { attack: true; defense: true }]; +// }>; export function tq>( query: ResultType, -): TypedDocumentNode, VariablesType> { +): TypedDocumentNode, ExtractVariables> { return gql(Zeus('query', query as any)); } From b47af44a8104c66dff19b37eb38dce6c1677881f Mon Sep 17 00:00:00 2001 From: Gjorgji Kjosev Date: Fri, 22 Apr 2022 18:14:45 +0100 Subject: [PATCH 6/7] feat: fully typed variable support --- .../src/zeus/typedDocumentNode.ts | 114 +++++------------- .../src/zeus/typedDocumentNode.ts | 98 ++++----------- src/plugins/typed-document-node/index.spec.ts | 28 +++-- src/plugins/typed-document-node/index.ts | 37 +++++- 4 files changed, 105 insertions(+), 172 deletions(-) diff --git a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts index 16825414..0ab9ab34 100644 --- a/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts +++ b/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts @@ -1,98 +1,48 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; -import { GraphQLTypes, InputType, ValueTypes, Zeus, $ } from './index'; +import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; -const $$ = new Proxy( - {}, - { - get(target, propName, receiver) { - return 'ZEUS_VAR$' + propName.toString(); - }, - }, -); - -const $$ = (name: Name) => { - return ('ZEUS_VAR$' + name) as any as Variable; +type Variable = { + ' __zeus_name': Name; + ' __zeus_type': T; }; -type X = { [Key in K]: Variable }; +type QueryInputWithVariables = T extends string | number | Array + ? Variable | T + : Variable | { [K in keyof T]: QueryInputWithVariables } | T; -type Variable = { - __zeus_name: Name; - __zeus_type: T; -}; +type QueryWithVariables = T extends [infer Input, infer Output] + ? [QueryInputWithVariables, QueryWithVariables] + : { [K in keyof T]: QueryWithVariables }; + +type ExtractVariables = Query extends Variable + ? { [key in VName]: VType } + : Query extends [infer Inputs, infer Outputs] + ? ExtractVariables & ExtractVariables + : Query extends string | number | boolean + ? {} + : UnionToIntersection<{ [K in keyof Query]: ExtractVariables }[keyof Query]>; -type VariablizedInput = T extends string | number | Array - ? T | Variable - : T | Variable | { [K in keyof T]: VariablizedInput }; +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; -type VariablizedQuery = T extends [infer Input, infer Output] - ? [VariablizedInput, VariablizedQuery] - : { [K in keyof T]: VariablizedQuery }; +export const $ = (name: Name) => { + return ('ZEUS_VAR$' + name) as any as Variable; +}; -type ConnValue = ValueTypes['connection']; -type Test = VariablizedQuery; -export function tq( - query: VariablizedQuery, -): TypedDocumentNode, VariablesType> { +export function query>( + query: Z, +): TypedDocumentNode, ExtractVariables> { return gql(Zeus('query', query as any)); } -export function tm( +export function mutation>( mutation: Z, -): TypedDocumentNode, {}> { - return gql(Zeus('mutation', mutation)); +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus('mutation', mutation as any)); } -export function ts( - mutation: Z, -): TypedDocumentNode, {}> { - return gql(Zeus('mutation', mutation)); +export function subscription>( + subscription: Z, +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus('subscription', subscription as any)); } - -tq({ - aggregateBookings: [ - { - where: { - bookerName: { _eq: $$('bookerName') }, - }, - }, - { - aggregate: { - avg: { - nights: true, - }, - }, - }, - ], -}); - -// Example -const userMemberships = tq({ - user: [ - { - id: $$('id'), - }, - { - memberships: [ - { - limit: $$('limit'), - }, - { - role: true, - }, - ], - }, - ], -}); - -const mutate = tm({ - insertBooking: [ - { - object: {}, - }, - { - bookerName: true, - }, - ], -}); diff --git a/examples/typescript-node/src/zeus/typedDocumentNode.ts b/examples/typescript-node/src/zeus/typedDocumentNode.ts index f05b87ea..0abdca25 100644 --- a/examples/typescript-node/src/zeus/typedDocumentNode.ts +++ b/examples/typescript-node/src/zeus/typedDocumentNode.ts @@ -1,97 +1,51 @@ +/* eslint-disable */ +/* eslint-disable */ + import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; -const $$ = (name: Name) => { - return ('ZEUS_VAR$' + name) as any as Variable; -}; - type Variable = { - __zeus_name: Name; - __zeus_type: T; + ' __zeus_name': Name; + ' __zeus_type': T; }; -type VariablizedInput = T extends string | number | Array - ? T | Variable - : T | Variable | { [K in keyof T]: VariablizedInput }; +type QueryInputWithVariables = T extends string | number | Array + ? Variable | T + : Variable | { [K in keyof T]: QueryInputWithVariables } | T; -type VariablizedQuery = T extends [infer Input, infer Output] - ? [VariablizedInput, VariablizedQuery] - : { [K in keyof T]: VariablizedQuery }; +type QueryWithVariables = T extends [infer Input, infer Output] + ? [QueryInputWithVariables, QueryWithVariables] + : { [K in keyof T]: QueryWithVariables }; type ExtractVariables = Query extends Variable ? { [key in VName]: VType } : Query extends [infer Inputs, infer Outputs] - ? Intersectionize<{ inputs: ExtractVariables; outputs: ExtractVariables }> + ? ExtractVariables & ExtractVariables : Query extends string | number | boolean ? {} - : Intersectionize<{ [K in keyof Query]: ExtractVariables }>; - -type Intersectionize = UnionToIntersection; + : UnionToIntersection<{ [K in keyof Query]: ExtractVariables }[keyof Query]>; type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; -// Unit testing: -// type TestExtractor = ExtractVariables<{ -// cardById: [{ cardId: Variable }, { attack: true; defense: true }]; -// }>; +export const $ = (name: Name) => { + return ('ZEUS_VAR$' + name) as any as Variable; +}; -export function tq>( - query: ResultType, -): TypedDocumentNode, ExtractVariables> { +export function query>( + query: Z, +): TypedDocumentNode, ExtractVariables> { return gql(Zeus('query', query as any)); } -export function tm( +export function mutation>( mutation: Z, -): TypedDocumentNode, {}> { - return gql(Zeus('mutation', mutation)); +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus('mutation', mutation as any)); } -export function ts( - mutation: Z, -): TypedDocumentNode, {}> { - return gql(Zeus('mutation', mutation)); +export function subscription>( + subscription: Z, +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus('subscription', subscription as any)); } - -const q = tq({ - cardById: [ - { - cardId: $$('test'), - }, - { - Attack: true, - Defense: true, - }, - ], -}); - -// Example -// const userMemberships = tq({ -// user: [ -// { -// id: $$('id'), -// }, -// { -// memberships: [ -// { -// limit: $$('limit'), -// }, -// { -// role: true, -// }, -// ], -// }, -// ], -// }); - -// const mutate = tm({ -// insertBooking: [ -// { -// object: {}, -// }, -// { -// bookerName: true, -// }, -// ], -// }); diff --git a/src/plugins/typed-document-node/index.spec.ts b/src/plugins/typed-document-node/index.spec.ts index 9b4668ae..be8cd087 100644 --- a/src/plugins/typed-document-node/index.spec.ts +++ b/src/plugins/typed-document-node/index.spec.ts @@ -2,7 +2,7 @@ import { Parser } from 'graphql-js-tree'; import { pluginTypedDocumentNode } from '.'; describe('plugin typed document node test', () => { - it('generates correct apollo plugin from the schema', () => { + it('generates correct typed document node plugin from the schema', () => { const schema = ` type Query{ people: [String!]! @@ -21,30 +21,32 @@ schema{ `; const tree = Parser.parse(schema); const tdnResult = pluginTypedDocumentNode({ tree }); + expect(tdnResult.ts).toContain(` import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; `); - expect(tdnResult.ts).toContain(`export function tq( - query: Z | ValueTypes["Query"], -): TypedDocumentNode, {}> { - return gql(Zeus("query", query)); + expect(tdnResult.ts).toContain(` +export function query>( + query: Z, +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus("query", query as any)); }`); expect(tdnResult.ts).toContain(` -export function tm( - mutation: Z | ValueTypes["Mutation"], -): TypedDocumentNode, {}> { - return gql(Zeus("mutation", mutation)); +export function mutation>( + mutation: Z, +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus("mutation", mutation as any)); } `); expect(tdnResult.ts).toContain(` -export function ts( - subscription: Z | ValueTypes["Subscription"], -): TypedDocumentNode, {}> { - return gql(Zeus("subscription", subscription)); +export function subscription>( + subscription: Z, +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus("subscription", subscription as any)); } `); }); diff --git a/src/plugins/typed-document-node/index.ts b/src/plugins/typed-document-node/index.ts index e22bef9f..4ed604ca 100644 --- a/src/plugins/typed-document-node/index.ts +++ b/src/plugins/typed-document-node/index.ts @@ -1,15 +1,15 @@ import { OperationType, ParserTree } from 'graphql-js-tree'; const createOperationFunction = ({ queryName, operation }: { queryName: string; operation: OperationType }) => { - const firstLetter = operation[0]; + // const firstLetter = operation[0]; return { queryName, operation, - ts: `export function t${firstLetter}( - ${operation}: Z | ValueTypes["${queryName}"], -): TypedDocumentNode, {}> { - return gql(Zeus("${operation}", ${operation})); + ts: `export function ${operation}>( + ${operation}: Z, +): TypedDocumentNode, ExtractVariables> { + return gql(Zeus("${operation}", ${operation} as any)); } `, }; @@ -36,6 +36,33 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag'; import { GraphQLTypes, InputType, ValueTypes, Zeus } from './index'; +type Variable = { + ' __zeus_name': Name; + ' __zeus_type': T; +}; + +type QueryInputWithVariables = T extends string | number | Array + ? Variable | T + : Variable | { [K in keyof T]: QueryInputWithVariables } | T; + +type QueryWithVariables = T extends [infer Input, infer Output] + ? [QueryInputWithVariables, QueryWithVariables] + : { [K in keyof T]: QueryWithVariables }; + +type ExtractVariables = Query extends Variable + ? { [key in VName]: VType } + : Query extends [infer Inputs, infer Outputs] + ? ExtractVariables & ExtractVariables + : Query extends string | number | boolean + ? {} + : UnionToIntersection<{ [K in keyof Query]: ExtractVariables }[keyof Query]>; + +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + +export const $ = (name: Name) => { + return ('ZEUS_VAR$' + name) as any as Variable; +}; + ${o.ts} `, }; From f6246447a75e6d463658fdfb129b987e9ddd5dac Mon Sep 17 00:00:00 2001 From: Gjorgji Kjosev Date: Fri, 22 Apr 2022 18:44:31 +0100 Subject: [PATCH 7/7] doc: add docs for the typed document node plugin --- doc/src/markdown/plugins/typedDocumentNode.md | 76 +++++++++++++++++++ .../src/zeus/typedDocumentNode.ts | 1 - 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 doc/src/markdown/plugins/typedDocumentNode.md diff --git a/doc/src/markdown/plugins/typedDocumentNode.md b/doc/src/markdown/plugins/typedDocumentNode.md new file mode 100644 index 00000000..97517df4 --- /dev/null +++ b/doc/src/markdown/plugins/typedDocumentNode.md @@ -0,0 +1,76 @@ +--- +link: plugins/typedDocumentNode +title: TypedDocumentNode +order: 4 +category: Plugins +--- + +## Usage with Typed Document Node + +Zeus can generate builders for [`TypedDocumentNode`][typed-document-node], a type-safe query +representation understood by most GraphQL clients (including Apollo, urql etc) by adding the +`--typedDocumentNode` flag to the CLI. + +### Generate Type-Safe Zeus Schema And TypedDocumentNode query builders + +```sh +$ zeus schema.graphql ./ --typedDocumentNode +# typedDocumentNode.ts file with typed document node builders is now in the output destination +``` + +### TypedDocumentNode + Apollo Client useQuery examples + +The following example demonstrates usage with Apollo. Other clients should work similarly. + +```tsx +import { query, $ } from './zeus/typedDocumentNode'; +import { useQuery } from '@apollo/client'; + +const myQuery = query({ + // Get autocomplete here: + cardById: [ + { cardId: $('cardId') }, + { + Attack: true, + Defense: true, + }, + ], +}); + +const Main = () => { + const { data } = useQuery(myQuery, { + variables: { + // Get autocomplete and typechecking here: + cardId: someId + } + }); + // data response is typed + return
{data.drawCard.name}
; +}; +``` + +### Variable support + +Variables should be supported at any level of the query. Examples: + +```typescript +const userMemberships = query({ + user: [ + { id: $('id') }, + { memberships: [ + { limit: $('limit') }, + { role: true }, + ], + }, + ], +}); + +const mutate = mutation({ + insertBooking: [ + { object: $('booking') }, + { id: true, bookerName: true }, + ], +}); +``` + +[typed-document-node]: https://www.graphql-code-generator.com/plugins/typed-document-node \ No newline at end of file diff --git a/examples/typescript-node/src/zeus/typedDocumentNode.ts b/examples/typescript-node/src/zeus/typedDocumentNode.ts index 0abdca25..f520ca23 100644 --- a/examples/typescript-node/src/zeus/typedDocumentNode.ts +++ b/examples/typescript-node/src/zeus/typedDocumentNode.ts @@ -1,5 +1,4 @@ /* eslint-disable */ -/* eslint-disable */ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import gql from 'graphql-tag';