Inflect DataID field from Node interface.#2249
Conversation
55f65a2 to
705e7a2
Compare
|
|
||
| const nextID = | ||
| item.id || | ||
| (field.idField && item[field.idField]) || |
There was a problem hiding this comment.
These two changes are the only runtime changes.
|
@josephsavona I got a couple of notification emails from GH with comments from you on this PR, but I don’t see them here. Is it a GH fluke or did you remove the comments? |
|
Sorry about that. I meant to start a code review and accidentally posted not fully thought out comments instead - will take another look when I have computer time (holidays, baby, etc). |
|
Oh no worries at all, I just got confused. Please enjoy your holidays and baby instead! 🙏 This can wait. |
2cc1e49 to
2ca1486
Compare
|
Rebased on master. |
| node.idField = idFieldSelection && idFieldSelection.name; | ||
| } | ||
| return node; | ||
| }, |
There was a problem hiding this comment.
@josephsavona I think in your retracted initial review you said that this should become its own transformer, right? I think that makes sense, it’s kinda awkward here.
There was a problem hiding this comment.
Yeah this should be its own transform rather than happening at codegen time
josephsavona
left a comment
There was a problem hiding this comment.
I will leave a real review to @kassens and others since I'm no longer working on Relay. Other than the comment about splitting out transform-like code from codegen into a transform, the other thing that occurs to me here is the perf impact: adding an extra property (that is almost always the same redundant "id" value) to tons of fields increases the size of query modules, and it also incurs an extra condition/branch at runtime when reading.
| node.idField = idFieldSelection && idFieldSelection.name; | ||
| } | ||
| return node; | ||
| }, |
There was a problem hiding this comment.
Yeah this should be its own transform rather than happening at codegen time
Ah yes, good point. I hadn’t considered bundle size enough, probably because 99% of our Relay code thus far exists in our mobile app. If that turns out to be a major concern, then we can just special-case
Related to the previous item, I think that even when reverting to special-casing the |
2ca1486 to
3e3decb
Compare
leebyron
left a comment
There was a problem hiding this comment.
This looks cool! Some suggestions to continue:
-
This should be an extension of the existing transform which adds id fields
-
Rather than having to track
idFieldon the resulting IR, you should use aliases, so that relay always knows to look for a specific response field that represents IDs. -
I'd suggest looking for
IDtype in addition toID!.
Also, a question. If a type has more than one fields with type ID!, what should be the right behavior?
I like the simplicity of that idea 👌 What name would you suggest, is this where we use a name like
The Global ID spec mentions that this field is supposed to be non-null, but that’s probably more because of how Relay Classic was supposed to be able to refetch using that ID, rather it being needed for cache denormalisation (indeed Relay Modern will generate a client ID if the ID is missing from the response)? If so, sounds good to me 👍
A type may, but the The logic in this PR is:
|
3e3decb to
6fb1d8d
Compare
|
@leebyron Ok, I think I addressed all your feedback, I’ve updated the original explanation to reflect this. The overall implementation is definitely a lot simpler 👌 There’s a fork of the TODO example app that uses this here https://github.com/alloy/relay-examples/tree/custom-node-id/todo and we’re pulling this into our app for further testing as well artsy/emission#985. |
4098931 to
ee71c36
Compare
|
Rebased on master. |
|
@leebyron Could I get another review? |
ee71c36 to
b5e4b8f
Compare
|
I’ve gone ahead and squashed all the commits now to ease rebasing on my end. I’ve also rebased, but alas can’t run the tests at the moment due to the issue described in #2407 |
b5e4b8f to
3ca8f8c
Compare
|
Tests pass again 👍 |
jstejada
left a comment
There was a problem hiding this comment.
@alloy going to import this to test internally
Regarding this earlier comment:
If that turns out to be a major concern, then we can just special-case id and revert to the previous behaviour of not emitting idField and hardcoding checks for id at runtime.
Just want to double check, did you end up doing this? It looks like you did, but still always alias to __id?
| ## Comparison to Classic Relay | ||
|
|
||
| For users of classic Relay, note that the runtime makes as few assumptions as possible about GraphQL. Compared to earlier versions of Relay there is no concept of routes, there are no limitations on mutation input arguments or side-effects, arbitrary root fields just work, etc. At present, the main restriction from classic Relay that remains is the use of the `Node` interface and `id` field for object identification. However there is no fundamental reason that this restriction can't be relaxed (there is a single place in the codebase where object identity is determined), and we welcome feedback from the community about ways to support customizable object identity without negatively impacting performance. | ||
| For users of classic Relay, note that the runtime makes as few assumptions as possible about GraphQL. Compared to earlier versions of Relay there is no concept of routes, there are no limitations on mutation input arguments or side-effects, arbitrary root fields just work, etc. Like classic Relay, modern Relay still uses the `Node` interface for object identification purposes; however, rather than always assuming the globally unique field to be `id`, modern Relay will inflect it from the `Node` interface by searching for either an `ID` or `ID!` field. |
There was a problem hiding this comment.
nit: can we use a different term than inflect, or not use it at all? (same for the title of this PR) I might just be me as a non-native english speaker, but I didn't know what inflect means, and googling the definition didn't really help until I actually looked at what this PR was doing.
maybe something like: "Relay Modern will find a field of type ID or ID! defined in the Node interface and use it as a globally unique ID"
| RelayRelayDirectiveTransform.transform, | ||
| RelayMaskTransform.transform, | ||
| RelayDeferrableFragmentTransform.transform, | ||
| RelayGenerateIDFieldTransform.transform, |
There was a problem hiding this comment.
sorry I'm not super familiar with the compiler code. If we weren't using this transform here before, where were we using it?
| * A transform that adds an `id` field on any type that has an id field but | ||
| * where there is no unaliased `id` selection. | ||
| * A transform that adds a `__id` field on any type that has a `Node` or `id` | ||
| * field but where there is no unaliased `__id` selection. |
There was a problem hiding this comment.
so we'll always add an __id selection, unless there's already an unaliased __id selection.
what happens when there's already an aliased __id selection?, will we add another __id selection too?
| // If the field already has an unaliased `id` field, do nothing | ||
| if (hasUnaliasedSelection(field, ID)) { | ||
| // If the field already has an `__id` selection, do nothing. | ||
| if (hasSelection(node, '__id')) { |
There was a problem hiding this comment.
answering my own question from above, it seems that this won't add an __id selection if a selection for __id already exists, be it aliased or unaliased. Maybe update the comment above to reflect that?
| return IRTransformer.transform( | ||
| context, | ||
| { | ||
| LinkedField: visitLinkedField, |
There was a problem hiding this comment.
sorry I'm probably missing some context, why did we previously only apply this transform to LinkedFields, and now we do so for Fragments too?
| // implement `Node` | ||
| // - If the field type is abstract, then generate a `... on Node { __id: id }` | ||
| // fragment if *any* concrete type implements `Node`. Then generate a | ||
| // `... on PossibleType { __id: id }` for every concrete type that does |
There was a problem hiding this comment.
what if PossibleType doesn't have an id field? can we add test coverage for this?
facebook-github-bot
left a comment
There was a problem hiding this comment.
@jstejada has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
|
New TODOs
|
* The ID is either the single `ID` field of the `Node` interface in the user’s schema or otherwise falls back to `id`. * The ID field is selected using the `__id` alias.
3ca8f8c to
20eeddb
Compare
|
This rebased version has a couple of failing tests, that I have left in as a good reminder of what to work on and I also need to address @jstejada’s feedback, but for now I want to first see if it actually works in our apps. |
facebook-github-bot
left a comment
There was a problem hiding this comment.
@jstejada has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
|
What is the current progress on this issue? Is there any way one could help to advance this PR? |
|
This patch has served us well, but we’ve decided that we are going to sunset this PR. The reasons are:
In the end, we decided to start experimentation with a v2 of our schema in which we transform v1 to rename all of our ID fields to conform to the Node spec. Things are looking good thus far and we’ve been able to upgrade to Relay 4 easily. |
|
@clentfort What a coincidence that you would ask that on the day that I was planning on sunsetting this PR 😞 If you are interested in taking a solution like this to the finish line, some work has been done to being able to provide a custom DataID value at runtime. However, unless your DataID would rely on |
This PR makes it possible to inflect the
DataIDfield to use from the user’s schema, rather than assuming it to beid.How
IDfield of theNodeinterface in the user’s schema or otherwise falls back toid.__idalias.__idfield in the response data is used as storeDataID.Example
If your schema looks like this:
…and you have a query like:
{ artist(slug: "banksy") { name } }…then Relay will transform that to:
{ artist(slug: "banksy") { __id: globalObjectID name } }…and use the value of
__idas the key when writing the record to the store.