hendrikniemann / purescript-graphql Goto Github PK
View Code? Open in Web Editor NEWEnd to End typesafe GraphQL with PureScript
Home Page: https://hendrikniemann.github.io/purescript-graphql
License: MIT License
End to End typesafe GraphQL with PureScript
Home Page: https://hendrikniemann.github.io/purescript-graphql
License: MIT License
In GraphQL fields can be deprecated. This should also be possible in PureScript GraphQL. The fields continue working, the deprecation is only visible in the introspection query. This could be easily handled by adding a deprecationReason
field in the introspection data structures and adding a type class similar to Describe
that has the member function withDeprecationReason
.
This only happens with pulp repl
. There are no problems using pulp run
, but I haven't tried other methods of running the code, like pulp build -O example.js && node example.js
.
$ pulp init
$ bower install purescript-graphql --save
$ purs repl
> import Prelude
> import GraphQL as Q
> import GraphQL.Type as Q
> import Data.Maybe (Maybe(..))
> import Data.Either (Either(..))
> import Effect (Effect)
> import Effect.Console as C
> import Effect.Aff (runAff_)
> import Global.Unsafe (unsafeStringify)
> :paste
schema :: Q.Schema Unit Unit
schema = Q.schema queryType Nothing
queryType :: Q.ObjectType Unit (Maybe Unit)
queryType = Q.objectType
"Query"
(Just "This is the root query operation type")
{ helloWorld: Q.field'
(Q.nonNull Q.string)
(Just "Basic hello world query")
\_ _ -> pure "hello world" }
main :: Effect Unit
main = runAff_ log $
Q.graphql schema "{ helloWorld }" unit unit Nothing Nothing
where
log (Left ex) = C.error (show ex)
log (Right x) = C.log (unsafeStringify x)
^D
> main
/Users/rndnoise/example/.psci_modules/node_modules/GraphQL.Type/foreign.js:21
return new G.GraphQLNonNull(type);
^
TypeError: G.GraphQLNonNull is not a constructor
at /Users/rndnoise/example/.psci_modules/node_modules/GraphQL.Type/foreign.js:21:12
...
I'm not sure why this happens but I suspect it might be due to the PureScript module's name GraphQL
being the same as the JavaScript library graphql
, and that pulp repl
might not be setting up the same library paths as pulp run
.
When I add this to one of the foreign.js files, var G = require("graphql"); console.warn(G);
the output is { graphql: [Function: graphql] }
. That looks like the PureScript module, rather than the JavaScript module.
This happens whether or not I have graphql
installed via npm, so it seems require("graphql")
is actually loading the PureScript GraphQL
module rather than the Facebook library. Doing npm install graphql --save
doesn't seem to change anything.
In theory we could allow to change the context of child fields. This might be possible by adjusting withResolver
so that the resolver could switch the context. I think this is wrong but maybe it conveys the idea:
- withResolver :: forall m a b argsd argsp.
+ withResolver :: forall m n a b argsd argsp.
MonadError Error m =>
- Field n a argsd argsp ->
+ Field n a argsd argsp ->
(Record argsp -> b -> m a) ->
Field m b argsd argsp
I can't really think this through right now, but maybe some ideas come up...
Hi! I was trying the tutorial https://hendrikniemann.github.io/purescript-graphql/#/getting_started.
Seems like a dependency has changed.
Steps for reproduction:
Init
mkdir purescript-graphql-example
cd purescript-graphql-example
spago --version
#0.19.0
spago init
spago build
packages.dhall
:
let additions =
{ graphql =
{ dependencies =
[ "argonaut"
, "console"
, "control"
, "effect"
, "enums"
, "foldable-traversable"
, "nullable"
, "numbers"
, "prelude"
, "psci-support"
, "record"
, "spec"
, "string-parsers"
]
, repo = "https://github.com/hendrikniemann/purescript-graphql.git"
, version = "master"
}
}
Actually, it is replace in upstream
with
let overrides = {=}
let additions = ...
in upstream // overrides // additions
But I have done this too many times, so no problem there.
Install
spago install graphql argonaut maybe either ordered-collections
spago build
Error found:
in module GraphQL.Type
at .spago/graphql/master/src/GraphQL/Type.purs:19:1 - 19:31 (line 19, column 1 - line 19, column 31)
Module Data.Variant was not found.
Make sure the source file exists, and that it has been provided as an input to the compiler.
I guess, "variant"
should be added to the dependencies, but after that I get
spago build
in module GraphQL.Type
at .spago/graphql/master/src/GraphQL/Type.purs:984:17 - 987:30 (line 984, column 17 - line 987, column 30)
Could not match type
String
with type
JsonDecodeError
while trying to match type Either String
with type Either JsonDecodeError
while checking that expression (bind (((...) (...)) hackedCtx)) (\fieldValue ->
(apply pure) ((...) recValue)
)
has type Either JsonDecodeError t0
in value declaration withInputField
where t0 is an unknown type
Currently arg
takes the type first and then the name of the argument. The inital idea was that this could be useful to quickly create alias functions like:
intArg = arg Scalar.int
In practice this is not really used and could easily be done through flip arg
. Instead this is confusing because field
takes the name first and in SDL also first the name and then the type is defined.
So this just came up in the PureScript meetup: Maybe resolvers could accept a more generic return type?
Currently type classes create contraints on which types are usable in certain situations. This has two problems. The first one is that it is just very confusing to have 3 different type classes (GraphQLType
, InputType
, OutputType
). Second this allows other libraries to define any of these type classes for their types and pass them to the functions that try to create a contraint on their arguments.
A possible solution could be to introduce a new kind instead of GraphQLType
. This would at least get rid of one of the type classes and maybe also solve the other problem of foreign GraphQL types.
I would like to change the type of the graphql
function from the root module.
graphql ::
โ m a.
MonadError Error m =>
Schema m a ->
String ->
Map String Json ->
Maybe String ->
- m a ->
+ a ->
m Json
This type signature was partly due to the way how resolvers worked in the past and partly because I misunderstood how the state monad works...
Hi! I was reading in the tutorial "PureScript GraphQL does not support subscriptions yet". Is this hard to implement? Do you have plans in doing so?
Version 2 needs to support input object types as well. I recently came up with these ideas:
newtype InputObjectType a = InputObjectType
{ name :: String
, description :: Maybe String
, parseLiteral :: AST.ValueNode -> Either String a
, parseValue :: Json.Json -> Either String a
}
newtype InputField n a = InputField
{ name :: SProxy n
, deserialize :: Maybe AST.ValueNode -> VariableMap -> Either String a
}
userDraft :: InputOnbjectType { name :: Maybe String, email :: String }
userDraft = inputObjectType "UserDraft"
{ name: optionalInputField Scalar.string .> "The name for this new user."
, email: inputField Scalar.string .> "The email of this user."
}
If you want to give feedback please open an issue
Congratulations on the latest improvements!
I would like to discuss some changes to the current DSL:
I think switching the infix operators for describe
(.>
) and withField
(:>
) could be a good change. The .
is more associated with fields and :
fits better with descriptions.
Maybe we should also rename the describe
function into withDescription
to align it with the other nomenclature.
The GraphQL spec demands that neighboring fields are executed in parallel for all fields that are not fields in the root mutation type. Mutation fields must be executed serially. The way how PureScript GraphQL currently works is that the results of the fields are transformed using traverse
which internally uses an Applicative
constraint and the apply
function.
In practice this means that whether effects run in parallel or serially depends on the Apply
implementation of the effect monad. E.g. Aff
does not run effects in parallel that are joined via apply
. Instead, it defines ParAff
, which can be used to implement the Parallel
type class. The Apply
instance of ParAff
runs effects in parallel.
We could add a Parallel
type class constraint to objectType
and execute selection sets in parallel.
We could leave this responsibility to the user (basically as a feature). The library user would have to chose a dedicated monad if they want their selection sets to be executed in parallel. This is problematic for two reasons:
apply
call is hidden inside of the execution engine, users might not think about when to use e.g. Aff
and ParAff
. Furthermore it is just impractical to convert all functions that return Aff
into ParAff
.Most DSL functions start with "with", e.g. withField
and withArgument
. The only odd one out is the describe
function. It also clashes with describe
from testing frameworks. This change is a bit bigger, because describe
is actually a class field and needs to be renamed in every instance. Luckily, describe
is seldomly used, as it is mostly meant to be used with its operator.
I am currently struggling with union types. One idea was to return a tuple of output type and value from a resolve type function. The problem is, that this does not work out in the type signature:
resolveType :: forall t. OutputType m t => a -> forall b. Tupe (t b) b
Furthermore, it is not introspectable which output types are potentially returned from the function.
Another idea could be to require a record of possible output types in the union type definition:
aType :: GQL.ObjectType Context AValue
aType = -- ...
bType :: GQL.ObjectType Context BValue
bType = -- ...
myUnionType :: GQL.UnionType Context (Variant (a :: AValue, b :: BValue))
myUnionType = GQL.union "MyUnion" { a: aType, b: bType }
Now this type's parent value has type: (Variant (a :: AValue, b :: BValue))
. We can now make usage of this type more convenient using cmap
as described in #23:
myUnionType :: GQL.UnionType Context (Either AValue BValue)
myUnionType =
cmap (either (inj (SProxy :: SProxy "a")) (inj (SProxy :: SProxy "b"))) $
GQL.union "MyUnion" { a: aType, b: bType }
Instead of Either
we could use any other sum type.
In GraphQL, if a field errors the error will be added to the errors
field in the response (as described in the specification). Furthermore the value for the field is set to null
if and only if the type of the field is nullable. Otherwise its parent field is set to null
if its type is nullable and so on until a nullable ancestor field is reached or the whole data
property of the response is set to null
.
We are currently not supporting this upwards propagation and instead always set the erroring field to null
. I think this could be done in serializeWithErrors
(here) or at least in this module.
Similar to the __schema
introspection query field, __type
needs to be added to the query type before being applied to the schema constructor. Documentation can be found in the specification.
It could be interesting to create Contravariant
instance for the GraphQL types. A contravariant is basically the opposite of a functor:
A Contravariant functor can be seen as a way of changing the input type of a consumer of input
This would allow us to do some interesting things together with a renameType
function:
Sometimes we want to write very simple object type that directly works on a record type to keep the resolvers as simple as possible. On the other hand we might want to use newtypes for the objects we get from the database to be able to define instances of typeclasses for them. Using cmap
we can get the best of both worlds:
newtype Todo = Todo { id :: string, title :: string }
-- ...
-- long to highlight types
todoRecordType :: forall m. MonadError Error m => ObjectType m { id :: string, title :: string }
todoRecordType = objectType "Todo"
:> field "id" Scalar.id
!#> _.id
todoType :: forall m. MonadError Error m => ObjectType m Todo
todoType = cmap unwrap todoRecordType
-- or short and direct
todoType :: forall m. MonadError Error m => ObjectType m Todo
todoType = cmap unwrap $ objectType "Todo"
:> field "id" Scalar.id
!#> _.id
The GraphQL community descusses for a while how scalars could be derived from the basic scalars defined in the specification. Scalars can be both input and output types but an Invariant
instance on Scalars would allow us to quickly create derived versions of a scalar:
newtype MyString = MyString String
myStringScalar :: ScalarType String
myStringScalar = renameType "MyString" $ imap wrap unwrap $ Scalar.string
This might not be very useful because usually we want to make some input validation on at least the input side:
newtype PositiveInt = PositiveInt Int
toInt :: PositiveInt -> Int
-- ...
fromInt :: Int -> Maybe PositiveInt
-- ...
positiveIntScalar :: ScalarType String
positiveIntScalar = renameType "PositiveInt" $ imap toInt fromInt $ Scalar.string
-- ^- This does not typecheck because not every int is positive
We might have to enhance the query AST with location information to return the location of the error. Currently the error only contains the path to the error but it should also contain line and character number of the problematic field/argument in the query string.
Currently it is not possible to add default values for field arguments and input fields. Ideally we could introduce a new function (and operator alias) withDefaultValue
. If there is no value provided for a variable that has a default value we can use the default value.
withDefaultValue :: forall a n. Symbol n => Tuple (SProxy n) (Argument a) -> a -> Tuple (SProxy n) (Argument a)
withDefaultValue (Tuple name arg) defaultValue = ???
-- Usage
mutationType :: GQL.ObjectType Context Unit
mutationType = GQL.objectType "Mutation"
:> GQL.field "myMutation" Scalar.boolean
?> GQL.arg GQLScalar.id (SProxy :: SProxy "id") `withDefaultValue` "123"
The implementation seems to think that fragments are operations:
Operation name is required for queries with multiple operations.
But this query should be fine:
{
search {
__typename
...TodoFrag
...UserFrag
}
}
fragment TodoFrag on Todo {
id
title
}
fragment UserFrag on User {
id
name
}
https://github.com/taojang/purescript-graphql
Is doing this. Generally it's simple
r. Do you have a specific reason to use Argonaut?
I ran into a snag when trying to define an object of type X that has a field with type X. This will not compile:
commentType :: ObjectType Unit Unit
commentType = objectType
"Comment"
(Just "comment")
{ replies: Q.field' (Q.list commentType) -- "The value commentType is not defined here"
(Just "replies")
\_ ctx -> pure (Just []) }
It also doesn't seem possible to use Data.Lazy.fix
to achieve this. However, there is a comment in graphql-91 that explains you can do this in JavaScript using a thunk, so I think another wrapper function around objectType
that has the type of it's last argument as (Unit -> Record fields)
might do the trick.
We have to typecheck variables based on their definition and place of use. The following query is invalid but is currently accepted by PureScript GraphQL.
type Query {
hello(name: String!)
}
query example($name: Int!) {
hello(name: $name)
}
The id scalar does not accept integers from JSON. It works correctly when the value is inlined.
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.