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.

feat(graphcache): default updater when none found (#3518)

Co-authored-by: Phil Pluckthun <phil@kitten.sh>

authored by

Jovi De Croock
Phil Pluckthun
and committed by
GitHub
50415140 bba28c7d

+148 -2
+5
.changeset/honest-queens-approve.md
··· 1 + --- 2 + '@urql/exchange-graphcache': major 3 + --- 4 + 5 + Add a default updater for mutation fields who are lacking an updater and where the returned entity is not present in the cache
+107
exchanges/graphcache/src/cacheExchange.test.ts
··· 1823 1823 }); 1824 1824 }); 1825 1825 1826 + describe('mutation updates', () => { 1827 + it('invalidates the type when the entity is not present in the cache', () => { 1828 + vi.useFakeTimers(); 1829 + 1830 + const authorsQuery = gql` 1831 + query { 1832 + authors { 1833 + id 1834 + name 1835 + } 1836 + } 1837 + `; 1838 + 1839 + const authorsQueryData = { 1840 + __typename: 'Query', 1841 + authors: [ 1842 + { 1843 + __typename: 'Author', 1844 + id: '1', 1845 + name: 'Author', 1846 + }, 1847 + ], 1848 + }; 1849 + 1850 + const mutation = gql` 1851 + mutation { 1852 + addAuthor { 1853 + id 1854 + name 1855 + } 1856 + } 1857 + `; 1858 + 1859 + const client = createClient({ 1860 + url: 'http://0.0.0.0', 1861 + exchanges: [], 1862 + }); 1863 + const { source: ops$, next } = makeSubject<Operation>(); 1864 + 1865 + const reexec = vi 1866 + .spyOn(client, 'reexecuteOperation') 1867 + .mockImplementation(next); 1868 + 1869 + const opOne = client.createRequestOperation('query', { 1870 + key: 1, 1871 + query: authorsQuery, 1872 + variables: undefined, 1873 + }); 1874 + 1875 + const opMutation = client.createRequestOperation('mutation', { 1876 + key: 2, 1877 + query: mutation, 1878 + variables: undefined, 1879 + }); 1880 + 1881 + const response = vi.fn((forwardOp: Operation): OperationResult => { 1882 + if (forwardOp.key === 1) { 1883 + return { ...queryResponse, operation: opOne, data: authorsQueryData }; 1884 + } else if (forwardOp.key === 2) { 1885 + return { 1886 + ...queryResponse, 1887 + operation: opMutation, 1888 + data: { 1889 + __typename: 'Mutation', 1890 + addAuthor: { id: '2', name: 'Author 2', __typename: 'Author' }, 1891 + }, 1892 + }; 1893 + } 1894 + 1895 + return undefined as any; 1896 + }); 1897 + 1898 + const result = vi.fn(); 1899 + const forward: ExchangeIO = ops$ => 1900 + pipe(ops$, delay(1), map(response), share); 1901 + 1902 + pipe( 1903 + cacheExchange()({ 1904 + forward, 1905 + client, 1906 + dispatchDebug, 1907 + })(ops$), 1908 + tap(result), 1909 + publish 1910 + ); 1911 + 1912 + next(opOne); 1913 + vi.runAllTimers(); 1914 + expect(response).toHaveBeenCalledTimes(1); 1915 + 1916 + next(opMutation); 1917 + expect(response).toHaveBeenCalledTimes(1); 1918 + expect(reexec).toHaveBeenCalledTimes(0); 1919 + 1920 + vi.runAllTimers(); 1921 + 1922 + expect(response).toHaveBeenCalledTimes(2); 1923 + expect(result).toHaveBeenCalledTimes(2); 1924 + expect(reexec).toHaveBeenCalledTimes(1); 1925 + 1926 + next(opOne); 1927 + vi.runAllTimers(); 1928 + expect(response).toHaveBeenCalledTimes(3); 1929 + expect(result).toHaveBeenCalledTimes(3); 1930 + }); 1931 + }); 1932 + 1826 1933 describe('extra variables', () => { 1827 1934 it('allows extra variables to be applied to updates', () => { 1828 1935 vi.useFakeTimers();
+30
exchanges/graphcache/src/operations/write.ts
··· 46 46 getFieldError, 47 47 deferRef, 48 48 } from './shared'; 49 + import { invalidateType } from './invalidate'; 49 50 50 51 export interface WriteResult { 51 52 data: null | Data; ··· 373 374 374 375 data[fieldName] = fieldValue; 375 376 updater(data, fieldArgs || {}, ctx.store, ctx); 377 + } else if ( 378 + typename === ctx.store.rootFields['mutation'] && 379 + !ctx.optimistic 380 + ) { 381 + // If we're on a mutation that doesn't have an updater, we'll see 382 + // whether we can find the entity returned by the mutation in the cache. 383 + // if we don't we'll assume this is a create mutation and invalidate 384 + // the found __typename. 385 + if (fieldValue && Array.isArray(fieldValue)) { 386 + for (let i = 0, l = fieldValue.length; i < l; i++) { 387 + const key = ctx.store.keyOfEntity(fieldValue[i]); 388 + if (key && fieldValue[i].__typename) { 389 + const resolved = InMemoryData.readRecord(key, '__typename'); 390 + const count = InMemoryData!.getRefCount(key); 391 + if (resolved && !count) { 392 + invalidateType(fieldValue[i].__typename); 393 + } 394 + } 395 + } 396 + } else if (fieldValue && typeof fieldValue === 'object') { 397 + const key = ctx.store.keyOfEntity(fieldValue as any); 398 + if (key) { 399 + const resolved = InMemoryData.readRecord(key, '__typename'); 400 + const count = InMemoryData.getRefCount(key); 401 + if ((!resolved || !count) && fieldValue.__typename) { 402 + invalidateType(fieldValue.__typename); 403 + } 404 + } 405 + } 376 406 } 377 407 378 408 // After processing the field, remove the current alias from the path again
+6 -2
exchanges/graphcache/src/store/data.ts
··· 338 338 return node !== undefined ? node[fieldKey] : undefined; 339 339 }; 340 340 341 + export function getRefCount(entityKey: string): number { 342 + return currentData!.refCount.get(entityKey) || 0; 343 + } 344 + 341 345 /** Adjusts the reference count of an entity on a refCount dict by "by" and updates the gc */ 342 346 const updateRCForEntity = (entityKey: string, by: number): void => { 343 347 // Retrieve the reference count and adjust it by "by" 344 - const count = currentData!.refCount.get(entityKey) || 0; 348 + const count = getRefCount(entityKey); 345 349 const newCount = count + by > 0 ? count + by : 0; 346 350 currentData!.refCount.set(entityKey, newCount); 347 351 // Add it to the garbage collection batch if it needs to be deleted or remove it ··· 410 414 411 415 // Check first whether the entity has any references, 412 416 // if so, we skip it from the GC run 413 - const rc = currentData!.refCount.get(entityKey) || 0; 417 + const rc = getRefCount(entityKey); 414 418 if (rc > 0) continue; 415 419 416 420 const record = currentData!.records.base.get(entityKey);