ghostdogpr / caliban Goto Github PK
View Code? Open in Web Editor NEWFunctional GraphQL library for Scala
Home Page: https://ghostdogpr.github.io/caliban/
License: Apache License 2.0
Functional GraphQL library for Scala
Home Page: https://ghostdogpr.github.io/caliban/
License: Apache License 2.0
Sangria has deferred resolvers
https://sangria-graphql.org/learn/#deferred-value-resolution
or Fetchers
https://sangria-graphql.org/learn/#high-level-fetch-api
In your example I would like to return CharacterDetails instead of Characters. I do not want to calculate statistics everytime only when they are queried and I do want to calculate statistics for all returned characters with single query (not having N+1 problem). Look at Repo.statistics
. I find solutions with memoizing
statistics call to repo. Is there an easier way to do that?
case class CharacterDetail(name: String, nicknames: List[String], origin: Origin, role: Option[Role], statistics: Task[Int])
object CharacterDetail {
def apply(c: Character, statistics: Task[Int]): CharacterDetail =
new CharacterDetail(c.name, c.nicknames, c.origin, c.role, statistics)
}
object Repo {
def statistics(characterNames: List[String]): Task[Map[String, Int]] = ZIO.effect({
println(s"statistics for ${characterNames.mkString("[", ",", "]")}") characterNames.map(n => (n, n.length)).toMap
})
}
case class Queries(
@GQLDescription("Return all characters from a given origin")
characters: CharactersArgs => URIO[Console, List[CharacterDetail]],
)
val interpreter = interpreter = graphQL(
RootResolver(
Queries(
args => service.getCharacters(args.origin).flatMap(characters =>
Repo.statistics(characters.map(_.name)).memoize
.map(allStatistics =>
characters.map(c => CharacterDetail(c, allStatistics.map(_(c.name))))
)
),
),
Mutations(args => service.deleteCharacter(args.name)),
Subscriptions(service.deletedEvents)
)
)
Move and split Readme into a microsite
With an annotation?
This article explains the problem faced by most graphql system: https://blog.apollographql.com/optimizing-your-graphql-request-waterfalls-7c3f3360b051
Interesting libraries to look at:
Some discussions are going on about this topic in the Caliban Discord.
I used this a lot in sangria gql api. In your example I would like to return CharacterDetail
s instead of Character
s. I do not want to calculate statistics everytime only when they are queried, that's why I model this field as a Task
and not an Int
.
case class CharacterDetail(name: String, nicknames: List[String], origin: Origin, role: Option[Role], statistics: Task[Int])
object CharacterDetail {
def apply(c: Character): CharacterDetail =
new CharacterDetail(c.name, c.nicknames, c.origin, c.role, ZIO.effect({
println(s"calculating statistics for ${c.name}")
c.nicknames.length
}))
}
case class Queries(
@GQLDescription("Return all characters from a given origin")
characters: CharactersArgs => URIO[Console, List[Character]],
@GQLDeprecated("Use `characters`")
character: CharacterArgs => URIO[Console, Option[CharacterDetail]],
)
What is the proper way to model it? Is this a good start? And how to write character
function?
Currently, execute
either fully succeeds with ResponseValue
or fails with CalibanError
. It would be nice to support partial results โ any evaluation error should bubble up until the first nullable node, and the error itself should be reported together with the response. For example, given the following schema:
Queries(one: Task[Int], two: Task[Int])
the query
query {
one
two
}
should be able to produce:
{
"data": {
"one": 1,
"two": null
},
"errors": [
{
"message": "Failed :(",
"locations": [ { "line": 3, "column": 3 } ],
"path": [ "two" ]
}
]
}
In terms of schema, this means that effects other than UIO
should be represented as nullable fields.
Maybe use https://github.com/propensive/contextual
It's not officially supported by the spec but it could be nice to have for people who want it. Maybe using an ADT for IntValue
and FloatValue
?
IO
seems a bit overkill in this use case. Wouldn't Either
be better?
Description
Scala Steward is a robot that helps you keeping library dependencies and sbt plugins up-to-date.
https://github.com/fthomas/scala-steward
All you have to do is edit this repos and send PR.
Missing validations:
ArgBuilder
error type should be changed to ValidationError
)This makes it easier to reuse existing builders.
def mapM[A](f: T => IO[ExecutionError, A]): ArgBuilder[A]
Example usage (with refined's NonEmptyString
):
implicit val nonEmptyStringArgBuilder: ArgBuilder[NonEmptyString] =
ArgBuilder.string.mapM(value => IO.fromEither(NonEmptyString.from(value)).mapError(ExecutionError(_)))
Add a new module similar to caliban-http4s
named caliban-akka-http
that exposes a REST and a WebSocket API for a given GraphQL interpreter.
If there is common code between the http4s and the akka one, it should be factored out.
I haven't used Akka in a while, so external contributors are welcome to pick this one up!
EDIT: HTTP is done, only WebSocket is missing
Compare various use cases with Sangria
Hi,
Any particular reason to stick to ZIO instead of using Cats Effect?
This looks exactly what we need, but we went with Monix, which makes integration a bit more complicated...
I am looking to add a subscription to the GQL endpoint that I developing with Caliban, and I can't see the way to provide parameters in the subscription request. It seems that the mechanism should be similar to the queries (e.g. from the apollo documentation --> https://www.apollographql.com/docs/react/data/subscriptions/).
In the Caliban documentation (https://ghostdogpr.github.io/caliban/docs/#subscriptions), I see that the parameter type for the subscription must be ZStream[Any, Nothing, ] and not a function => ZStream[Any, Nothing, ] following the pattern of Queries and Mutations.
The examples don't take any parameters (e.g. deletedEvents) either.
I am not sure what I am not seeing, I would appreciate a pointer in the right direction.
Thanks
Miguel
It's because of this line of code
When generating input type from case class, case object..., the suffix "Input" is always added. Can we avoid that?
Frequently Asked Questions:
Currently when facing a sealed trait with both case classes and case objects, we generate a union
but the case objects don't have any field, which is invalid graphql.
Maybe we could add an arbitrary field in that case?
The current code in Http4sAdapter
converts the result to a string, then is parsed into Json. Instead, we could use Circe (which is already in scope in the Http4sAdapter
) to convert the result to Json directly. This will take care of edge cases (breaklines, etc) and will be faster.
Here's the existing code:
result <- execute(interpreter, query)
.fold(
err => s"""{"errors":["${err.toString.replace("\"", "'")}"]}""",
result => s"""{"data":$result}"""
)
json <- Task.fromEither(parse(result))
Instead, we can define an Encoder[ResponseValue]
and Encoder[ErrorResponse]
to convert to Json directly.
Trying the examples (to understand the lib), but they not compile.
Is it also possible to have a build.sbt as example for running the project?
Thanks
Hello,
First, kudos to an amazingly clean GQL service. Impossible to make it any better.
I am stuck with some code that users Enumerations instead of sealed traits to express Enums. It was easy enough to create a schema for Enumeration types (see below).
My question is what do I need to provide the schema to be able to use the Enum type also as an "input type" in GQL.
Simply putting it in as a field in the arguments case class is not enough (not surprising). It complains that it misses the Query Case class Schema, probably because it does not know how to marshall the input into the internal type.
Any insights on how to provide the compiler with the right info/implcits will be more than welcome. If I come up with a general approach, I'll be glad to make it available.
Thanks
The barebones example code (that does not compile) looks like:
import caliban.RootResolver
import caliban.GraphQL._
import scala.collection.mutable
object QueryWithEnum {
type ThingType = ThingType.Value
object ThingType extends Enumeration {
val BIG = Value(1)
val SMALLISH = Value(2)
}
import caliban.schema
def enumSchema[E <: Enumeration](name: String) = schema.Schema.scalarSchema[E#Value]("name", None, e => caliban.ResponseValue.StringValue(e.toString))
implicit val thingTypeSchema = enumSchema[ThingType.type]("ThingType")
case class Thing(k: String, name: Option[String], thingType: ThingType, value: Double)
private val thingStore: mutable.Map[String, Thing] = mutable.Map(
"thingOne" -> Thing("thingOne", None, ThingType.BIG, 33.2),
"thingTwo" -> Thing("thingTwo", Some("Dearest Thingy"), ThingType.SMALLISH, 324.34)
)
case class ByKey(k: String)
def allThingsEnum: List[Thing] = thingStore.values.toList
def oneThingEnum(tk: ByKey): Option[Thing] = thingStore.get(tk.k)
case class ByType(t: ThingType)
def someThingsEnum(tk: ByType): List[Thing] = allThingsEnum.filter(_.thingType == tk.t)
case class Query(things: List[Thing], thing: ByKey => Option[Thing], someThings: ByType => List[Thing])
val queryService = Query(allThingsEnum, oneThingEnum, someThingsEnum)
val interpreter = graphQL(RootResolver(queryService))
}
And the compiler error:
[error] MessageQuery.scala:34:27: could not find implicit value for parameter querySchema: caliban.schema.Schema[R,com.cruxsystems.ais.service.inspection.QueryWithEnum.Query] [error] val interpreter = graphQL(RootResolver(queryService))
It would be nice if, given a GraphQL schema, we could generate (with an sbt command?) the corresponding case classes and sealed traits.
Sometimes when you miss an instance of Schema
for a custom type that is not a case class nor a sealed trait, the compile error just tells you it couldn't find the Schema but doesn't tell you what's missing exactly.
I'm not sure how much can be done here and how much needs to be done in Magnolia but it's worth investigating.
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.