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): Allow cache.resolve to return undefined for uncached fields (#3333)

authored by

Phil Pluckthun and committed by
GitHub
68d02c1a a0741aad

+79 -43
+5
.changeset/thin-badgers-return.md
··· 1 + --- 2 + '@urql/exchange-graphcache': minor 3 + --- 4 + 5 + Allow `cache.resolve` to return `undefined` when a value is not cached to make it easier to cause a cache miss in resolvers. **Reminder:** Returning `undefined` from a resolver means a field is uncached, while returning `null` means that a field’s value is `null` without causing a cache miss.
+27 -14
docs/graphcache/local-resolvers.md
··· 60 60 docs](../api/graphcache.md#info). 61 61 62 62 The local resolvers may return any value that fits the query document's shape, however we must 63 - ensure that what we return matches the types of our schema. It for instance isn't possible to turn a 63 + ensure that what we return matches the types of our schema. It, for instance, isn't possible to turn a 64 64 record field into a link, i.e. replace a scalar with an entity. Instead, local resolvers are useful 65 65 to transform records, like dates in our previous example, or to imitate server-side logic to allow 66 66 Graphcache to retrieve more data from its cache without sending a query to our API. ··· 69 69 methods to read from our cache, only ["Cache Updates"](./cache-updates.md) get to write and change 70 70 the cache. If you call `cache.updateQuery`, `cache.writeFragment`, or `cache.link` in resolvers, 71 71 you‘ll get an error, since it‘s not possible to update the cache while reading from it. 72 + 73 + When writing a resolver you’ll mostly use `cache.resolve`, which can be chained, to read field 74 + values from the cache. When a field points to another entity we may get a key, but resolvers are 75 + allowed to return keys or partial entities containing keys. 76 + 77 + > **Note:** This essentially means that resolvers can return either scalar values for fields without 78 + > selection sets, and either partial entities or keys for fields with selection sets, i.e. 79 + > links / relations. When we return `null`, this will be interpreted a the literal GraphQL Null scalar, 80 + > while returning `undefined` will cause a cache miss. 72 81 73 82 ## Transforming Records 74 83 ··· 222 231 223 232 However sometimes we'll need to resolve data from other fields in our resolvers. 224 233 225 - For records, if the other field is on the same `parent` entity, it may seem logical to access it on 226 - `parent[otherFieldName]` as well, however the `parent` object will only be sparsely populated with 227 - fields that the cache has already queried prior to reaching the resolver. 228 - 229 - In the previous example, where we've created a resolver for `Todo.updatedAt` and accessed 230 - `parent.updatedAt` to transform its value the `parent.updatedAt` field is essentially a shortcut 231 - that allows us to get to the record quickly. 234 + > **Note:** For records, if the other field is on the same `parent` entity, it may seem logical to access it on 235 + > `parent[otherFieldName]` as well, however the `parent` object will only be sparsely populated with 236 + > fields that the cache has already queried prior to reaching the resolver. 237 + > In the previous example, where we've created a resolver for `Todo.updatedAt` and accessed 238 + > `parent.updatedAt` to transform its value the `parent.updatedAt` field is essentially a shortcut 239 + > that allows us to get to the record quickly. 232 240 233 241 Instead we can use [the `cache.resolve` method](../api/graphcache.md#resolve). This method 234 242 allows us to access Graphcache's cached data directly. It is used to resolve records or links on any ··· 261 269 262 270 When we call `cache.resolve(parent, "updatedAt")`, the cache will look up the `"updatedAt"` field on 263 271 the `parent` entity, i.e. on the current `Todo` entity. 264 - We've also previously learned that `parent` may not contain all fields that the entity may have and 265 - may hence be missing its keyable fields, like `id`, so why does this then work? 266 - It works because `cache.resolve(parent)` is a shortcut for `cache.resolve(info.parentKey)`. 272 + 273 + > **Note:** We've also previously learned that `parent` may not contain all fields that the entity may have and 274 + > may hence be missing its keyable fields, like `id`, so why does this then work? 275 + > It works because `cache.resolve(parent)` is a shortcut for `cache.resolve(info.parentKey)`. 267 276 268 277 Like the `info.fieldName` property `info.parentKey` gives us information about the current state of 269 278 Graphcache's query operation. In this case, `info.parentKey` tells us what the parent's key is. ··· 308 317 other entities ("links") instead. It can even give you arrays of keys or records when the field's 309 318 value contains a list. 310 319 311 - It's a pretty flexible method that allows us to access arbitrary values from our cache, however, we 312 - have to be careful about what value will be resolved by it, since the cache can't know itself what 313 - type of value it may return. 320 + When a value is not present in the cache, `cache.resolve` will instead return `undefined` to signal 321 + that a value is uncached. Similarly, a resolver may return `undefined` to tell Graphcache that the 322 + field isn’t cached and that a call to the API is necessary. 323 + 324 + `cache.resolve` is a pretty flexible method that allows us to access arbitrary values from our cache, 325 + however, we have to be careful about what value will be resolved by it, since the cache can't know 326 + itself what type of value it may return. 314 327 315 328 The last trick this method allows you to apply is to access arbitrary fields on the root `Query` 316 329 type. If we call `cache.resolve("Query", ...)` then we're also able to access arbitrary fields
+7 -1
docs/graphcache/normalized-caching.md
··· 393 393 object is keyable, it will tell _Graphcache_ what the key of the returned entity is. In other words, 394 394 we've told it how to get to a `Todo` from the `Query.todo` field. 395 395 396 - This mechanism is immensely more powerful than this example. We have two other use-cases that 396 + This mechanism is immensely more powerful than this example. We have other use-cases that 397 397 resolvers may be used for: 398 398 399 399 - Resolvers can be applied to fields with records, which means that it can be used to change or ··· 402 402 - Resolvers can return deeply nested results, which will be layered on top of the in-memory 403 403 relational cached data of _Graphcache_, which means that it can emulate infinite pagination and 404 404 other complex behaviour. 405 + - Resolvers can change when a cache miss or hit occurs. Returning `null` means that a field’s value 406 + is literally `null`, which will not cause a cache miss, while returning `undefined` will mean 407 + a field’s value is uncached. 408 + - Resolvers can return either partial entities or keys, so we can chain `cache.resolve` calls to 409 + read fields from the cache, even when a field is pointing at another entity, since we can return 410 + keys to the other entity directly. 405 411 406 412 [Read more about resolvers on the following page about "Local Resolvers".](./local-resolvers.md) 407 413
+2 -2
exchanges/graphcache/src/operations/shared.ts
··· 223 223 x == null ? null : (x as Data | NullArray<Data>); 224 224 225 225 export const ensureLink = (store: Store, ref: Link<Entity>): Link => { 226 - if (ref == null) { 227 - return ref; 226 + if (!ref) { 227 + return ref || null; 228 228 } else if (Array.isArray(ref)) { 229 229 const link = new Array(ref.length); 230 230 for (let i = 0, l = link.length; i < l; i++)
+4 -6
exchanges/graphcache/src/store/data.ts
··· 422 422 }; 423 423 424 424 const updateDependencies = (entityKey: string, fieldKey?: string) => { 425 - if (fieldKey !== '__typename') { 426 - if (entityKey !== currentData!.queryRootKey) { 427 - currentDependencies!.add(entityKey); 428 - } else if (fieldKey !== undefined) { 429 - currentDependencies!.add(joinKeys(entityKey, fieldKey)); 430 - } 425 + if (entityKey !== currentData!.queryRootKey) { 426 + currentDependencies!.add(entityKey); 427 + } else if (fieldKey !== undefined && fieldKey !== '__typename') { 428 + currentDependencies!.add(joinKeys(entityKey, fieldKey)); 431 429 } 432 430 }; 433 431
+18 -15
exchanges/graphcache/src/store/store.ts
··· 155 155 return globalID || !key ? key : `${typename}:${key}`; 156 156 } 157 157 158 - resolve(entity: Entity, field: string, args?: FieldArgs): DataField { 159 - const fieldKey = keyOfField(field, args); 158 + resolve( 159 + entity: Entity, 160 + field: string, 161 + args?: FieldArgs 162 + ): DataField | undefined { 163 + let fieldValue: DataField | undefined = null; 160 164 const entityKey = this.keyOfEntity(entity); 161 - if (!entityKey) return null; 162 - const fieldValue = InMemoryData.readRecord(entityKey, fieldKey); 163 - if (fieldValue !== undefined) return fieldValue; 164 - const link = InMemoryData.readLink(entityKey, fieldKey); 165 - return link || null; 165 + if (entityKey) { 166 + const fieldKey = keyOfField(field, args); 167 + fieldValue = InMemoryData.readRecord(entityKey, fieldKey); 168 + if (fieldValue === undefined) 169 + fieldValue = InMemoryData.readLink(entityKey, fieldKey); 170 + } 171 + return fieldValue; 166 172 } 167 173 168 174 resolveFieldByKey(entity: Entity, field: string, args?: FieldArgs) { ··· 248 254 link( 249 255 entity: Entity, 250 256 field: string, 251 - argsOrLink: FieldArgs | Link<Entity>, 252 - maybeLink?: Link<Entity> 257 + ...rest: [FieldArgs, Link<Entity>] | [Link<Entity>] 253 258 ): void { 254 - const args = (maybeLink !== undefined ? argsOrLink : null) as FieldArgs; 255 - const link = ( 256 - maybeLink !== undefined ? maybeLink : argsOrLink 257 - ) as Link<Entity>; 258 - const entityKey = ensureLink(this, entity); 259 - if (typeof entityKey === 'string') { 259 + const args = rest.length === 2 ? rest[0] : null; 260 + const link = rest.length === 2 ? rest[1] : rest[0]; 261 + const entityKey = this.keyOfEntity(entity); 262 + if (entityKey) { 260 263 InMemoryData.writeLink( 261 264 entityKey, 262 265 keyOfField(field, args),
+16 -5
exchanges/graphcache/src/types.ts
··· 131 131 * as retrieved for instance by {@link Cache.keyOfEntity} or a partial GraphQL object 132 132 * (i.e. an object with a `__typename` and key field). 133 133 */ 134 - export type Entity = null | Data | string; 134 + export type Entity = undefined | null | Data | string; 135 135 136 136 /** A key of an entity, or `null`; or a list of keys. 137 137 * ··· 284 284 * If it’s passed a `string` or `null`, it will simply return what it’s been passed. 285 285 * Objects that lack a `__typename` field will return `null`. 286 286 */ 287 - keyOfEntity(entity: Entity): string | null; 287 + keyOfEntity(entity: Entity | undefined): string | null; 288 288 289 289 /** Returns the cache key for a field. 290 290 * ··· 327 327 * ); 328 328 * ``` 329 329 */ 330 - resolve(entity: Entity, fieldName: string, args?: FieldArgs): DataField; 330 + resolve( 331 + entity: Entity | undefined, 332 + fieldName: string, 333 + args?: FieldArgs 334 + ): DataField | undefined; 331 335 332 336 /** Returns a cached value on a given entity’s field by its field key. 333 337 * 334 338 * @deprecated 335 339 * Use {@link cache.resolve} instead. 336 340 */ 337 - resolveFieldByKey(entity: Entity, fieldKey: string): DataField; 341 + resolveFieldByKey( 342 + entity: Entity | undefined, 343 + fieldKey: string 344 + ): DataField | undefined; 338 345 339 346 /** Returns a list of cached fields for a given GraphQL object (“entity”). 340 347 * ··· 372 379 * However, if a field name (and optionally, its arguments) are passed, 373 380 * only a single field is erased. 374 381 */ 375 - invalidate(entity: Entity, fieldName?: string, args?: FieldArgs): void; 382 + invalidate( 383 + entity: Entity | undefined, 384 + fieldName?: string, 385 + args?: FieldArgs 386 + ): void; 376 387 377 388 /** Updates a GraphQL query‘s cached data. 378 389 *