Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

(graphcache) - Replace buildClientSchema with lean implementation (#1189)

* Add reimplementation of buildClientSchema

* Replace GraphQLSchema with local SchemaIntrospector

* Replace root type getters with properties

* Remove unnecessary cruft on SchemaIntrospector

* Fix linting errors

* Remove specific Scalar construction

* Add changeset

* Refactor schema predicates to be name-based

* Rename buildClientSchema.ts to schema.ts

* Update changeset

authored by

Phil Pluckthun and committed by
GitHub
4ded9362 64668b3a

+212 -113
+8
.changeset/nervous-beds-battle.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Replace `graphql/utilities/buildClientSchema.mjs` with a custom-tailored, lighter implementation 6 + built into `@urql/exchange-graphcache`. This will appear to increase its size by about `0.2kB gzip` 7 + but will actually save around `8.5kB gzip` to `9.4kB gzip` in any production bundle by using less of 8 + `graphql`'s code.
+1
exchanges/graphcache/src/ast/index.ts
··· 1 1 export * from './variables'; 2 2 export * from './traversal'; 3 + export * from './schema'; 3 4 export * from './schemaPredicates'; 4 5 export * from './node';
+107
exchanges/graphcache/src/ast/schema.ts
··· 1 + import { 2 + IntrospectionQuery, 3 + IntrospectionInputValue, 4 + IntrospectionTypeRef, 5 + IntrospectionType, 6 + } from 'graphql'; 7 + 8 + export interface SchemaField { 9 + name: string; 10 + type: IntrospectionTypeRef; 11 + args: Record<string, IntrospectionInputValue>; 12 + } 13 + 14 + export interface SchemaObject { 15 + name: string; 16 + kind: 'INTERFACE' | 'OBJECT'; 17 + interfaces: Record<string, unknown>; 18 + fields: Record<string, SchemaField>; 19 + } 20 + 21 + export interface SchemaUnion { 22 + name: string; 23 + kind: 'UNION'; 24 + types: Record<string, unknown>; 25 + } 26 + 27 + export interface SchemaIntrospector { 28 + query: string | null; 29 + mutation: string | null; 30 + subscription: string | null; 31 + types: Record<string, SchemaObject | SchemaUnion>; 32 + isSubType(abstract: string, possible: string): boolean; 33 + } 34 + 35 + export const buildClientSchema = ({ 36 + __schema, 37 + }: IntrospectionQuery): SchemaIntrospector => { 38 + const typemap: Record<string, SchemaObject | SchemaUnion> = {}; 39 + 40 + const buildNameMap = <T extends { name: string }>( 41 + arr: ReadonlyArray<T> 42 + ): { [name: string]: T } => { 43 + const map: Record<string, T> = {}; 44 + for (let i = 0; i < arr.length; i++) map[arr[i].name] = arr[i]; 45 + return map; 46 + }; 47 + 48 + const buildType = ( 49 + type: IntrospectionType 50 + ): SchemaObject | SchemaUnion | void => { 51 + switch (type.kind) { 52 + case 'OBJECT': 53 + case 'INTERFACE': 54 + return { 55 + name: type.name, 56 + kind: type.kind as 'OBJECT' | 'INTERFACE', 57 + interfaces: buildNameMap(type.interfaces || []), 58 + fields: buildNameMap( 59 + type.fields.map(field => ({ 60 + name: field.name, 61 + type: field.type, 62 + args: buildNameMap(field.args), 63 + })) 64 + ), 65 + } as SchemaObject; 66 + case 'UNION': 67 + return { 68 + name: type.name, 69 + kind: type.kind as 'UNION', 70 + types: buildNameMap(type.possibleTypes || []), 71 + } as SchemaUnion; 72 + } 73 + }; 74 + 75 + for (let i = 0; i < __schema.types.length; i++) { 76 + const type = __schema.types[i]; 77 + if (type && type.name) { 78 + const out = buildType(type); 79 + if (out) typemap[type.name] = out; 80 + } 81 + } 82 + 83 + return { 84 + query: __schema.queryType ? __schema.queryType.name : null, 85 + mutation: __schema.mutationType ? __schema.mutationType.name : null, 86 + subscription: __schema.subscriptionType 87 + ? __schema.subscriptionType.name 88 + : null, 89 + types: typemap, 90 + isSubType(abstract: string, possible: string) { 91 + const abstractType = typemap[abstract]; 92 + const possibleType = typemap[possible]; 93 + if (!abstractType || !possibleType) { 94 + return false; 95 + } else if (abstractType.kind === 'UNION') { 96 + return !!abstractType.types[possible]; 97 + } else if ( 98 + abstractType.kind !== 'OBJECT' && 99 + possibleType.kind === 'OBJECT' 100 + ) { 101 + return !!possibleType.interfaces[abstract]; 102 + } else { 103 + return abstract === possible; 104 + } 105 + }, 106 + }; 107 + };
+2 -1
exchanges/graphcache/src/ast/schemaPredicates.test.ts
··· 1 - import { Kind, InlineFragmentNode, buildClientSchema } from 'graphql'; 1 + import { Kind, InlineFragmentNode } from 'graphql'; 2 2 import { mocked } from 'ts-jest/utils'; 3 + import { buildClientSchema } from './schema'; 3 4 import * as SchemaPredicates from './schemaPredicates'; 4 5 5 6 describe('SchemaPredicates', () => {
+76 -92
exchanges/graphcache/src/ast/schemaPredicates.ts
··· 1 - import { 2 - isNullableType, 3 - isListType, 4 - isNonNullType, 5 - InlineFragmentNode, 6 - FragmentDefinitionNode, 7 - GraphQLSchema, 8 - GraphQLAbstractType, 9 - GraphQLObjectType, 10 - GraphQLInterfaceType, 11 - GraphQLUnionType, 12 - } from 'graphql'; 1 + import { InlineFragmentNode, FragmentDefinitionNode } from 'graphql'; 13 2 14 3 import { warn, invariant } from '../helpers/help'; 15 4 import { getTypeCondition } from './node'; 5 + import { SchemaIntrospector, SchemaObject } from './schema'; 6 + 16 7 import { 17 8 KeyingConfig, 18 9 UpdateResolver, ··· 23 14 const BUILTIN_FIELD_RE = /^__/; 24 15 25 16 export const isFieldNullable = ( 26 - schema: GraphQLSchema, 17 + schema: SchemaIntrospector, 27 18 typename: string, 28 19 fieldName: string 29 20 ): boolean => { 30 21 if (BUILTIN_FIELD_RE.test(fieldName)) return true; 31 22 const field = getField(schema, typename, fieldName); 32 - return !!field && isNullableType(field.type); 23 + return !!field && field.type.kind !== 'NON_NULL'; 33 24 }; 34 25 35 26 export const isListNullable = ( 36 - schema: GraphQLSchema, 27 + schema: SchemaIntrospector, 37 28 typename: string, 38 29 fieldName: string 39 30 ): boolean => { 40 31 const field = getField(schema, typename, fieldName); 41 32 if (!field) return false; 42 - const ofType = isNonNullType(field.type) ? field.type.ofType : field.type; 43 - return isListType(ofType) && isNullableType(ofType.ofType); 33 + const ofType = 34 + field.type.kind === 'NON_NULL' ? field.type.ofType : field.type; 35 + return ofType.kind === 'LIST' && ofType.ofType.kind !== 'NON_NULL'; 44 36 }; 45 37 46 38 export const isFieldAvailableOnType = ( 47 - schema: GraphQLSchema, 39 + schema: SchemaIntrospector, 48 40 typename: string, 49 41 fieldName: string 50 42 ): boolean => { ··· 53 45 }; 54 46 55 47 export const isInterfaceOfType = ( 56 - schema: GraphQLSchema, 48 + schema: SchemaIntrospector, 57 49 node: InlineFragmentNode | FragmentDefinitionNode, 58 50 typename: string | void 59 51 ): boolean => { 60 52 if (!typename) return false; 61 53 const typeCondition = getTypeCondition(node); 62 54 if (!typeCondition || typename === typeCondition) return true; 63 - 64 - const abstractType = schema.getType(typeCondition); 65 - const objectType = schema.getType(typename); 66 - 67 - if (abstractType instanceof GraphQLObjectType) { 68 - return abstractType === objectType; 69 - } 70 - 71 - expectAbstractType(abstractType, typeCondition); 72 - expectObjectType(objectType, typename); 73 - return schema.isPossibleType(abstractType, objectType); 55 + if ( 56 + schema.types[typeCondition] && 57 + schema.types[typeCondition].kind === 'OBJECT' 58 + ) 59 + return typeCondition === typename; 60 + expectAbstractType(schema, typeCondition!); 61 + expectObjectType(schema, typename!); 62 + return schema.isSubType(typeCondition, typename); 74 63 }; 75 64 76 65 const getField = ( 77 - schema: GraphQLSchema, 66 + schema: SchemaIntrospector, 78 67 typename: string, 79 68 fieldName: string 80 69 ) => { 81 - const object = schema.getType(typename); 82 - expectObjectType(object, typename); 83 - 84 - const field = object.getFields()[fieldName]; 70 + expectObjectType(schema, typename); 71 + const object = schema.types[typename] as SchemaObject; 72 + const field = object.fields[fieldName]; 85 73 if (!field) { 86 74 warn( 87 75 'Invalid field: The field `' + ··· 98 86 return field; 99 87 }; 100 88 101 - function expectObjectType( 102 - x: any, 103 - typename: string 104 - ): asserts x is GraphQLObjectType { 89 + function expectObjectType(schema: SchemaIntrospector, typename: string) { 105 90 invariant( 106 - x instanceof GraphQLObjectType, 91 + schema.types[typename] && schema.types[typename].kind === 'OBJECT', 107 92 'Invalid Object type: The type `' + 108 93 typename + 109 94 '` is not an object in the defined schema, ' + ··· 112 97 ); 113 98 } 114 99 115 - function expectAbstractType( 116 - x: any, 117 - typename: string 118 - ): asserts x is GraphQLAbstractType { 100 + function expectAbstractType(schema: SchemaIntrospector, typename: string) { 119 101 invariant( 120 - x instanceof GraphQLInterfaceType || x instanceof GraphQLUnionType, 102 + schema.types[typename] && 103 + (schema.types[typename].kind === 'INTERFACE' || 104 + schema.types[typename].kind === 'UNION'), 121 105 'Invalid Abstract type: The type `' + 122 106 typename + 123 107 '` is not an Interface or Union type in the defined schema, ' + ··· 127 111 } 128 112 129 113 export function expectValidKeyingConfig( 130 - schema: GraphQLSchema, 114 + schema: SchemaIntrospector, 131 115 keys: KeyingConfig 132 116 ): void { 133 117 if (process.env.NODE_ENV !== 'production') { 134 - const types = schema.getTypeMap(); 135 118 for (const key in keys) { 136 - if (!types[key]) { 119 + if (!schema.types[key]) { 137 120 warn( 138 121 'Invalid Object type: The type `' + 139 122 key + ··· 146 129 } 147 130 148 131 export function expectValidUpdatesConfig( 149 - schema: GraphQLSchema, 132 + schema: SchemaIntrospector, 150 133 updates: Record<string, Record<string, UpdateResolver>> 151 134 ): void { 152 135 if (process.env.NODE_ENV === 'production') { 153 136 return; 154 137 } 155 138 156 - const mutation = schema.getMutationType(); 157 - const subscription = schema.getSubscriptionType(); 158 - const mutationFields = mutation ? mutation.getFields() : {}; 159 - const subscriptionFields = subscription ? subscription.getFields() : {}; 160 - const givenMutations = (mutation && updates[mutation.name]) || {}; 161 - const givenSubscription = (subscription && updates[subscription.name]) || {}; 162 - 163 - for (const fieldName in givenMutations) { 164 - if (mutationFields[fieldName] === undefined) { 165 - warn( 166 - 'Invalid mutation field: `' + 167 - fieldName + 168 - '` is not in the defined schema, but the `updates.Mutation` option is referencing it.', 169 - 21 170 - ); 139 + if (schema.mutation) { 140 + const mutationFields = (schema.types[schema.mutation] as SchemaObject) 141 + .fields; 142 + const givenMutations = updates[schema.mutation] || {}; 143 + for (const fieldName in givenMutations) { 144 + if (mutationFields[fieldName] === undefined) { 145 + warn( 146 + 'Invalid mutation field: `' + 147 + fieldName + 148 + '` is not in the defined schema, but the `updates.Mutation` option is referencing it.', 149 + 21 150 + ); 151 + } 171 152 } 172 153 } 173 154 174 - for (const fieldName in givenSubscription) { 175 - if (subscriptionFields[fieldName] === undefined) { 176 - warn( 177 - 'Invalid subscription field: `' + 178 - fieldName + 179 - '` is not in the defined schema, but the `updates.Subscription` option is referencing it.', 180 - 22 181 - ); 155 + if (schema.subscription) { 156 + const subscriptionFields = (schema.types[ 157 + schema.subscription 158 + ] as SchemaObject).fields; 159 + const givenSubscription = updates[schema.subscription] || {}; 160 + for (const fieldName in givenSubscription) { 161 + if (subscriptionFields[fieldName] === undefined) { 162 + warn( 163 + 'Invalid subscription field: `' + 164 + fieldName + 165 + '` is not in the defined schema, but the `updates.Subscription` option is referencing it.', 166 + 22 167 + ); 168 + } 182 169 } 183 170 } 184 171 } ··· 191 178 } 192 179 193 180 export function expectValidResolversConfig( 194 - schema: GraphQLSchema, 181 + schema: SchemaIntrospector, 195 182 resolvers: ResolverConfig 196 183 ): void { 197 184 if (process.env.NODE_ENV === 'production') { 198 185 return; 199 186 } 200 187 201 - const validTypes = schema.getTypeMap(); 202 188 for (const key in resolvers) { 203 189 if (key === 'Query') { 204 - const queryType = schema.getQueryType(); 205 - if (queryType) { 206 - const validQueries = queryType.getFields(); 190 + if (schema.query) { 191 + const validQueries = (schema.types[schema.query] as SchemaObject) 192 + .fields; 207 193 for (const resolverQuery in resolvers.Query) { 208 194 if (!validQueries[resolverQuery]) { 209 195 warnAboutResolver('Query.' + resolverQuery); ··· 213 199 warnAboutResolver('Query'); 214 200 } 215 201 } else { 216 - if (!validTypes[key]) { 202 + if (!schema.types[key]) { 217 203 warnAboutResolver(key); 218 204 } else { 219 - const validTypeProperties = (schema.getType( 220 - key 221 - ) as GraphQLObjectType).getFields(); 205 + const validTypeProperties = (schema.types[key] as SchemaObject).fields; 222 206 for (const resolverProperty in resolvers[key]) { 223 207 if (!validTypeProperties[resolverProperty]) { 224 208 warnAboutResolver(key + '.' + resolverProperty); ··· 230 214 } 231 215 232 216 export function expectValidOptimisticMutationsConfig( 233 - schema: GraphQLSchema, 217 + schema: SchemaIntrospector, 234 218 optimisticMutations: OptimisticMutationConfig 235 219 ): void { 236 220 if (process.env.NODE_ENV === 'production') { 237 221 return; 238 222 } 239 223 240 - const validMutations = schema.getMutationType() 241 - ? (schema.getMutationType() as GraphQLObjectType).getFields() 242 - : {}; 243 - 244 - for (const mutation in optimisticMutations) { 245 - if (!validMutations[mutation]) { 246 - warn( 247 - `Invalid optimistic mutation field: \`${mutation}\` is not a mutation field in the defined schema, but the \`optimistic\` option is referencing it.`, 248 - 24 249 - ); 224 + if (schema.mutation) { 225 + const validMutations = (schema.types[schema.mutation] as SchemaObject) 226 + .fields; 227 + for (const mutation in optimisticMutations) { 228 + if (!validMutations[mutation]) { 229 + warn( 230 + `Invalid optimistic mutation field: \`${mutation}\` is not a mutation field in the defined schema, but the \`optimistic\` option is referencing it.`, 231 + 24 232 + ); 233 + } 250 234 } 251 235 } 252 236 }
+18 -20
exchanges/graphcache/src/store/store.ts
··· 1 - import { 2 - buildClientSchema, 3 - DocumentNode, 4 - IntrospectionQuery, 5 - GraphQLSchema, 6 - } from 'graphql'; 1 + import { DocumentNode, IntrospectionQuery } from 'graphql'; 7 2 8 3 import { TypedDocumentNode, formatDocument, createRequest } from '@urql/core'; 9 4 ··· 27 22 import { invalidateEntity } from '../operations/invalidate'; 28 23 import { keyOfField } from './keys'; 29 24 import * as InMemoryData from './data'; 30 - import * as SchemaPredicates from '../ast/schemaPredicates'; 25 + 26 + import { 27 + SchemaIntrospector, 28 + buildClientSchema, 29 + expectValidKeyingConfig, 30 + expectValidUpdatesConfig, 31 + expectValidResolversConfig, 32 + expectValidOptimisticMutationsConfig, 33 + } from '../ast'; 31 34 32 35 type RootField = 'query' | 'mutation' | 'subscription'; 33 36 ··· 46 49 updates: Record<string, Record<string, UpdateResolver>>; 47 50 optimisticMutations: OptimisticMutationConfig; 48 51 keys: KeyingConfig; 49 - schema?: GraphQLSchema; 52 + schema?: SchemaIntrospector; 50 53 51 54 rootFields: { query: string; mutation: string; subscription: string }; 52 55 rootNames: { [name: string]: RootField }; ··· 63 66 let subscriptionName = 'Subscription'; 64 67 if (opts.schema) { 65 68 const schema = (this.schema = buildClientSchema(opts.schema)); 66 - const queryType = schema.getQueryType(); 67 - const mutationType = schema.getMutationType(); 68 - const subscriptionType = schema.getSubscriptionType(); 69 - queryName = queryType ? queryType.name : queryName; 70 - mutationName = mutationType ? mutationType.name : mutationName; 71 - subscriptionName = subscriptionType 72 - ? subscriptionType.name 73 - : subscriptionName; 69 + queryName = schema.query || queryName; 70 + mutationName = schema.mutation || mutationName; 71 + subscriptionName = schema.subscription || subscriptionName; 74 72 } 75 73 76 74 this.updates = { ··· 93 91 this.data = InMemoryData.make(queryName); 94 92 95 93 if (this.schema && process.env.NODE_ENV !== 'production') { 96 - SchemaPredicates.expectValidKeyingConfig(this.schema, this.keys); 97 - SchemaPredicates.expectValidUpdatesConfig(this.schema, this.updates); 98 - SchemaPredicates.expectValidResolversConfig(this.schema, this.resolvers); 99 - SchemaPredicates.expectValidOptimisticMutationsConfig( 94 + expectValidKeyingConfig(this.schema, this.keys); 95 + expectValidUpdatesConfig(this.schema, this.updates); 96 + expectValidResolversConfig(this.schema, this.resolvers); 97 + expectValidOptimisticMutationsConfig( 100 98 this.schema, 101 99 this.optimisticMutations 102 100 );