-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Type refinement uses per-type metadata (previously per-record)
Summary: Changes from storing information about whether a *record* conforms to a given interface/union, to instead storing information about whether a given *concrete type* conforms to a given interface/union. The main advantage of this new approach is that we will have fewer cases where we're missing type discriminators - if we've ever seen a type/interface refinement on one record, we can reuse that information for all other records of the same type. For example, imagine that a client executes the following query: ``` query UrlQuery($id: ID!) { node(id: $id) { ... on Entity { url } } } ``` For id=X, and the cache contains ``` root: Record { ... node(id:"X"): Ref("X") } X: Record { __id: "X", __typename: "Story", id: "X", url: "https://..." } ``` Previously, because the `__isEntity` key was missing on the *record*, this query would be considered to have missing data, even though the record has a `url` value. With the new change, if we've already learned that `Story` implements `Entity`, we know that this record does too, and can correctly report that the query can be fulfilled from cache and isn't missing data. == Tradeoffs The approach here for storing type/interface conformance metadata is to reuse records, creating a record per concrete type, and storing interface conformance as fields on those type records. The upside of this is that all our existing logic works: new type metadata is merged with existing metadata, unneeded type metadata can be GC'd, etc. The downside is that to be precise, Reader should record that it has "seen" the type schema records. So with a fragment like: ``` fragment StoryHeader on Story { id name ... on Entity { url } } ``` This means that *every* subscription to StoryHeader will subscribe to *all* changes to the `Story` type schema record, in addition to their specific Story records. Any change to the Story type schema record (say we learn that Story conforms to the Foo interface) will cause us to re-evaluate *all* StoryHeaders fragments, even though they aren't likely to change. Possible alternatives: * Instead of creating type schema records *per-type*, create them *per type/interface pair*. So instead of having a `User`-type schema record with fields per interface, we'd have a `User/Entity` type schema record with a single `implements: boolean` field on it. This would avoid having to reread fragments in most cases, since the `implements` value would never change in practice for a given type/interface pair. * Another approach is to just assume that type/interface conformance will never change over the course of the session, and *not* record a dependency on the type schema record. == What About Updates There's also a question of whether we should notify fragment subscriptions if isMissingData changes. Right now we *only* notify if the `data` field changes, but it's possible that data could be the same while isMissingData changes (for example if we didn't have a type discriminator and it gets loaded later). Overall not notifying of isMissingData changes seems fine in practice but not ideal (though note that there is precedent for not notifying of all changes to a Snapshot - we don't notify if data has no changes but seenRecords does change). == Approach Given those constraints I think the most reasonable option is what's implemented here: * Store type/interface conformance per-type (so there's a single record for e.g. `User` with fields for each interface we refine it to) * Don't publish fragment subscription updates that only affect isMissingData (achieved by having reader look at the type schema record, without recording a dependency on it). Reviewed By: kassens Differential Revision: D21663869 fbshipit-source-id: 3faa602f90171295a5d857df400e4e60273aac54
- Loading branch information
1 parent
e913579
commit 2cf9ac9
Showing
8 changed files
with
295 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow strict | ||
* @format | ||
*/ | ||
|
||
// flowlint ambiguous-object-type:error | ||
|
||
'use strict'; | ||
|
||
import type {DataID} from '../util/RelayRuntimeTypes'; | ||
|
||
const PREFIX = 'client:__type:'; | ||
const TYPE_SCHEMA_TYPE = '__TypeSchema'; | ||
|
||
function generateTypeID(typeName: string): DataID { | ||
return PREFIX + typeName; | ||
} | ||
|
||
function isTypeID(id: DataID): boolean { | ||
return id.indexOf(PREFIX) === 0; | ||
} | ||
|
||
module.exports = {generateTypeID, isTypeID, TYPE_SCHEMA_TYPE}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.