Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rework traversal strategy in Policies#applyMerges. (#5880)
Previously, I thought it was important to process any child object fields with custom merge functions before processing their parents, which required a post-order traversal of the result tree (in other words, calling policies.applyMerges recursively on incoming.__value *before* invoking the custom merge function). I have come to realize this was a mistake, for a few different and somewhat subtle reasons. First, a merge function should be able to return merged data in any format (specified by the TExisting type), but it should also be able to assume the incoming data has not been modified by merge functions. Reasoning about data types is much simpler if the incoming data is always just the type of data returned by the GraphQL server/schema (StoreValue, most generally), and the existing type is always some other type chosen by the developer (TExisting), possibly but not necessarily the same as the schema type (for example, an indexed lookup table, or an ordered history of results). Calling policies.applyMerges recursively *before* invoking the merge function violated this assumption, because nested incoming data could be turned into TExisting data before the merge function saw it. Note: this is not just a case of trying to make the type system happy, because TypeScript's flexible type inference isn't powerful enough to provide any reliable warnings or errors. A stronger static type system would make enforcement easier, but even in a completely dynamic language this kind of type-level reasoning would still be important, because it has actual semantic implications. Second, there's no universal way for the cache to merge arrays automatically (should it replace, concatenate, and/or deduplicate? what if the other value is not an array?), so blindly calling policies.applyMerges on array values before the merge function had a chance to process them was a risky plunge. This commit severs the false linkage between items in different arrays that happen to have the same index. Third, I have been hoping to provide an options.merge helper function to facilitate forced merges of unidentified data, like { ...existing, ...incoming } but with appropriate regard for custom merge functions. Unfortunately, the risk of repeatedly merging already merged data would have required that all merge functions be idempotent (a useful quality for most merge functions to have, but not always possible). I feared that implementing options.merge was going to require a new field function in addition to read and merge (like merge but taking two TExisting values and merging them idempotently), but thankfully the changes in this commit allow us to implement that functionality with just read and merge.
- Loading branch information