-
Notifications
You must be signed in to change notification settings - Fork 58
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
feat: Specify caching fields with typePolicy directive #554
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -239,16 +239,20 @@ public final class GraphQLObjectType: GraphQLCompositeType, GraphQLInterfaceImpl | |
public private(set) var fields: [String: GraphQLField]! | ||
|
||
public private(set) var interfaces: [GraphQLInterfaceType]! | ||
|
||
public private(set) var keyFields: [String]! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assumed we would implement this by adding The way you are implementing this, we do that logic in the There is a possibility of using the If a type is added to a schema after the client has been shipped, but it implements an interface that had a defined This also runs into some minor edge cases (eg. if the type implements multiple interfaces that have conflicting We also wouldn't be able to actually know all of the interface types that an unknown type implemented. We could only infer that if a type with an unknown This is a super niche and likely very rare edge case, but I think it is enough of a consideration that we should not handle computing cache keys for unknown types via inheritance of an interface's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Potential counter argument: interface Node @typePolicy(keyFields: "id") {
id: ID!
} In schemas with global ids, that's a convenient way do add FWIW, Kotlin currently keep tracks of key fields on interfaces at runtime too. Also because we don't have a global "typename -> object" dictionnary at runtime and didn't want to introduce it just for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extend interface Node @typePolicy(keyFields: "id") Every type I want to use in my specific iOS project has a base interface with the ID field, so it's important to me that the directive works on interfaces. If a GQL server introduces a new type for an old endpoint used by a Client app that isn't up to date, it will now just miss the directive because the Object declaration doesn't exist. It's still possible to cache it anyway using Programmatic Cache Keys. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be addressed in a separate PR? If yes, should it be done before or after merging this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if we aren't handling all the cases of "unknown types" this is important because we are currently building a feature to allow many concrete types to not be generated (if they aren't directly referenced, but only can be returned for a field from an interface). So this means that ALOT of known types would not normalize properly as well, as they would appear at run-time to be unrecognized types. I really would have loved for us to be able to solve the issues with handling unknown types here in a more elegant way, but for now, I think this is what we need to do. Which, to be fair, is actually just mirroring what Apollo Kotlin does currently, and they haven't seen a lot of issues come up on that side. The only thing that's not working properly here is handling cache normalization for an unknown (or not-generated) type that, conforms to two interfaces with conflicting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1. Also about this part:
Longer term, we could add linting/schema checks to catch that before the schema is actually put in production. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR already throws an error if any type is found that has conflicting typePolicies through its inherited interfaces. As soon as the schema is updated, any new conflict will become apparent. The fact that it'll use the typePolicy of just one of the interfaces until then is unfortunate, but IMHO very unlikely. I'm not aware of what the changes look like when Apollo iOS generates less code, but if the entire schema is still validated in the Javascript/IR step this will still catch conflicting policies (regardless of their usage by the client). I can update this PR to add typePolicy information to the Interfaces as well as the Objects, so it will also run the above validation on interfaces without (known) object types. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've now added keyFields to the generated Interface instances. |
||
|
||
/// Initializer to be used for creating mock objects in tests only. | ||
init( | ||
name: GraphQLName, | ||
documentation: String?, | ||
fields: [String: GraphQLField], | ||
interfaces: [GraphQLInterfaceType] | ||
interfaces: [GraphQLInterfaceType], | ||
keyFields: [String] | ||
) { | ||
self.fields = fields | ||
self.interfaces = interfaces | ||
self.keyFields = keyFields | ||
super.init(name: name, documentation: documentation) | ||
} | ||
|
||
|
@@ -259,6 +263,7 @@ public final class GraphQLObjectType: GraphQLCompositeType, GraphQLInterfaceImpl | |
override func finalize(_ jsValue: JSValue, bridge: isolated JavaScriptBridge) { | ||
self.fields = try! bridge.invokeMethod("getFields", on: jsValue) | ||
self.interfaces = try! bridge.invokeMethod("getInterfaces", on: jsValue) | ||
self.keyFields = jsValue["_apolloKeyFields"] | ||
} | ||
|
||
public override var debugDescription: String { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the directive to one of the schemas, but I didn't regenerate the example sources since that would add another 100 files to the Pull Request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine while we do code review. But we should go ahead and generate those files and verify they look correct and compile prior to merging.