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.

fix(graphcache): Replace default optional/required directive implementation (#3341)

authored by

Phil Pluckthun and committed by
GitHub
438d75fe 25a34c64

+28 -43
+5
.changeset/hot-sloths-look.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Replace implementation for `@_optional` and `@_required` with built-in handling inside cache reads to allow `@_optional` to work for nested selection sets.
-21
exchanges/graphcache/src/helpers/defaultDirectives.ts
··· 1 - import type { Resolver, DirectivesConfig } from '../types'; 2 - 3 - const optional: Resolver = (_parent, _args, cache, info) => { 4 - const result = cache.resolve(info.parentKey, info.parentFieldKey); 5 - if (result === undefined) { 6 - info.partial = true; 7 - return null; 8 - } else { 9 - return result; 10 - } 11 - }; 12 - 13 - const required: Resolver = (_parent, _args, cache, info) => { 14 - const result = cache.resolve(info.parentKey, info.parentFieldKey); 15 - return result == null ? undefined : result; 16 - }; 17 - 18 - export const defaultDirectives: DirectivesConfig = { 19 - optional: () => optional, 20 - required: () => required, 21 - };
+22 -19
exchanges/graphcache/src/operations/query.ts
··· 298 298 }; 299 299 300 300 function getFieldResolver( 301 - ctx: Context, 302 - node: FormattedNode<FieldNode>, 301 + directives: ReturnType<typeof getDirectives>, 303 302 typename: string, 304 - fieldName: string 303 + fieldName: string, 304 + ctx: Context 305 305 ): Resolver | void { 306 306 const resolvers = ctx.store.resolvers[typename]; 307 307 const fieldResolver = resolvers && resolvers[fieldName]; 308 - const directives = getDirectives(node); 309 308 310 309 let directiveResolver: Resolver | undefined; 311 310 for (const name in directives) { ··· 398 397 const fieldName = getName(node); 399 398 const fieldArgs = getFieldArguments(node, ctx.variables); 400 399 const fieldAlias = getFieldAlias(node); 400 + const directives = getDirectives(node); 401 + const resolver = getFieldResolver(directives, typename, fieldName, ctx); 401 402 const fieldKey = keyOfField(fieldName, fieldArgs); 402 - const resolver = getFieldResolver(ctx, node, typename, fieldName); 403 403 const key = joinKeys(entityKey, fieldKey); 404 404 const fieldValue = InMemoryData.readRecord(entityKey, fieldKey); 405 405 const resultValue = result ? result[fieldName] : undefined; ··· 513 513 // Now that dataFieldValue has been retrieved it'll be set on data 514 514 // If it's uncached (undefined) but nullable we can continue assembling 515 515 // a partial query result 516 - if (dataFieldValue === undefined && deferRef) { 517 - // The field is undelivered and uncached, but is included in a deferred fragment 518 - hasNext = true; 519 - } else if ( 516 + if ( 517 + !deferRef && 520 518 dataFieldValue === undefined && 521 - ((store.schema && isFieldNullable(store.schema, typename, fieldName)) || 522 - !!getFieldError(ctx)) 519 + (directives.optional || 520 + !!getFieldError(ctx) || 521 + (store.schema && isFieldNullable(store.schema, typename, fieldName))) 523 522 ) { 524 523 // The field is uncached or has errored, so it'll be set to null and skipped 525 524 ctx.partial = true; 526 525 dataFieldValue = null; 527 - } else if (dataFieldValue === undefined) { 528 - // If the field isn't deferred or partial then we have to abort and also reset 529 - // the partial field 530 - ctx.partial = hasPartials; 531 - ctx.__internal.path.pop(); 532 - return undefined; 526 + } else if (dataFieldValue === null && directives.required) { 527 + dataFieldValue = undefined; 533 528 } else { 534 - // Otherwise continue as usual 535 529 hasFields = hasFields || fieldName !== '__typename'; 536 530 } 537 531 ··· 539 533 ctx.__internal.path.pop(); 540 534 // Check for any referential changes in the field's value 541 535 hasChanged = hasChanged || dataFieldValue !== input[fieldAlias]; 542 - if (dataFieldValue !== undefined) output[fieldAlias] = dataFieldValue; 536 + if (dataFieldValue !== undefined) { 537 + output[fieldAlias] = dataFieldValue; 538 + } else if (deferRef) { 539 + hasNext = true; 540 + } else { 541 + // If the field isn't deferred or partial then we have to abort and also reset 542 + // the partial field 543 + ctx.partial = hasPartials; 544 + return undefined; 545 + } 543 546 } 544 547 545 548 ctx.partial = ctx.partial || hasPartials;
+1 -3
exchanges/graphcache/src/store/store.ts
··· 19 19 } from '../types'; 20 20 21 21 import { invariant } from '../helpers/help'; 22 - import { defaultDirectives } from '../helpers/defaultDirectives'; 23 22 import { contextRef, ensureLink } from '../operations/shared'; 24 23 import { _query, _queryFragment } from '../operations/query'; 25 24 import { _write, _writeFragment } from '../operations/write'; ··· 63 62 if (!opts) opts = {} as C; 64 63 65 64 this.resolvers = opts.resolvers || {}; 66 - this.directives = 67 - { ...defaultDirectives, ...opts.directives } || defaultDirectives; 65 + this.directives = opts.directives || {}; 68 66 this.optimisticMutations = opts.optimistic || {}; 69 67 this.keys = opts.keys || {}; 70 68