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

feat(graphcache): track abstract-concrete type mapping (#3548)

authored by

Jovi De Croock and committed by
GitHub
a6f51628 6bd4c90f

+53 -2
+5
.changeset/many-toes-try.md
··· 1 + --- 2 + '@urql/exchange-graphcache': minor 3 + --- 4 + 5 + Track abstract types being written so that we have a more reliable way of matching abstract fragments
+28 -2
exchanges/graphcache/src/operations/shared.ts
··· 17 17 } from '../ast'; 18 18 19 19 import { warn, pushDebugNode, popDebugNode } from '../helpers/help'; 20 - import { hasField, currentOperation, currentOptimistic } from '../store/data'; 20 + import { 21 + hasField, 22 + currentOperation, 23 + currentOptimistic, 24 + writeConcreteType, 25 + getConcreteTypes, 26 + } from '../store/data'; 21 27 import { keyOfField } from '../store/keys'; 22 28 import type { Store } from '../store/store'; 23 29 ··· 219 225 !fragment.typeCondition || 220 226 (ctx.store.schema 221 227 ? isInterfaceOfType(ctx.store.schema, fragment, typename) 222 - : isFragmentHeuristicallyMatching( 228 + : (currentOperation === 'read' && 229 + isFragmentMatching( 230 + fragment.typeCondition.name.value, 231 + typename 232 + )) || 233 + isFragmentHeuristicallyMatching( 223 234 fragment, 224 235 typename, 225 236 entityKey, 226 237 ctx.variables, 227 238 ctx.store.logger 228 239 )); 240 + 229 241 if (isMatching) { 230 242 if (process.env.NODE_ENV !== 'production') 231 243 pushDebugNode(typename, fragment); 232 244 const isFragmentOptional = isOptional(select); 245 + if ( 246 + fragment.typeCondition && 247 + typename !== fragment.typeCondition.name.value 248 + ) { 249 + writeConcreteType(fragment.typeCondition.name.value, typename!); 250 + } 251 + 233 252 child = makeSelectionIterator( 234 253 typename, 235 254 entityKey, ··· 249 268 } 250 269 }; 251 270 } 271 + 272 + const isFragmentMatching = (typeCondition: string, typename: string | void) => { 273 + if (!typename) return false; 274 + if (typeCondition === typename) return true; 275 + const types = getConcreteTypes(typeCondition); 276 + return types.size && types.has(typename); 277 + }; 252 278 253 279 export const ensureData = (x: DataField): Data | NullArray<Data> | null => 254 280 x == null ? null : (x as Data | NullArray<Data>);
+20
exchanges/graphcache/src/store/data.ts
··· 59 59 optimisticOrder: number[]; 60 60 /** This may be a persistence adapter that will receive changes in a batch */ 61 61 storage: StorageAdapter | null; 62 + /** A map of all the types we have encountered that did not map directly to a concrete type */ 63 + abstractToConcreteMap: Map<string, Set<string>>; 62 64 } 63 65 64 66 let currentOwnership: null | WeakSet<any> = null; ··· 249 251 optimistic: new Map(), 250 252 base: new Map(), 251 253 }, 254 + abstractToConcreteMap: new Map(), 252 255 records: { 253 256 optimistic: new Map(), 254 257 base: new Map(), ··· 479 482 currentData!.types.set(typename, typeSet); 480 483 } else { 481 484 existingTypes.add(entityKey); 485 + } 486 + }; 487 + 488 + export const getConcreteTypes = (typename: string): Set<string> => 489 + currentData!.abstractToConcreteMap.get(typename) || DEFAULT_EMPTY_SET; 490 + 491 + export const writeConcreteType = ( 492 + abstractType: string, 493 + concreteType: string 494 + ) => { 495 + const existingTypes = currentData!.abstractToConcreteMap.get(abstractType); 496 + if (!existingTypes) { 497 + const typeSet = new Set<string>(); 498 + typeSet.add(concreteType); 499 + currentData!.abstractToConcreteMap.set(abstractType, typeSet); 500 + } else { 501 + existingTypes.add(concreteType); 482 502 } 483 503 }; 484 504