-
Notifications
You must be signed in to change notification settings - Fork 311
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
Custom scalars as extensions #1150
Comments
I did some exploration to get a sense of the AST utilities and types from the graphql package. It is straight forward. There are TypeScript AST interface types. No functional utilities. I would encode the typed-interface input to these objects. Then use the GraphQL native utilities to turn this into GrahQL syntax. /* eslint-disable */
import {
type DocumentNode,
type FieldNode,
GraphQLInt,
Kind,
type NameNode,
type OperationDefinitionNode,
OperationTypeNode,
parse,
print,
type SelectionSetNode,
} from 'graphql'
const d = console.log
// d(GraphQLInt)
// Exploring traversing the document AST.
const ss1 = parse(`query { foo(int:5) }`)
const d1 = ss1.definitions[0]!
// if (d1.kind === Kind.OPERATION_DEFINITION) {
// const ss1 = d1.selectionSet.selections[0]!
// if (ss1.kind === Kind.FIELD) {
// d(ss1.name.value)
// d(ss1.arguments)
// }
// }
// // Exploring building up a document AST.
// // Create a name node for the field
// const fieldNameNode: NameNode = {
// kind: Kind.NAME,
// value: 'bar',
// }
// // Create a name node for the alias
// const aliasNameNode: NameNode = {
// kind: Kind.NAME,
// value: 'foo',
// }
// // Create a field node
// const fieldNode: FieldNode = {
// kind: Kind.FIELD,
// name: fieldNameNode,
// alias: aliasNameNode,
// }
// // Create a selection set node
// const selectionSetNode: SelectionSetNode = {
// kind: Kind.SELECTION_SET,
// selections: [fieldNode],
// }
// // Create an operation definition node
// const operationDefinitionNode: OperationDefinitionNode = {
// kind: Kind.OPERATION_DEFINITION,
// operation: OperationTypeNode.QUERY,
// selectionSet: selectionSetNode,
// }
// // Create the document node
// const documentNode: DocumentNode = {
// kind: Kind.DOCUMENT,
// definitions: [operationDefinitionNode],
// }
// // Log the constructed document
// d(documentNode)
// // Print the query string
// d(print(documentNode)) Our encoder would:
There is an alternative path for 3 which we have yet not done but is as follows. Instead of encoding and then inlining arguments, raise them up to be variables in the operation, and make the arguments being inputs to those operation variables. This can be a future feature. Deferring it does not appear to lead to any wasted effort now. |
GraphqL custom scalars are a core concept. We can do a few things in core:
|
I began to implement this and had some thoughts.
The point here is about exposing a real API for custom scalars that the generator the builds on top of. For the generator to benefit statically it would need to be given a reference to a scalars module. If a user would not inform the generator of those custom scalar types statically then the user would get string typing for custom scalars in those static APIs. |
Perceived Problem
Ideas / Proposed Solution(s)
Can we simplify this to:
We need to consider both runtime and buildtime (types).
Runtime
What do we need to do?
How do we encode arguments?
Extensions could be allowed to add scalar codecs. Codecs are already a Graffle data type and we could basically use them here as they are currently defined. They are a pair of functions, encode and decode.
When an extension would add a codec, the codec's encode function would be run on every argument of that type.
To discover which arguments are of that type, the encode step would recursively iterate over the selection set searching for where arguments are used. As soon as a usesite for an argument is found, the search for that argument can end because we can assume thatTo discover which arguments are of that type, if we are dealing with a graphql document object, then we need only look at the defined arguments of the document which will all already be typed. We literally just string compare the type name of the argument in the document with the type name of the custom scalar. If no arguments use the custom scalar then no action is needed. If there is an argument that is a custom scalar (defined as having a type that does not match any of the standard scalar names) but has no codec available for it we can either throw an error or passthrough the value.The simplicity of the above is predicated on there being a process by which we transform interface-typed inputs into graphql document objects. This relates to a realize in the opening message of #1149.
If we do not do that work, then, we have a harder time of encoding here wherein we need to deeply traverse the selection set until we discover where each argument was used in it, then refer to the schema index for what type is at that point.
How do we decode data?
For decoding, we need to recursively traverse the result set, applying the codec decode function to every value whose type is of that codecs. Discovering a value's type in the result set requires a schema map that is co-traversed to make type checks. But this gets more complicated with aliases which allow the result set to diverge from the schema and thus another co-traversal must be added, the selection set.
There are likely many many ways to speed this up, here are some ideas:
During encoding, note if there were any custom scalar arguments used. If none, we don't need to decode at all.This is wrong: lack of argument custom scalars does not mean lack of output custom scalars.Looking at the above, I think this is the optimal approach for us:
@include
directive could mean what appears in the selection set never does in the result set.Extension Interface
Buildtime
The scope of buildtime diverges with runtime. While we could have the extension return static types of any provided scalar codecs there would be no way for the generated typed to be augmented by that.Some generated types have type parameters that are filled by the instance using the HKT-ish trick. We could expand use of this trick such that the generated select types also take on a type parameter that is passed a config type whose type in turn is subject to the extensions the client has used.The text was updated successfully, but these errors were encountered: