-
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
ease upgrade path for programmatic default values #4296
base: main
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for compassionate-pike-271cb3 ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Hi @yaacovCR, I'm @github-actions bot happy to help you with this PR 👋 Supported commandsPlease post this commands in separate comments and only one per comment:
|
To summarize, cross-posting from #4288 (comment):In v16, the available default value option on In v17 currently, prior to the proposed #4296, we adopted @leebyron 's work and replaced that with two options, After #4296, we have both. On the config, to disambiguate, we use Converting from the old to new and vice a versaCoercing the external values for the defaults to the input values can be done easily using the If you want to safely turn an internal value into something external, we have no function to do this in v16 or v17. But if you want, you can do what's done unsafely in v16 for introspection, you can use |
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.
Not a big fan of externalDefaultValue
, generally just not a big fan of giving multiple options to achieve the same thing. Even if this eases the upgrade path we are just multiplying the combinations
Strong agreement in terms of the long term value of having a single way of specifying options. The addition of The other benefit of this is that it is nice to mark the option within the option name as external, so as to perhaps prevent confusion in the future. For context, the default value work was originally a collaboration between @leebryon and @andimarek => of note, when graphql-java implemented this (in 2021!!!) looks to me like they also preserved for now the ability to set the default value as internal: public class InputValueWithState {
private enum State {
/**
* Value was never set aka not provided
*/
NOT_SET,
/**
* The value is an Ast literal
*/
LITERAL,
/**
* The value is an external value
*/
EXTERNAL_VALUE,
/**
* This is only used to preserve backward compatibility (for now): it is a value which is assumed to
* be already coerced.
* This will be removed at one point.
*/
INTERNAL_VALUE
}
... (With that option preserved until today!) Basically, I see your point, and for that reason I am closing #4303 => where it's significantly confusing to do, we should not give multiple options, even to ease the upgrade path. But I think in this case, the benefits outweigh the hopefully temporary additional complexity. But maybe it might be good to loop in @hayes or @ardatan. I think this PR would help make it possible for pothos and maybe in graphql-tools to bring graphql v17 to their respective libraries without a breaking change, which might make help the ecosystem overall move forward. |
@yaacovCR as a GraphQL Java inside: we simply decided it is to painful for the ecosystem to break all use case where they use the "old" way. We really try to hard to minimize the impact of breaking changes and often we decide to support the old way as much as possible. I am not sure if we will ever clean that up tbh. |
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 think this change would enable pothos users to more safely upgrade to v17 without changes in Pothos.
I am less sure if this would allow pothos to support new v17 features in a backwards compatible way (ie. without releasing a new major version). This is probably fine though, and major bumps (with small migrations) in pothos for major graphql releases is probably reasonable.
The biggest issue on the Pothos side is that Pothos does not have a way to safely type the new externalDefaultValue
, because all Pothos types are based in the 'internal input" and "internal result" types. So far, pothos has never considered "external" or "serialized" types anywhere in it's api.
I think pothos will likely do the following:
- add a way to provide an "external" type for scalars (defaulting to be the same as the internal type
- continue to support
defaultValue
typed based on the internal input type (marked as deprecated)- In v18, we would likely do the unsafe
coerceOutputValue
which should work for most scalars
- In v18, we would likely do the unsafe
- add support for
externalDefaultValue
in config
Some thoughts/questions I had:
- From a spec perspective, are the types of a external input and output types independent? And is this something Pothos should take into account when letting users define external types for their custom scalars
- Is there a way that Pothos can detect the version of GraphQL being used? It might be helpful if the graphql package exported a version or other indicator that could be used that doesn't require doing some sort of feature detection.
- Are there any real world examples where using coerceExternalValue would fail, or is it only unsafe because the input and result types (both internal, and external) are theoretically fully independent?
- File scalars are one that comes to mind here
@@ -1069,7 +1069,9 @@ export interface GraphQLArgumentExtensions { | |||
export interface GraphQLArgumentConfig { | |||
description?: Maybe<string>; | |||
type: GraphQLInputType; | |||
/** @deprecated use externalDefaultValue instead, defaultValue will be removed in v17 **/ |
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.
this will be removed in v18 right?
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.
yes, typo, thanks!
defaultValue: this.defaultValue?.value, | ||
defaultValueLiteral: this.defaultValue?.literal, | ||
defaultValue: this.defaultValue, | ||
externalDefaultValue: this.externalDefaultValue?.value, |
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.
externalDefaultValue
seems to be an overloaded term now. I understand why we want to unify the value and literal in one property on the instance, but have them separate on the config, but having a properties with the same name on the instance and config, with different types seems more confusing.
Would using something lie instance.externalDefault?: { value: unknown } | { literal: ConstValueNode }
work here? Not sure if externalDefault
is too generic.
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 like instance.externalDefault?: { value: unknown } | { literal: ConstValueNode }
or even instance.default?: { value: unknown } | { literal: ConstValueNode }
as we are going to retire defaultValue
@@ -1927,7 +1939,9 @@ export interface GraphQLInputFieldExtensions { | |||
export interface GraphQLInputFieldConfig { | |||
description?: Maybe<string>; | |||
type: GraphQLInputType; | |||
/** @deprecated use externalDefaultValue instead, defaultValue will be removed in v17 **/ |
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.
v18?
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 think the spec section that might describe this best would be somewhere her: It is my understanding that the the external scalar type is independent of its position in terms of input vs output, but the coercion rules are not necessarily always inversible, hence the problem of supplying default values in terms of the internal coerced types for input.
we have over here a string and a semver info object: https://github.com/graphql/graphql-js/blob/16.x.x/src/version.ts
This is the main one that I have come across. I think the blast radius of this change may be smaller than I think. There is the example from #3051 but I think there the schema representation is off, but execution would still be correct. |
I see this code in type-options.ts: export interface ScalarTypeOptions<
Types extends SchemaTypes = SchemaTypes,
ScalarInputShape = unknown,
ScalarOutputShape = unknown,
> extends BaseTypeOptions<Types> {
// Serializes an internal value to include in a response.
serialize: (outputValue: ScalarOutputShape) => unknown; // <====== This unknown really could be the external type?
// Parses an externally provided value to use as an input.
parseValue?: GraphQLScalarValueParser<ScalarInputShape>;
// Parses an externally provided literal value to use as an input.
parseLiteral?: GraphQLScalarLiteralParser<ScalarInputShape>;
} I think you can change the I think! Not sure if the above is correct and/or helps, but pothos is cool. :) |
Glad you like it! The builder needs to know the types of all the scalars without referencing the implementation, so that fields can be typed correctly. Today you specify an input and an output type, but I think we'll just have to extend that with an external type that can be used for defaults. |
Ah. I was assuming that the external type could be inferred from the serialize/coerceOutputValue method return type…. |
#3814 added default value validation and coercion, deprecating
astFromValue()
(which was unsafe, usedserialize()
to uncoerce input values provided in the internal format) and replacing it with a call tovalueToLiteral()
which safely operates on external values.This PR makes that change backwards compatible by reintroducing it as a new config option of externalDefaultValue instead of replacing the existing option of defaultValue.
defaultValueLiteral stays as its open top-level config option, but is subsumed under externalDefaultValue within the GraphQLArgument/GraphQLInputField as it is always external.