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) - Shift mutation results to top layer (#745)

authored by

Phil Plückthun and committed by
GitHub
e02392ab b3c745dc

+71 -39
+5
.changeset/long-rocks-give.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Adjust mutation results priority to always override query results as they arrive, similarly to subscriptions. This will prevent race conditions when mutations are slow to execute at the cost of some consistency.
+1 -1
exchanges/graphcache/src/cacheExchange.test.ts
··· 1545 1545 }); 1546 1546 1547 1547 expect(reexec).toHaveBeenCalledTimes(2); 1548 - expect(data).toHaveProperty('node.name', 'query b'); 1548 + expect(data).toHaveProperty('node.name', 'mutation'); 1549 1549 }); 1550 1550 1551 1551 it('applies optimistic updates on top of commutative queries', () => {
+11 -24
exchanges/graphcache/src/cacheExchange.ts
··· 140 140 reserveLayer(store.data, operation.key); 141 141 } else if (operation.operationName === 'teardown') { 142 142 noopDataState(store.data, operation.key); 143 - } else if (operation.operationName === 'mutation') { 143 + } else if ( 144 + operation.operationName === 'mutation' && 145 + operation.context.requestPolicy !== 'network-only' 146 + ) { 144 147 // This executes an optimistic update for mutations and registers it if necessary 145 - if (operation.context.requestPolicy !== 'network-only') { 146 - const { dependencies } = writeOptimistic( 147 - store, 148 - operation, 149 - operation.key 150 - ); 151 - if (dependencies.size !== 0) { 152 - optimisticKeysToDependencies.set(operation.key, dependencies); 153 - const pendingOperations = new Set<number>(); 154 - collectPendingOperations(pendingOperations, dependencies); 155 - executePendingOperations(operation, pendingOperations); 156 - } 157 - } else { 158 - reserveLayer(store.data, operation.key); 148 + const { dependencies } = writeOptimistic(store, operation, operation.key); 149 + if (dependencies.size !== 0) { 150 + optimisticKeysToDependencies.set(operation.key, dependencies); 151 + const pendingOperations = new Set<number>(); 152 + collectPendingOperations(pendingOperations, dependencies); 153 + executePendingOperations(operation, pendingOperations); 159 154 } 160 155 } 161 156 ··· 175 170 const updateDependencies = (op: Operation, dependencies: Set<string>) => { 176 171 dependencies.forEach(dep => { 177 172 (deps[dep] || (deps[dep] = [])).push(op.key); 178 - 179 - if (!ops.has(op.key)) { 180 - ops.set( 181 - op.key, 182 - op.context.requestPolicy === 'network-only' 183 - ? toRequestPolicy(op, 'cache-and-network') 184 - : op 185 - ); 186 - } 173 + ops.set(op.key, op); 187 174 }); 188 175 }; 189 176
+4 -6
exchanges/graphcache/src/operations/query.ts
··· 230 230 (result && result.__typename) 231 231 : key; 232 232 233 - if ( 234 - typeof typename !== 'string' || 235 - (result && typename !== result.__typename) 236 - ) { 237 - // TODO: This may be an invalid error for resolvers that return interfaces 233 + if (typeof typename !== 'string') { 234 + return; 235 + } else if (result && typename !== result.__typename) { 238 236 warn( 239 237 'Invalid resolver data: The resolver at `' + 240 238 entityKey + ··· 243 241 8 244 242 ); 245 243 246 - return undefined; 244 + return; 247 245 } 248 246 249 247 // The following closely mirrors readSelection, but differs only slightly for the
+33
exchanges/graphcache/src/store/data.test.ts
··· 240 240 241 241 InMemoryData.initDataState(data, null); 242 242 expect(InMemoryData.readRecord('Query', 'index')).toBe(1); 243 + InMemoryData.clearDataState(); 243 244 244 245 expect(data.optimisticOrder).toEqual([]); 246 + }); 247 + 248 + it('discards optimistic order when concrete data is written', () => { 249 + InMemoryData.reserveLayer(data, 1); 250 + InMemoryData.reserveLayer(data, 2); 251 + InMemoryData.reserveLayer(data, 3); 252 + 253 + InMemoryData.initDataState(data, 2, true); 254 + InMemoryData.writeRecord('Query', 'index', 2); 255 + InMemoryData.writeRecord('Query', 'optimistic', true); 256 + InMemoryData.clearDataState(); 257 + 258 + InMemoryData.initDataState(data, 3); 259 + InMemoryData.writeRecord('Query', 'index', 3); 260 + InMemoryData.clearDataState(); 261 + 262 + // Expect Layer 3 263 + expect(data.optimisticOrder).toEqual([3, 2, 1]); 264 + InMemoryData.initDataState(data, null); 265 + expect(InMemoryData.readRecord('Query', 'index')).toBe(3); 266 + expect(InMemoryData.readRecord('Query', 'optimistic')).toBe(true); 267 + 268 + // Write 2 again 269 + InMemoryData.initDataState(data, 2); 270 + InMemoryData.writeRecord('Query', 'index', 2); 271 + InMemoryData.clearDataState(); 272 + 273 + // 2 has moved in front of 3 274 + expect(data.optimisticOrder).toEqual([2, 3, 1]); 275 + InMemoryData.initDataState(data, null); 276 + expect(InMemoryData.readRecord('Query', 'index')).toBe(2); 277 + expect(InMemoryData.readRecord('Query', 'optimistic')).toBe(undefined); 245 278 }); 246 279 247 280 it('overrides data using optimistic layers', () => {
+17 -8
exchanges/graphcache/src/store/data.ts
··· 80 80 81 81 if (!layerKey) { 82 82 currentOptimisticKey = null; 83 - } else if ( 84 - isOptimistic || 85 - (data.optimisticOrder.length > 1 && 86 - data.optimisticOrder.indexOf(layerKey) > -1) 87 - ) { 83 + } else if (isOptimistic || data.optimisticOrder.length > 0) { 88 84 // If this operation isn't optimistic and we see it for the first time, 89 85 // then it must've been optimistic in the past, so we can proactively 90 86 // clear the optimistic data before writing 91 87 if (!isOptimistic && !data.commutativeKeys.has(layerKey)) { 92 - clearLayer(data, layerKey); 93 - data.commutativeKeys.add(layerKey); 88 + reserveLayer(data, layerKey); 89 + } else if (isOptimistic) { 90 + // NOTE: This optimally shouldn't happen as it implies that an optimistic 91 + // write is being performed after a concrete write. 92 + data.commutativeKeys.delete(layerKey); 94 93 } 94 + 95 95 // An optimistic update of a mutation may force an optimistic layer, 96 96 // or this Query update may be applied optimistically since it's part 97 97 // of a commutate chain ··· 457 457 458 458 /** Reserves an optimistic layer and preorders it */ 459 459 export const reserveLayer = (data: InMemoryData, layerKey: number) => { 460 - if (data.optimisticOrder.indexOf(layerKey) === -1) { 460 + const index = data.optimisticOrder.indexOf(layerKey); 461 + if (index === -1) { 461 462 // The new layer needs to be reserved in front of all other commutative 462 463 // keys but after all non-commutative keys (which are added by `forceUpdate`) 464 + data.optimisticOrder.unshift(layerKey); 465 + } else if (!data.commutativeKeys.has(layerKey)) { 466 + // Protect optimistic layers from being turned into non-optimistic layers 467 + // while preserving optimistic data 468 + clearLayer(data, layerKey); 469 + // If the layer was an optimistic layer prior to this call, it'll be converted 470 + // to a new non-optimistic layer and shifted ahead 471 + data.optimisticOrder.splice(index, 1); 463 472 data.optimisticOrder.unshift(layerKey); 464 473 } 465 474