gemini-hlsw / clue Goto Github PK
View Code? Open in Web Editor NEWGraphQL client for Scala.
GraphQL client for Scala.
It can be quite common to perform the same subquery in multiple queries. It's therefore desirable to be able to abstract this subquery away and be able to include it into different queries for reuse (and reuse of the typeclass instances of the result class - which we may already have if we're mapping into an external type).
I imagine something along the lines of:
trait SubQuery[A] {
def subDocument: String
implicit def jsonDecoder: Decoder[A]
}
And a string interpolator to insert the subDocument
in a Query
.
An example of usage would be:
object TargetSubQuery extends SubQuery[Target] {
val subDocument = """
id
name
magnitudes {
band
etc
}
"""
}
And then somewhere else, in a Query
:
val obsQueryDocument = gql"""
query {
observations {
targets {
$TargetSubQuery
}
}
}
"""
The biggest choke point I see with this is how to get grackle
to process obsQueryDocument
. We could trick it somehow. Otherwise we would have to process the interpolated string within the scalafix rule, which means evaluating it, which I don't know how to do (maybe we could imitate mdoc
?). But I think that other than that, we can get away with generating a reference to an external type (Target
in this case, obtained from the SubQuery
) and resolving the interpolated string only at runtime.
See https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#request-parameters
Responses can also contain extensions.
When getting extensions like:
{
"errors": [
{
"message": "Source too bright, well half filled in 0.76 seconds",
"extension": {
"errorCode": "SOURCE_TOO_BRIGHT",
"error": {
"halfWell": 0.76
}
}
}
],
"data": null
}
They don't seem to show up on the error results instead getting:
NonEmptyList(GraphQLError(Source too bright, well half filled in 0.76 seconds,None,None,None))
We are quite a few versions behind.
Upon receiving an error
message on a subscription, we are currently terminating it.
This goes contrary to the spec.
The way around this would be to provide a Stream[Either[Error, A]]
instead of a Stream[A]
. Actually, it'd be nice to have methods that support both.
The headers for TransactionalClient
are set when the client is build but it maybe necessary to set them per queryy (e.g. the auth token which keeps changing)
This addresses the case where the server misbehaves upon a specific query and drops the connection.
Since the connection is established, the retry counter is reset to zero and the client is stuck in an active loop of connecting and disconnecting.
I'm using "spec" here very loosely ๐ in order of specy-ness:
I still have a lot of reading to do since I'm new to GraphQL. Some of my notes so far:
circe.Json
as an intermediary format since that's pretty close to what the spec describesIn order to reduce the size of Scala.js code, we could add of the option of generating native Scala.js traits/classes to hold query results instead of Scala case classes.
Ideally, these would behave as facades into the JS object of the result, using js.Array
instead of List
, js.UndefOr
instead of Option
, etc. We wouldn't even need a Decoder
, which means revising the base Query
type.
Things that may be tricky:
Reusability
, Eq
and Show
instances (we do have all the info to generate them though).Focus
macro would work.It remains to be seen how much of an improvement this would be, in order to evaluate it it's worth the effort.
In the ODB we have in the schema:
type Gcal implements StepConfig {
continuum: GcalContinuum
arcs: [GcalArc!]!
filter: GcalFilter!
diffuser: GcalDiffuser!
shutter: GcalShutter!
stepType: StepType!
}
If we define a subquery:
@GraphQL
abstract class GcalStepConfigSubquery extends GraphQLSubquery[ObservationDB]("Gcal"):
override val subquery: String = """
{
continuum
arcs
filter
diffuser
shutter
}
"""
@GraphQLStub
object GcalStepConfigSubquery
and then query:
query($$obsId: ObservationId!) {
observation(observationId: $$obsId) {
execution {
executionConfig {
... on GmosSouthExecutionConfig {
acquisition {
nextAtom {
steps {
stepConfig {
... on Gcal $GcalStepConfigSubquery
}
}
}
}
}
}
}
}
}
This results in:
[error] scalafix.internal.v1.FileException: unexpected error processing file /Users/rpiaggio/gemini/explore/common-graphql/src/main/scala/queries/common/GeneratedSequenceSQL.scala
[error] Caused by: java.lang.Exception: Could not resolve type for field [subquery10] - Is this a valid field present in the schema?
[error] at clue.gen.QueryGen.$anonfun$resolveData$9(QueryGen.scala:270)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:327)
[error] at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:326)
[error] at scala.collection.immutable.List.collect(List.scala:267)
[error] at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:326)
[error] at scala.collection.immutable.List.map(List.scala:250)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error] at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error] at scala.Option.map(Option.scala:242)
[error] at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen.$anonfun$resolveData$20(QueryGen.scala:331)
[error] at scala.collection.immutable.List.map(List.scala:250)
[error] at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:331)
[error] at scala.collection.immutable.List.map(List.scala:246)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error] at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error] at scala.Option.map(Option.scala:242)
[error] at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:327)
[error] at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:326)
[error] at scala.collection.immutable.List.collect(List.scala:275)
[error] at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:326)
[error] at scala.collection.immutable.List.map(List.scala:246)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error] at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error] at scala.Option.map(Option.scala:242)
[error] at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen.$anonfun$resolveData$20(QueryGen.scala:331)
[error] at scala.collection.immutable.List.map(List.scala:246)
[error] at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:331)
[error] at scala.collection.immutable.List.map(List.scala:246)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error] at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error] at scala.Option.map(Option.scala:242)
[error] at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen.$anonfun$resolveData$20(QueryGen.scala:331)
[error] at scala.collection.immutable.List.map(List.scala:250)
[error] at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:331)
[error] at scala.collection.immutable.List.map(List.scala:246)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error] at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:327)
[error] at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:326)
[error] at scala.collection.immutable.List.collect(List.scala:267)
[error] at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:326)
[error] at scala.collection.immutable.List.map(List.scala:250)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error] at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error] at scala.Option.map(Option.scala:242)
[error] at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error] at scala.Option.map(Option.scala:242)
[error] at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error] at scala.Option.map(Option.scala:242)
[error] at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error] at scala.Option.fold(Option.scala:263)
[error] at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error] at clue.gen.QueryGen.resolveData(QueryGen.scala:376)
[error] at clue.gen.QueryGen.resolveData$(QueryGen.scala:220)
[error] at clue.gen.GraphQLGen.resolveData(GraphQLGen.scala:15)
[error] at clue.gen.QueryGen.$anonfun$addData$1(QueryGen.scala:416)
[error] at scala.Function$.$anonfun$chain$2(Function.scala:23)
[error] at scala.collection.LinearSeqOps.foldLeft(LinearSeq.scala:183)
[error] at scala.collection.LinearSeqOps.foldLeft$(LinearSeq.scala:179)
[error] at scala.collection.immutable.List.foldLeft(List.scala:79)
[error] at scala.Function$.$anonfun$chain$1(Function.scala:23)
[error] at clue.gen.GraphQLGen$$anonfun$2.$anonfun$applyOrElse$8(GraphQLGen.scala:138)
[error] at apply @ clue.gen.GraphQLGen$$anonfun$2.$anonfun$applyOrElse$7(GraphQLGen.scala:115)
[error] at >> @ clue.gen.GraphQLGenConfig.$anonfun$retrieveSchema$11(GraphQLGenConfig.scala:70)
[error] at complete @ clue.gen.GraphQLGenConfig.$anonfun$getSchema$3(GraphQLGenConfig.scala:84)
[error] at >> @ clue.gen.GraphQLGenConfig.$anonfun$retrieveSchema$11(GraphQLGenConfig.scala:70)
[error] at apply @ clue.gen.GraphQLGenConfig.$anonfun$retrieveSchema$9(GraphQLGenConfig.scala:56)
[error] at flatten @ clue.gen.GraphQLGenConfig.$anonfun$getSchema$1(GraphQLGenConfig.scala:87)
[error] at flatTap @ clue.gen.GraphQLGenConfig.$anonfun$getSchema$2(GraphQLGenConfig.scala:84)
[error] (graphql / Compile / scalafix) scalafix.sbt.ScalafixFailed: UnexpectedError
Currently clue has a dependency on the http4s-jdk-http-client, which requires Java 11.
Now that there is a common web socket client API in http4s/http4s#5520, it should be possible to make this library agnostic to the backend and remove the dependency on the jdk-http-client (and thus on JDK 11). This would allow you to publish for JDK 8.
Of course, testing would still require the jdk-http-client for now, so those would have to compile/run on JDK 11. Hopefully we'll get an ember-client websocket backend in the future that runs on JDK8 (and also Node.js :).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.