Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identify written results using processed StoreObject in StoreWriter#processSelectionSet #8996

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Nov 1, 2021

This PR supersedes #8860, accomplishing the same goals and passing the same tests, using a different (but backwards-compatible) approach to identifying written result objects.

The crux of this approach is that keyFields extraction during policies.identify does not need to worry about field aliases if it operates on the fully de-aliased StoreObject that's about to be written into the EntityStore, rather than operating on the raw result object (which still contains aliases).

With this shift from identifying raw result objects to identifying processed StoreObjects, the AliasMap logic that previously complicated keyFieldsFnFromSpecifier is no longer necessary. That logic was essentially duplicating effort also performed by processSelectionSet during its recursive traversal of the selection set, so this new approach should be computationally simpler and faster, as well as easier to understand.

If you have any custom keyFields functions in the typePolicies you pass to InMemoryCache, the raw result object will still be passed as the first argument, to ensure backwards compatibility. However, you may want to consider using the KeyFieldsContext object passed as the second argument to your keyFields function, specifically context.storeObject and context.readField("someField", context.storeObject), instead of reading fields from the raw result object.

If you're using the more declarative keyFields: [...key field names...] syntax, you shouldn't need to change anything, since this syntax is now implemented in terms of context.storeObject behind the scenes.

As a bonus, it may finally be possible to use synthetic client-only fields as keyFields (potentially fixing #8939), since those fields are now extracted in a way that respects custom read functions, though I'm not sure I can recommend this as a best practice, since keyFields should ideally be cheap scalar fields. If you can think of any compelling use cases for this pattern, please drop a comment below.

The test changes were necessary because the expected keyFields
identification exception is now thrown after field traversal, allowing
missing field errors to be logged during traversal that previously were
silently hidden.
@benjamn benjamn self-assigned this Nov 1, 2021
Comment on lines 185 to 195
// Usually the extracted value will be a scalar value, since most primary
// key fields are scalar, but just in case we get an object or an array, we
// need to do some normalization of the order of (nested) keys.
if (isNonNullObject(value)) {
if (Array.isArray(value)) {
return value.map(normalize) as any;
}
return collectSpecifierPaths(
Object.keys(value).sort(),
path => extractKeyPath(value, path),
);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sorting of the keys of unexpected leaf objects is the part of this PR that fixes #8855, by ensuring both keyFields and keyArgs extraction produces canonical ID strings, even if the KeySpecifier array does not explicitly determine an order for all object keys.

That said, it's probably not a great idea in general to use objects or arrays as keyFields values, since they can change size and shape without warning, and entity ID strings are supposed to be relatively stable and cheap to compute.

package.json Outdated Show resolved Hide resolved
Comment on lines 361 to 386
const [id, keyObject] = policies.identify(result, {
selectionSet,
fragmentMap: context.fragmentMap,
storeObject: incoming,
readField,
});

// If dataId was not provided, fall back to the id just generated by
// policies.identify.
dataId = dataId || id;

// Write any key fields that were used during identification, even if
// they were not mentioned in the original query.
if (keyObject) {
// TODO Reverse the order of the arguments?
incoming = context.merge(incoming, keyObject);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to remove the keyObject logic here, but we have a number of tests where a key field (often id) is provided in the written data but not mentioned in the query/fragment, and this logic makes sure we write those key fields anyway (even though write execution is primarily driven by the structure of the query/fragment). We could warn about such situations (like we do in the opposite case, for fields missing from the result object but mentioned in the query/fragment), but I don't think we can change the behavior without a major version bump (that is, AC4).

Copy link
Contributor

@brainkim brainkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per usual, I’m not happy about the number of callbacks/higher-order functions in these changes, but I can tolerate it! Overall though it seems like a cleaner solution than the former one. I think some more accurate types would make these files easier to reason about.

src/cache/inmemory/key-extractor.ts Show resolved Hide resolved
src/cache/inmemory/key-extractor.ts Outdated Show resolved Hide resolved
src/cache/inmemory/key-extractor.ts Outdated Show resolved Hide resolved
src/cache/inmemory/key-extractor.ts Outdated Show resolved Hide resolved
src/cache/inmemory/key-extractor.ts Show resolved Hide resolved
src/cache/inmemory/key-extractor.ts Show resolved Hide resolved
@benjamn benjamn requested a review from brainkim November 3, 2021 19:49
Copy link
Contributor

@brainkim brainkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀🚀🚀

@benjamn benjamn force-pushed the identify-written-results-using-StoreObject-in-processSelectionSet branch from abccacc to 280ab72 Compare November 3, 2021 21:29
@benjamn benjamn merged commit b3a2461 into release-3.5 Nov 3, 2021
@benjamn benjamn deleted the identify-written-results-using-StoreObject-in-processSelectionSet branch November 3, 2021 21:43
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
2 participants