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): debug logs for cache misses (#3446)

authored by

Jovi De Croock and committed by
GitHub
0964a014 e5c67c23

+105
+5
.changeset/mean-onions-share.md
··· 1 + --- 2 + '@urql/exchange-graphcache': minor 3 + --- 4 + 5 + Allow the user to debug cache-misses by means of the new `logger` interface on the `cacheExchange`. A field miss will dispatch a `debug` log when it's not marked with `@_optional` or when it's non-nullable in the `schema`.
+76
exchanges/graphcache/src/cacheExchange.test.ts
··· 110 110 expect(result.mock.calls[1][0].data).toBe(result.mock.calls[0][0].data); 111 111 }); 112 112 113 + it('logs cache misses', () => { 114 + const client = createClient({ 115 + url: 'http://0.0.0.0', 116 + exchanges: [], 117 + }); 118 + const op = client.createRequestOperation('query', { 119 + key: 1, 120 + query: queryOne, 121 + variables: undefined, 122 + }); 123 + 124 + const expected = { 125 + __typename: 'Query', 126 + author: { 127 + id: '123', 128 + name: 'Author', 129 + __typename: 'Author', 130 + }, 131 + unrelated: { 132 + id: 'unrelated', 133 + __typename: 'Unrelated', 134 + }, 135 + }; 136 + 137 + const response = vi.fn((forwardOp: Operation): OperationResult => { 138 + expect(forwardOp.key).toBe(op.key); 139 + return { ...queryResponse, operation: forwardOp, data: expected }; 140 + }); 141 + 142 + const { source: ops$, next } = makeSubject<Operation>(); 143 + const result = vi.fn(); 144 + const forward: ExchangeIO = ops$ => pipe(ops$, map(response), share); 145 + 146 + const messages: string[] = []; 147 + pipe( 148 + cacheExchange({ 149 + logger(severity, message) { 150 + if (severity === 'debug') { 151 + messages.push(message); 152 + } 153 + }, 154 + })({ forward, client, dispatchDebug })(ops$), 155 + tap(result), 156 + publish 157 + ); 158 + 159 + next(op); 160 + next(op); 161 + next({ 162 + ...op, 163 + query: gql` 164 + query ($id: ID!) { 165 + author(id: $id) { 166 + id 167 + name 168 + } 169 + } 170 + `, 171 + variables: { id: '123' }, 172 + }); 173 + expect(response).toHaveBeenCalledTimes(1); 174 + expect(result).toHaveBeenCalledTimes(2); 175 + 176 + expect(expected).toMatchObject(result.mock.calls[0][0].data); 177 + expect(result.mock.calls[1][0]).toHaveProperty( 178 + 'operation.context.meta.cacheOutcome', 179 + 'hit' 180 + ); 181 + expect(expected).toMatchObject(result.mock.calls[1][0].data); 182 + expect(result.mock.calls[1][0].data).toBe(result.mock.calls[0][0].data); 183 + expect(messages).toEqual([ 184 + 'No value for field "author" on entity "Query"', 185 + 'No value for field "author" with args {"id":"123"} on entity "Query"', 186 + ]); 187 + }); 188 + 113 189 it('respects cache-only operations', () => { 114 190 const client = createClient({ 115 191 url: 'http://0.0.0.0',
+24
exchanges/graphcache/src/operations/query.ts
··· 537 537 ctx.partial = true; 538 538 dataFieldValue = null; 539 539 } else if (dataFieldValue === null && directives.required) { 540 + if ( 541 + ctx.store.logger && 542 + process.env.NODE_ENV !== 'production' && 543 + InMemoryData.currentOperation === 'read' 544 + ) { 545 + ctx.store.logger( 546 + 'debug', 547 + `Got value "null" for required field "${fieldName}"${ 548 + fieldArgs ? ` with args ${JSON.stringify(fieldArgs)}` : '' 549 + } on entity "${entityKey}"` 550 + ); 551 + } 540 552 dataFieldValue = undefined; 541 553 } else { 542 554 hasFields = hasFields || fieldName !== '__typename'; ··· 551 563 } else if (deferRef) { 552 564 hasNext = true; 553 565 } else { 566 + if ( 567 + ctx.store.logger && 568 + process.env.NODE_ENV !== 'production' && 569 + InMemoryData.currentOperation === 'read' 570 + ) { 571 + ctx.store.logger( 572 + 'debug', 573 + `No value for field "${fieldName}"${ 574 + fieldArgs ? ` with args ${JSON.stringify(fieldArgs)}` : '' 575 + } on entity "${entityKey}"` 576 + ); 577 + } 554 578 // If the field isn't deferred or partial then we have to abort and also reset 555 579 // the partial field 556 580 ctx.partial = hasPartials;