From e2c24b85518d53b7f78a7d14d5337b3a0c2d58bf Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 13:50:08 +0000 Subject: [PATCH 01/11] Reuse more helpers and add mutations where necessary in populateExchange Refactor some parts to avoid unnecessary immutability for micro-perf optimisations, and reduce size where possible by reusing helpers and swapping out immutability for mutability. Total minzipped size: 8.24kB -> 8.19kB --- src/populateExchange.ts | 93 +++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/src/populateExchange.ts b/src/populateExchange.ts index 6064fa0..0dcd496 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -1,5 +1,3 @@ -import { pipe, tap, map } from 'wonka'; -import { Exchange, Operation } from 'urql'; import { DocumentNode, buildClientSchema, @@ -19,6 +17,11 @@ import { isAbstractType, visit, } from 'graphql'; + +import { pipe, tap, map } from 'wonka'; +import { Exchange, Operation } from 'urql'; + +import { getName, getSelectionSet } from './ast'; import { invariant, warn } from './helpers/help'; interface ExchangeArgs { @@ -71,7 +74,6 @@ export const populateExchange = ({ } activeOperations.add(key); - if (parsedOperations.has(key)) { return; } @@ -86,13 +88,10 @@ export const populateExchange = ({ query, }); - userFragments = newFragments.reduce( - (state, fragment) => ({ - ...state, - [fragment.name.value]: fragment, - }), - userFragments - ); + userFragments = newFragments.reduce((state, fragment) => { + state[getName(fragment)] = fragment; + return state; + }, userFragments); typeFragments = newSelections.reduce((state, { selections, type }) => { const current = state[type] || []; @@ -115,19 +114,17 @@ export const populateExchange = ({ }, type, }; - return { - ...state, - [type]: [...current, entry], - }; + + current.push(entry); + state[type] = current; + return state; }, typeFragments); }; const handleIncomingTeardown = ({ key, operationName }: Operation) => { - if (operationName !== 'teardown') { - return; + if (operationName === 'teardown') { + activeOperations.delete(key); } - - activeOperations.delete(key); }; return ops$ => { @@ -167,23 +164,21 @@ export const extractSelectionsFromQuery = ({ schema, query, }: MakeFragmentsFromQueryArg) => { - let selections: { selections: SelectionSetNode; type: string }[] = []; - let fragments: FragmentDefinitionNode[] = []; + const selections: { selections: SelectionSetNode; type: string }[] = []; + const fragments: FragmentDefinitionNode[] = []; const typeInfo = new TypeInfo(schema); visit( query, visitWithTypeInfo(typeInfo, { Field: node => { - if (!node.selectionSet) { - return undefined; + if (node.selectionSet) { + const type = getTypeName(typeInfo); + selections.push({ selections: node.selectionSet, type }); } - - const type = getTypeName(typeInfo); - selections = [...selections, { selections: node.selectionSet, type }]; }, FragmentDefinition: node => { - fragments = [...fragments, node]; + fragments.push(node); }, }) ); @@ -207,23 +202,26 @@ export const addFragmentsToQuery = ({ }: AddFragmentsToQuery) => { const typeInfo = new TypeInfo(schema); const requiredUserFragments = new Set(); - let additionalFragments: Record = {}; + const additionalFragments: Record< + string, + FragmentDefinitionNode + > = Object.create(null); return visit( query, visitWithTypeInfo(typeInfo, { Field: { enter: node => { - if ( - !node.directives || - !node.directives.find(d => d.name.value === 'populate') - ) { + if (!node.directives) { return; } const directives = node.directives.filter( - d => d.name.value !== 'populate' + d => getName(d) !== 'populate' ); + if (directives.length === node.directives.length) { + return; + } const types = getTypes(schema, typeInfo); const newSelections = types.reduce((p, t) => { @@ -235,30 +233,29 @@ export const addFragmentsToQuery = ({ return [ ...p, ...typeFragments[t.name].map(({ fragment }) => { + const fragmentName = getName(fragment); + const usedFragments = getUsedFragments(fragment); + // Add used fragment for insertion at Document node - getUsedFragments(fragment).forEach(f => - requiredUserFragments.add(userFragments[f]) - ); + for (let i = 0, l = usedFragments.length; i < l; i++) { + requiredUserFragments.add(userFragments[usedFragments[i]]); + } // Add fragment for insertion at Document node - additionalFragments = { - ...additionalFragments, - [fragment.name.value]: fragment, - }; + additionalFragments[fragmentName] = fragment; return { kind: 'FragmentSpread', name: { kind: 'Name', - value: fragment.name.value, + value: fragmentName, }, } as const; }), ]; }, [] as FragmentSpreadNode[]); - const existingSelections = - (node.selectionSet && node.selectionSet.selections) || []; + const existingSelections = getSelectionSet(node); const selections = existingSelections.length + newSelections.length !== 0 @@ -317,11 +314,9 @@ const getTypes = (schema: GraphQLSchema, typeInfo: TypeInfo) => { return []; } - if (isInterfaceType(type) || isUnionType(type)) { - return schema.getPossibleTypes(type); - } - - return [type]; + return isInterfaceType(type) || isUnionType(type) + ? schema.getPossibleTypes(type) + : [type]; }; /** Get name of non-abstract type for adding to 'typeFragments'. */ @@ -343,11 +338,11 @@ const getTypeName = (t: TypeInfo) => { /** Get fragment names referenced by node. */ const getUsedFragments = (node: ASTNode) => { - let names: string[] = []; + const names: string[] = []; visit(node, { FragmentSpread: f => { - names = [...names, f.name.value]; + names.push(getName(f)); }, }); From 4567e2689a036fe63c5a1e4ab1d5f8eb3a1353ff Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 13:54:59 +0000 Subject: [PATCH 02/11] Remove usage of Array.from This isn't supported in IE11 and hence breaks compatibility if it isn't user polyfilled. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from --- src/populateExchange.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/populateExchange.ts b/src/populateExchange.ts index 0dcd496..bbdc623 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -201,7 +201,12 @@ export const addFragmentsToQuery = ({ userFragments, }: AddFragmentsToQuery) => { const typeInfo = new TypeInfo(schema); - const requiredUserFragments = new Set(); + + const requiredUserFragments: Record< + string, + FragmentDefinitionNode + > = Object.create(null); + const additionalFragments: Record< string, FragmentDefinitionNode @@ -238,7 +243,8 @@ export const addFragmentsToQuery = ({ // Add used fragment for insertion at Document node for (let i = 0, l = usedFragments.length; i < l; i++) { - requiredUserFragments.add(userFragments[usedFragments[i]]); + const name = usedFragments[i]; + requiredUserFragments[name] = userFragments[name]; } // Add fragment for insertion at Document node @@ -282,16 +288,12 @@ export const addFragmentsToQuery = ({ }, Document: { leave: node => { - return { - ...node, - definitions: [ - ...node.definitions, - ...Object.keys(additionalFragments).map( - k => additionalFragments[k] - ), // Object.values - ...Array.from(requiredUserFragments), - ], - }; + const definitions = [...node.definitions]; + for (const key in additionalFragments) + definitions.push(additionalFragments[key]); + for (const key in requiredUserFragments) + definitions.push(requiredUserFragments[key]); + return { ...node, definitions }; }, }, }) From 8b24fe3acf3b8293f7885e9c84920fc9d1f8ddb4 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 14:07:55 +0000 Subject: [PATCH 03/11] Remove another internal immutable change in addTypenamesToQuery --- src/populateExchange.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/populateExchange.ts b/src/populateExchange.ts index bbdc623..6c04498 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -235,8 +235,7 @@ export const addFragmentsToQuery = ({ return p; } - return [ - ...p, + p.push( ...typeFragments[t.name].map(({ fragment }) => { const fragmentName = getName(fragment); const usedFragments = getUsedFragments(fragment); @@ -257,8 +256,10 @@ export const addFragmentsToQuery = ({ value: fragmentName, }, } as const; - }), - ]; + }) + ); + + return p; }, [] as FragmentSpreadNode[]); const existingSelections = getSelectionSet(node); From bf78f1561117c8cec9806caa8f6a3a51d908a758 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 14:18:15 +0000 Subject: [PATCH 04/11] Extract unwrapType helper and enhance type-safety --- docs/help.md | 7 +++++-- src/ast/node.ts | 13 ++++++++++++- src/populateExchange.ts | 24 ++++++------------------ src/types.ts | 17 ++++++++++++++++- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/docs/help.md b/docs/help.md index 72d1601..9657f26 100644 --- a/docs/help.md +++ b/docs/help.md @@ -261,11 +261,14 @@ field somewhere, maybe due to a typo. ## (18) Invalid TypeInfo state -> Invalid TypeInfo state: Found an abstract type when none was expected. +> Invalid TypeInfo state: Found no flat schema type when one was expected. When you're using the populate exchange with an introspected schema, it will start collecting used fragments and selection sets on all of your queries. -This error may occur if it hits unexpected abstract types when doing so. +This error may occur if it hits unexpected types or inexistent types when doing so. + +Check whether your schema is up-to-date or whether you're using an invalid +field somewhere, maybe due to a typo. Please open an issue if it happens on a query that you expect to be supported by the `populateExchange`. diff --git a/src/ast/node.ts b/src/ast/node.ts index 2eaa923..f54d446 100644 --- a/src/ast/node.ts +++ b/src/ast/node.ts @@ -6,10 +6,13 @@ import { InlineFragmentNode, FieldNode, FragmentDefinitionNode, + GraphQLOutputType, Kind, + isListType, + isNonNullType, } from 'graphql'; -import { SelectionSet } from '../types'; +import { SelectionSet, GraphQLFlatType } from '../types'; /** Returns the name of a given node */ export const getName = (node: { name: NameNode }): string => node.name.value; @@ -40,3 +43,11 @@ export const isFieldNode = (node: SelectionNode): node is FieldNode => export const isInlineFragment = ( node: SelectionNode ): node is InlineFragmentNode => node.kind === Kind.INLINE_FRAGMENT; + +export const unwrapType = ( + type: null | undefined | GraphQLOutputType +): GraphQLFlatType | null => { + return type && (isListType(type) || isNonNullType(type)) + ? type.ofType + : type || null; +}; diff --git a/src/populateExchange.ts b/src/populateExchange.ts index 6c04498..8821531 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -9,8 +9,6 @@ import { IntrospectionQuery, FragmentSpreadNode, ASTNode, - isNonNullType, - isListType, isUnionType, isInterfaceType, isCompositeType, @@ -21,7 +19,7 @@ import { import { pipe, tap, map } from 'wonka'; import { Exchange, Operation } from 'urql'; -import { getName, getSelectionSet } from './ast'; +import { getName, getSelectionSet, unwrapType } from './ast'; import { invariant, warn } from './helpers/help'; interface ExchangeArgs { @@ -303,12 +301,7 @@ export const addFragmentsToQuery = ({ /** Get all possible types for node with TypeInfo. */ const getTypes = (schema: GraphQLSchema, typeInfo: TypeInfo) => { - const typeInfoType = typeInfo.getType(); - const type = - isListType(typeInfoType) || isNonNullType(typeInfoType) - ? typeInfoType.ofType - : typeInfoType; - + const type = unwrapType(typeInfo.getType()); if (!isCompositeType(type)) { warn( 'Invalid type: The type ` + type + ` is used with @populate but does not exist.', @@ -323,16 +316,11 @@ const getTypes = (schema: GraphQLSchema, typeInfo: TypeInfo) => { }; /** Get name of non-abstract type for adding to 'typeFragments'. */ -const getTypeName = (t: TypeInfo) => { - const typeInfoType = t.getType(); - const type = - isListType(typeInfoType) || isNonNullType(typeInfoType) - ? typeInfoType.ofType - : typeInfoType; - +const getTypeName = (typeInfo: TypeInfo) => { + const type = unwrapType(typeInfo.getType()); invariant( - !isAbstractType(type), - 'Invalid TypeInfo state: Found an abstract type when none was expected.', + type && !isAbstractType(type), + 'Invalid TypeInfo state: Found no flat schema type when one was expected.', 18 ); diff --git a/src/types.ts b/src/types.ts index acccc06..5af9241 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,13 @@ -import { DocumentNode, FragmentDefinitionNode, SelectionNode } from 'graphql'; +import { + DocumentNode, + FragmentDefinitionNode, + SelectionNode, + GraphQLScalarType, + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, +} from 'graphql'; // Helper types export type NullArray = Array; @@ -9,6 +18,12 @@ export interface Ref { // GraphQL helper types export type SelectionSet = ReadonlyArray; +export type GraphQLFlatType = + | GraphQLScalarType + | GraphQLObjectType + | GraphQLInterfaceType + | GraphQLUnionType + | GraphQLEnumType; export interface Fragments { [fragmentName: string]: void | FragmentDefinitionNode; } From a6fa0a28e893e256dc53791832ad9f813586c739 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 14:23:13 +0000 Subject: [PATCH 05/11] Remove userFragments/typeFragments reduce Removes additional closures that aren't needed. Addresses PR comments --- src/populateExchange.ts | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/populateExchange.ts b/src/populateExchange.ts index 8821531..6bca140 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -36,9 +36,9 @@ export const populateExchange = ({ /** List of operation keys that have not been torn down. */ const activeOperations = new Set(); /** Collection of fragments used by the user. */ - let userFragments: UserFragmentMap = Object.create(null); + const userFragments: UserFragmentMap = Object.create(null); /** Collection of type fragments. */ - let typeFragments: TypeFragmentMap = Object.create(null); + const typeFragments: TypeFragmentMap = Object.create(null); /** Handle mutation and inject selections + fragments. */ const handleIncomingMutation = (op: Operation) => { @@ -46,13 +46,12 @@ export const populateExchange = ({ return op; } - const activeSelections = Object.keys(typeFragments).reduce( - (state, key) => ({ - ...state, - [key]: state[key].filter(s => activeOperations.has(s.key)), - }), - typeFragments - ); + const activeSelections: TypeFragmentMap = Object.create(null); + for (const name in typeFragments) { + activeSelections[name] = typeFragments[name].filter(s => + activeOperations.has(s.key) + ); + } return { ...op, @@ -86,13 +85,14 @@ export const populateExchange = ({ query, }); - userFragments = newFragments.reduce((state, fragment) => { - state[getName(fragment)] = fragment; - return state; - }, userFragments); + for (let i = 0, l = newFragments.length; i < l; i++) { + const fragment = newFragments[i]; + userFragments[getName(fragment)] = fragment; + } - typeFragments = newSelections.reduce((state, { selections, type }) => { - const current = state[type] || []; + for (let i = 0, l = newSelections.length; i < l; i++) { + const { selections, type } = newSelections[i]; + const current = typeFragments[type] || []; const entry: TypeFragment = { key, fragment: { @@ -113,10 +113,9 @@ export const populateExchange = ({ type, }; + typeFragments[type] = current; current.push(entry); - state[type] = current; - return state; - }, typeFragments); + } }; const handleIncomingTeardown = ({ key, operationName }: Operation) => { From abfd97ad921d76fb65b7d4d76b4c375b5fe64b6f Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 14:27:55 +0000 Subject: [PATCH 06/11] Remove additional map for for-loop --- src/populateExchange.ts | 51 +++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/populateExchange.ts b/src/populateExchange.ts index 6bca140..d621798 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -170,8 +170,10 @@ export const extractSelectionsFromQuery = ({ visitWithTypeInfo(typeInfo, { Field: node => { if (node.selectionSet) { - const type = getTypeName(typeInfo); - selections.push({ selections: node.selectionSet, type }); + selections.push({ + selections: node.selectionSet, + type: getTypeName(typeInfo), + }); } }, FragmentDefinition: node => { @@ -232,29 +234,28 @@ export const addFragmentsToQuery = ({ return p; } - p.push( - ...typeFragments[t.name].map(({ fragment }) => { - const fragmentName = getName(fragment); - const usedFragments = getUsedFragments(fragment); - - // Add used fragment for insertion at Document node - for (let i = 0, l = usedFragments.length; i < l; i++) { - const name = usedFragments[i]; - requiredUserFragments[name] = userFragments[name]; - } - - // Add fragment for insertion at Document node - additionalFragments[fragmentName] = fragment; - - return { - kind: 'FragmentSpread', - name: { - kind: 'Name', - value: fragmentName, - }, - } as const; - }) - ); + for (let i = 0, l = typeFrags.length; i < l; i++) { + const { fragment } = typeFrags[i]; + const fragmentName = getName(fragment); + const usedFragments = getUsedFragments(fragment); + + // Add used fragment for insertion at Document node + for (let j = 0, l = usedFragments.length; j < l; j++) { + const name = usedFragments[j]; + requiredUserFragments[name] = userFragments[name]; + } + + // Add fragment for insertion at Document node + additionalFragments[fragmentName] = fragment; + + p.push({ + kind: 'FragmentSpread', + name: { + kind: 'Name', + value: fragmentName, + }, + }); + } return p; }, [] as FragmentSpreadNode[]); From adaf52a2e432ba872c1cec1e44ddeb55c7cedbad Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 14:57:07 +0000 Subject: [PATCH 07/11] Use Kind enum from graphql --- src/populateExchange.ts | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/populateExchange.ts b/src/populateExchange.ts index d621798..9813b58 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -8,11 +8,13 @@ import { GraphQLSchema, IntrospectionQuery, FragmentSpreadNode, + NameNode, ASTNode, isUnionType, isInterfaceType, isCompositeType, isAbstractType, + Kind, visit, } from 'graphql'; @@ -96,18 +98,12 @@ export const populateExchange = ({ const entry: TypeFragment = { key, fragment: { - kind: 'FragmentDefinition', + kind: Kind.FRAGMENT_DEFINITION, typeCondition: { - kind: 'NamedType', - name: { - kind: 'Name', - value: type, - }, - }, - name: { - kind: 'Name', - value: `${type}_PopulateFragment_${current.length}`, + kind: Kind.NAMED_TYPE, + name: nameNode(type), }, + name: nameNode(`${type}_PopulateFragment_${current.length}`), selectionSet: selections, }, type, @@ -249,11 +245,8 @@ export const addFragmentsToQuery = ({ additionalFragments[fragmentName] = fragment; p.push({ - kind: 'FragmentSpread', - name: { - kind: 'Name', - value: fragmentName, - }, + kind: Kind.FRAGMENT_SPREAD, + name: nameNode(fragmentName), }); } @@ -267,11 +260,8 @@ export const addFragmentsToQuery = ({ ? [...newSelections, ...existingSelections] : [ { - kind: 'Field', - name: { - kind: 'Name', - value: '__typename', - }, + kind: Kind.FIELD, + name: nameNode('__typename'), }, ]; @@ -279,7 +269,7 @@ export const addFragmentsToQuery = ({ ...node, directives, selectionSet: { - kind: 'SelectionSet', + kind: Kind.SELECTION_SET, selections, }, }; @@ -299,6 +289,11 @@ export const addFragmentsToQuery = ({ ); }; +const nameNode = (value: string): NameNode => ({ + kind: Kind.NAME, + value, +}); + /** Get all possible types for node with TypeInfo. */ const getTypes = (schema: GraphQLSchema, typeInfo: TypeInfo) => { const type = unwrapType(typeInfo.getType()); From b76cce97710e94869cb28d945202754a32fd4961 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 15:12:07 +0000 Subject: [PATCH 08/11] Refactor extractSelectionsFromQuery and addFragmentsToQuery --- src/populateExchange.ts | 102 +++++++++++++++------------------------- 1 file changed, 39 insertions(+), 63 deletions(-) diff --git a/src/populateExchange.ts b/src/populateExchange.ts index 9813b58..15381fa 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -4,7 +4,6 @@ import { visitWithTypeInfo, TypeInfo, FragmentDefinitionNode, - SelectionSetNode, GraphQLSchema, IntrospectionQuery, FragmentSpreadNode, @@ -57,12 +56,12 @@ export const populateExchange = ({ return { ...op, - query: addFragmentsToQuery({ + query: addFragmentsToQuery( schema, - typeFragments: activeSelections, - userFragments: userFragments, - query: op.query, - }), + op.query, + activeSelections, + userFragments + ), }; }; @@ -79,38 +78,23 @@ export const populateExchange = ({ parsedOperations.add(key); - const { - fragments: newFragments, - selections: newSelections, - } = extractSelectionsFromQuery({ + const [extractedFragments, newFragments] = extractSelectionsFromQuery( schema, - query, - }); + query + ); - for (let i = 0, l = newFragments.length; i < l; i++) { - const fragment = newFragments[i]; + for (let i = 0, l = extractedFragments.length; i < l; i++) { + const fragment = extractedFragments[i]; userFragments[getName(fragment)] = fragment; } - for (let i = 0, l = newSelections.length; i < l; i++) { - const { selections, type } = newSelections[i]; - const current = typeFragments[type] || []; - const entry: TypeFragment = { - key, - fragment: { - kind: Kind.FRAGMENT_DEFINITION, - typeCondition: { - kind: Kind.NAMED_TYPE, - name: nameNode(type), - }, - name: nameNode(`${type}_PopulateFragment_${current.length}`), - selectionSet: selections, - }, - type, - }; + for (let i = 0, l = newFragments.length; i < l; i++) { + const fragment = newFragments[i]; + const type = getName(fragment.typeCondition); + const current = typeFragments[type] || (typeFragments[type] = []); - typeFragments[type] = current; - current.push(entry); + (fragment as any).name.value += current.length; + current.push({ key, fragment }); } }; @@ -143,22 +127,15 @@ interface TypeFragment { key: number; /** Selection set. */ fragment: FragmentDefinitionNode; - /** Type of selection. */ - type: string; -} - -interface MakeFragmentsFromQueryArg { - schema: GraphQLSchema; - query: DocumentNode; } /** Gets typed selection sets and fragments from query */ -export const extractSelectionsFromQuery = ({ - schema, - query, -}: MakeFragmentsFromQueryArg) => { - const selections: { selections: SelectionSetNode; type: string }[] = []; - const fragments: FragmentDefinitionNode[] = []; +export const extractSelectionsFromQuery = ( + schema: GraphQLSchema, + query: DocumentNode +) => { + const extractedFragments: FragmentDefinitionNode[] = []; + const newFragments: FragmentDefinitionNode[] = []; const typeInfo = new TypeInfo(schema); visit( @@ -166,35 +143,34 @@ export const extractSelectionsFromQuery = ({ visitWithTypeInfo(typeInfo, { Field: node => { if (node.selectionSet) { - selections.push({ - selections: node.selectionSet, - type: getTypeName(typeInfo), + const type = getTypeName(typeInfo); + newFragments.push({ + kind: Kind.FRAGMENT_DEFINITION, + typeCondition: { + kind: Kind.NAMED_TYPE, + name: nameNode(type), + }, + name: nameNode(`${type}_PopulateFragment_`), + selectionSet: node.selectionSet, }); } }, FragmentDefinition: node => { - fragments.push(node); + extractedFragments.push(node); }, }) ); - return { selections, fragments }; + return [extractedFragments, newFragments]; }; -interface AddFragmentsToQuery { - schema: GraphQLSchema; - query: DocumentNode; - typeFragments: Record[]>; - userFragments: UserFragmentMap; -} - /** Replaces populate decorator with fragment spreads + fragments. */ -export const addFragmentsToQuery = ({ - schema, - query, - typeFragments, - userFragments, -}: AddFragmentsToQuery) => { +export const addFragmentsToQuery = ( + schema: GraphQLSchema, + query: DocumentNode, + typeFragments: TypeFragmentMap, + userFragments: UserFragmentMap +) => { const typeInfo = new TypeInfo(schema); const requiredUserFragments: Record< From c50798065a3bd372d53a529a0dcd89fb652225b4 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 15:35:27 +0000 Subject: [PATCH 09/11] Add IntrospectionQuery type from populate to cache --- src/cacheExchange.ts | 3 ++- src/populateExchange.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cacheExchange.ts b/src/cacheExchange.ts index 07d2113..b7b7136 100644 --- a/src/cacheExchange.ts +++ b/src/cacheExchange.ts @@ -7,6 +7,7 @@ import { CacheOutcome, } from 'urql'; +import { IntrospectionQuery } from 'graphql'; import { filter, map, merge, pipe, share, tap } from 'wonka'; import { query, write, writeOptimistic } from './operations'; import { SchemaPredicates } from './ast/schemaPredicates'; @@ -85,7 +86,7 @@ export interface CacheExchangeOpts { resolvers?: ResolverConfig; optimistic?: OptimisticMutationConfig; keys?: KeyingConfig; - schema?: object; + schema?: IntrospectionQuery; } export const cacheExchange = (opts?: CacheExchangeOpts): Exchange => ({ diff --git a/src/populateExchange.ts b/src/populateExchange.ts index 15381fa..f449769 100644 --- a/src/populateExchange.ts +++ b/src/populateExchange.ts @@ -23,14 +23,14 @@ import { Exchange, Operation } from 'urql'; import { getName, getSelectionSet, unwrapType } from './ast'; import { invariant, warn } from './helpers/help'; -interface ExchangeArgs { +interface PopulateExchangeOpts { schema: IntrospectionQuery; } /** An exchange for auto-populating mutations with a required response body. */ export const populateExchange = ({ schema: ogSchema, -}: ExchangeArgs): Exchange => ({ forward }) => { +}: PopulateExchangeOpts): Exchange => ({ forward }) => { const schema = buildClientSchema(ogSchema); /** List of operation keys that have already been parsed. */ const parsedOperations = new Set(); From cc633a0f92a3e98ba5ae334f04f4653eaa3f32b1 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 16:05:23 +0000 Subject: [PATCH 10/11] Simplify GraphQLFlatType --- src/types.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/types.ts b/src/types.ts index 5af9241..92fd690 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,11 +2,8 @@ import { DocumentNode, FragmentDefinitionNode, SelectionNode, - GraphQLScalarType, - GraphQLObjectType, - GraphQLInterfaceType, - GraphQLUnionType, - GraphQLEnumType, + GraphQLOutputType, + GraphQLWrappingType, } from 'graphql'; // Helper types @@ -18,12 +15,7 @@ export interface Ref { // GraphQL helper types export type SelectionSet = ReadonlyArray; -export type GraphQLFlatType = - | GraphQLScalarType - | GraphQLObjectType - | GraphQLInterfaceType - | GraphQLUnionType - | GraphQLEnumType; +export type GraphQLFlatType = Exclude; export interface Fragments { [fragmentName: string]: void | FragmentDefinitionNode; } From a611689cf5fd4830d4039a57a67089be20c96131 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Tue, 3 Dec 2019 16:22:51 +0000 Subject: [PATCH 11/11] Moving map.ts to src/helpers/ --- src/{ => helpers}/map.ts | 0 src/store.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{ => helpers}/map.ts (100%) diff --git a/src/map.ts b/src/helpers/map.ts similarity index 100% rename from src/map.ts rename to src/helpers/map.ts diff --git a/src/store.ts b/src/store.ts index 6c3f401..73d82b8 100644 --- a/src/store.ts +++ b/src/store.ts @@ -17,7 +17,7 @@ import { KeyingConfig, } from './types'; -import * as KVMap from './map'; +import * as KVMap from './helpers/map'; import { joinKeys, keyOfField } from './helpers'; import { invariant, currentDebugStack } from './helpers/help'; import { read, readFragment } from './operations/query';