···11+---
22+'@urql/exchange-graphcache': minor
33+---
44+55+Track list of entity keys for a given type name. This enables enumerating and invalidating all entities of a given type within the normalized cache.
+19
docs/graphcache/cache-updates.md
···490490mutation by enumerating all `todos` listing fields using `cache.inspectFields` and targetedly
491491invalidate only these fields, which causes all queries using these listing fields to be refetched.
492492493493+### Invalidating a type
494494+495495+We can also invalidate all the entities of a given type, this could be handy in the case of a
496496+list update or when you aren't sure what entity is affected.
497497+498498+This can be done by only passing the relevant `__typename` to the `invalidate` function.
499499+500500+```js
501501+cacheExchange({
502502+ updates: {
503503+ Mutation: {
504504+ deleteTodo(_result, args, cache, _info) {
505505+ cache.invalidate('Todo');
506506+ },
507507+ },
508508+ },
509509+});
510510+```
511511+493512## Optimistic updates
494513495514If we know what result a mutation may return, why wait for the GraphQL API to fulfill our mutations?
···4747 records: NodeMap<EntityField>;
4848 /** A map of entity links which are connections from one entity to another (key-value entries per entity) */
4949 links: NodeMap<Link>;
5050+ /** A map of typename to a list of entity-keys belonging to said type */
5151+ types: Map<string, Set<string>>;
5052 /** A set of Query operation keys that are in-flight and deferred/streamed */
5153 deferredKeys: Set<number>;
5254 /** A set of Query operation keys that are in-flight and awaiting a result */
···234236 return currentDependencies;
235237};
236238239239+const DEFAULT_EMPTY_SET = new Set<string>();
237240export const make = (queryRootKey: string): InMemoryData => ({
238241 hydrating: false,
239242 defer: false,
240243 gc: new Set(),
244244+ types: new Map(),
241245 persist: new Set(),
242246 queryRootKey,
243247 refCount: new Map(),
···409413 const rc = currentData!.refCount.get(entityKey) || 0;
410414 if (rc > 0) continue;
411415416416+ const record = currentData!.records.base.get(entityKey);
412417 // Delete the reference count, and delete the entity from the GC batch
413418 currentData!.refCount.delete(entityKey);
414419 currentData!.records.base.delete(entityKey);
420420+421421+ const typename = (record && record.__typename) as string | undefined;
422422+ if (typename) {
423423+ const type = currentData!.types.get(typename);
424424+ if (type) type.delete(entityKey);
425425+ }
426426+415427 const linkNode = currentData!.links.base.get(entityKey);
416428 if (linkNode) {
417429 currentData!.links.base.delete(entityKey);
···450462): Link | undefined => {
451463 updateDependencies(entityKey, fieldKey);
452464 return getNode(currentData!.links, entityKey, fieldKey);
465465+};
466466+467467+export const getEntitiesForType = (typename: string): Set<string> =>
468468+ currentData!.types.get(typename) || DEFAULT_EMPTY_SET;
469469+470470+export const writeType = (typename: string, entityKey: string) => {
471471+ const existingTypes = currentData!.types.get(typename);
472472+ if (!existingTypes) {
473473+ const typeSet = new Set<string>();
474474+ typeSet.add(entityKey);
475475+ currentData!.types.set(typename, typeSet);
476476+ } else {
477477+ existingTypes.add(entityKey);
478478+ }
453479};
454480455481/** Writes an entity's field (a "record") to data */
+9
exchanges/graphcache/src/store/store.test.ts
···844844 expect(data).toBe(null);
845845 });
846846 });
847847+848848+ describe('Invalidating a type', () => {
849849+ it('removes an entity from a list.', () => {
850850+ InMemoryData.initDataState('write', store.data, null);
851851+ store.invalidate('Todo');
852852+ const { data } = query(store, { query: Todos });
853853+ expect(data).toBe(null);
854854+ });
855855+ });
847856});
848857849858describe('Store with storage', () => {
+17-11
exchanges/graphcache/src/store/store.ts
···2424import { contextRef, ensureLink } from '../operations/shared';
2525import { _query, _queryFragment } from '../operations/query';
2626import { _write, _writeFragment } from '../operations/write';
2727-import { invalidateEntity } from '../operations/invalidate';
2727+import { invalidateEntity, invalidateType } from '../operations/invalidate';
2828import { keyOfField } from './keys';
2929import * as InMemoryData from './data';
3030···170170171171 invalidate(entity: Entity, field?: string, args?: FieldArgs) {
172172 const entityKey = this.keyOfEntity(entity);
173173+ const shouldInvalidateType =
174174+ entity && typeof entity === 'string' && !field && !args;
173175174174- invariant(
175175- entityKey,
176176- "Can't generate a key for invalidate(...).\n" +
177177- 'You have to pass an id or _id field or create a custom `keys` field for `' +
178178- (typeof entity === 'object'
179179- ? (entity as Data).__typename
180180- : entity + '`.'),
181181- 19
182182- );
176176+ if (shouldInvalidateType) {
177177+ invalidateType(entity);
178178+ } else {
179179+ invariant(
180180+ entityKey,
181181+ "Can't generate a key for invalidate(...).\n" +
182182+ 'You have to pass an id or _id field or create a custom `keys` field for `' +
183183+ (typeof entity === 'object'
184184+ ? (entity as Data).__typename
185185+ : entity + '`.'),
186186+ 19
187187+ );
183188184184- invalidateEntity(entityKey, field, args);
189189+ invalidateEntity(entityKey, field, args);
190190+ }
185191 }
186192187193 inspectFields(entity: Entity): FieldInfo[] {