-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Enable Fragment Arguments (a form of parameterized fragments) during execution #3152
Conversation
@IvanGoncharov would it be better to create a branch within There's some complexity in terms of validating the argument usage, given this is the first time we'll have arguments whose type is defined by a variable definition, and where those variable definitions are not visible on the operation. It might be better to have many people contribute to those new validation rules, and update existing validation rules, which I think would be easier from a repo branch (plus then I wouldn't clobber other people's commits, like I did with @daniel-nagy's initial work). |
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.
Really happy to see this progress. This is quite clever and I think a good way to thread the needle on complexity around variable scope. This PR scoped all variables at the operation level, and I know when we explored this years ago we struggled to answer how to scope variables if fragment variable definitions existed. This approach is a good compromise IMO: A variable is scoped to either an immediate fragment variable definition or an operation variable definition.
One high level feedback is naming these. Are these "Fragment Arguments" or "Fragment Variables"? Right now we interchange these terms and I think that's causing confusion. Though we provide these as arguments in a fragment spread, otherwise elsewhere they look and feel like variables - loosely held thought here, but maybe we should explore the "Fragment Variables" term.
Otherwise my feedback inline is pretty fine-grained around naming things.
The next major step is validation, which opens some questions
"Unique variable names" - should a fragment variable be able to name override another variable?
In other words, should this document be valid? And if it is valid, what do we consider the type of $var
in field(arg: $var)
?
query Example1($var: String!) {
...frag(var: $var)
}
fragment frag($var: String) on Query {
field(arg: $var)
}
…nt transform syntax into operations without fragment arguments, which are executable by all graphql.js versions See graphql/graphql-js#3152 for reference
…nt transform syntax into operations without fragment arguments, which are executable by all graphql.js versions See graphql/graphql-js#3152 for reference
…nt transform syntax into operations without fragment arguments, which are executable by all graphql.js versions See graphql/graphql-js#3152 for reference
The way I'm thinking of them is, when used within a fragment definition, they are "fragment variables", as they're variables defined on a fragment that can be used anywhere you'd use an "operation variable". However, when exposed for use on fragment spreads, they're "fragment arguments": just like a field defines arguments that can be fulfilled by either variable or explicit values, fragments now define arguments that can be fulfilled by explicit values. I think it would be less confusing (and easier to write validation for!) if we had the concept of "fragment interfaces" which a "Fragment implementation" can implement. The current syntax is basically creating this concept, but not explicitly defining it. So something like fragment Foo($v: Int) on User {
size(x: $v)
} Is really doing something like: fragment interface Foo(v: Int) on User;
fragment Foo($v: Int) on User {
size(x: $v)
} If we had the above syntax, you'd only need to re-check a fragment spread is correct when either the spread's argument changes, or the fragment interface changes, but not when the fragment implementation changes. In this world, it might be legal to do: fragment interface Foo(v: Int) on User;
fragment Foo on User {
size(x: 3)
} i.e. create an interface that expects a specific argument, then allow implementations to not use that argument at all. This is especially helpful if you want to be able to inject, at runtime, different values for the same fragment spread depending on what context you're in (this is something I have a lot of use for, but in JS land it's probably not very desired). |
…nt transform syntax into operations without fragment arguments, which are executable by all graphql.js versions See graphql/graphql-js#3152 for reference
* wip: implement parser extensions for transforming the fragment argument transform syntax into operations without fragment arguments, which are executable by all graphql.js versions See graphql/graphql-js#3152 for reference * fixes Co-authored-by: Dotan Simha <[email protected]>
Any progress updates on this? |
Any updates on this, especially for those who unfortunately missed GraphQL conf? |
Yeah the short of it is I'm back to iterating on this proposal. I think the core plan of attack is:
One hesitancy I have: we may want to enable "automatic threading", e.g. if you have:
My initial idea is that an explicitly defined fragment argument must be explicitly passed in from above, or else it is considered null. We could start by enforcing all fragment arguments must be explicitly set (even if you want them to be null), though I think this will get incredibly confusing when you start combining fragment arguments with default values. All of the above is to say: I should have another iteration of this PR up shortly. |
✅ Deploy Preview for compassionate-pike-271cb3 ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
7bade9d
to
e7908ef
Compare
975e051
to
33fd7fb
Compare
This PR is now very close to ready for review (review question, do people prefer stacked PRs, i.e. one PR per commit/idea, or PRs that have all the changes bundled together?) We currently have:
In graphql-js, I still need to:
And then I'll need to update the PR graphql/graphql-spec#865, present to the WG, and hopefully get feedback and keep iterating. |
1a5126b
to
042a56a
Compare
094d2d6
to
884251f
Compare
I've updated the changes to be what I think are the minimal needed to get the behavior and validation to match the updates described by the spec changes in graphql/graphql-spec#865 Some notes:
|
884251f
to
686fe56
Compare
`expect.fail()` was mistakenly introduced in graphql#3760 under the assumption that expect.fail() will always cause the test to fail, and that `.next()` should be called at most once. Actually, thought, `expect.fail()` just throws an error, `.next()` is called more than once, the error is produced and wrapped by GraphQL, but then it is filtered, so the test ultimately passes. Fortunately, that's actually the desired behavior! It's ok if the number of calls to `.next()` is more than 1 (in our case it is 2). The exact number of calls to `.next()` depends on the # of ticks that it takes for the erroring deferred fragment to resolve, which should be as low as possible, but that is a separate objective (with other PRs in the mix already aimed at reducing). So the test as written is intending to be overly strict, but is not actually meeting that goal and so functions as desires. This PR makes no change to the test semantics, but changes the comment, the coverage ignore statement, and removes the potentially confusing use of `expect.fail`, so that the test semantics are more clearly apparent.
Moving this years-old stack to #3835 as a way of cleaning up the implementation. I've also created a new discussion in |
* wip: implement parser extensions for transforming the fragment argument transform syntax into operations without fragment arguments, which are executable by all graphql.js versions See graphql/graphql-js#3152 for reference * fixes Co-authored-by: Dotan Simha <[email protected]>
This is really a continuation of @daniel-nagy's work in #2871 and, more distantly,
@sam-swarr's initial AST implementations in #1141, as well as @josephsavona and the entire Relay team's work from before graphql/graphql-spec#204 was opened.
Background
graphql/graphql-spec#204 is the original open source issue from ~5 years ago.
For the past 5+ years, Relay has had the
@arguments
directive, which is not spec compliant. In some sense, Relay is a dual GraphQL client: there's Relay syntax which is used to resolve data available locally on the client, and then that syntax compiles down into a spec compliant syntax to resolve data from an external source (aka a "server"), which hydrates a graph of "local" data the relay-specific resolvers operate over.This means Relay can get away with having user-written fragments that are freed from operation-defined knowledge: Relay's fragments can be provided variable values that were never defined at the operation level, to use when resolving arguments.
This PR: A graphql-js implementation of "fragment argument replacement", a la Relay
This enables executing requests that use fragment arguments: when collecting fields on fragment spreads, the executor now replaces any variables matching fragment argument values, with the rule:
Note that last point, without adding better validation rules, allows an operation defined variable to "clobber" a fragment variable that was purposefully left unset. I think this is undesirable behavior.
The two new runtime errors I added were:
null
is passed in at the argument, throw.With just these new errors, this behavior already ought to compose cleanly with other executor errors to disallow things like spreading the same fragment multiple times at the same level with different arguments: that would create overlapping fields. It also cleanly allows for variables to be passed in as fragment arguments.
In some sense, this is a baby step towards @IvanGoncharov's idea of adding an execution plan stage. It's unlikely you'd want to implement this execution plan using
visit
as I have here, but it works, and I'm trying to ensure this behavior won't add overhead unless you're actually using the new feature.Validation Required
IMO the executor behavior is too permissive for a first iteration on the spec: it will likely be difficult for existing clients to immediately support spreading the same fragment multiple times in the same operation with different argument values. So my plan is to add additional validation rules (will add on this PR):