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

feat(graphcache): add possibleTypes config for deterministic fragment matching (#3805)

Changed files
+57 -10
.changeset
exchanges
graphcache
src
operations
store
+5
.changeset/unlucky-apes-change.md
···
+
---
+
'@urql/exchange-graphcache': minor
+
---
+
+
Add possibleTypes config for deterministic fragment matching
+29 -10
exchanges/graphcache/src/operations/shared.ts
···
fragment,
this.typename
)
-
: (currentOperation === 'read' &&
-
isFragmentMatching(
+
: this.ctx.store.possibleTypeMap
+
? isSuperType(
+
this.ctx.store.possibleTypeMap,
fragment.typeCondition.name.value,
this.typename
-
)) ||
-
isFragmentHeuristicallyMatching(
-
fragment,
-
this.typename,
-
this.entityKey,
-
this.ctx.variables,
-
this.ctx.store.logger
-
));
+
)
+
: (currentOperation === 'read' &&
+
isFragmentMatching(
+
fragment.typeCondition.name.value,
+
this.typename
+
)) ||
+
isFragmentHeuristicallyMatching(
+
fragment,
+
this.typename,
+
this.entityKey,
+
this.ctx.variables,
+
this.ctx.store.logger
+
));
if (
isMatching ||
(currentOperation === 'write' && !this.ctx.store.schema)
···
return undefined;
}
}
+
+
const isSuperType = (
+
possibleTypeMap: Map<string, Set<string>>,
+
typeCondition: string,
+
typename: string | void
+
) => {
+
if (!typename) return false;
+
if (typeCondition === typename) return true;
+
+
const concreteTypes = possibleTypeMap.get(typeCondition);
+
+
return concreteTypes && concreteTypes.has(typename);
+
};
const isFragmentMatching = (typeCondition: string, typename: string | void) => {
if (!typename) return false;
+9
exchanges/graphcache/src/store/store.ts
···
keys: KeyingConfig;
globalIDs: Set<string> | boolean;
schema?: SchemaIntrospector;
+
possibleTypeMap?: Map<string, Set<string>>;
rootFields: { query: string; mutation: string; subscription: string };
rootNames: { [name: string]: RootField | void };
···
subscriptionName = schema.subscription || subscriptionName;
// Only add schema introspector if it has types info
if (schema.types) this.schema = schema;
+
}
+
+
if (!this.schema && opts.possibleTypes) {
+
this.possibleTypeMap = new Map();
+
for (const entry of Object.entries(opts.possibleTypes)) {
+
const [abstractType, concreteTypes] = entry;
+
this.possibleTypeMap.set(abstractType, new Set(concreteTypes));
+
}
}
this.updates = opts.updates || {};
+14
exchanges/graphcache/src/types.ts
···
* type names may be passed instead.
*/
globalIDs?: string[] | boolean;
+
/** Configures abstract to concrete types mapping for GraphQL types.
+
*
+
* @remarks
+
* This will disable heuristic fragment matching, allowing Graphcache to match
+
* fragment deterministically.
+
*
+
* When both `possibleTypes` and `schema` is set, `possibleTypes` value will be
+
* ignored.
+
*/
+
possibleTypes?: PossibleTypesConfig;
/** Configures Graphcache with Schema Introspection data.
*
* @remarks
···
*/
export type KeyingConfig = {
[typename: string]: KeyGenerator;
+
};
+
+
export type PossibleTypesConfig = {
+
[abstractType: string]: string[];
};
/** Serialized normalized caching data. */