Giter VIP home page Giter VIP logo

purescript-graphql's People

Contributors

hendrikniemann avatar i-am-the-slime avatar rndnoise avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

purescript-graphql's Issues

Can't define recursive or mutually recursive object types

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.

Using variant for Unions

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.

Allow to change the context in subfield through resolver

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...

Input object types in v2

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."
  }

Executing fields in parallel demanded by spec but Apply instance dependent

The Problem

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.

Possible solutions

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:

  • This might lead to APIs that execute mutations in parallel which is forbidden by the spec and might therefore confuse users of the API
  • This adds another pitfall/quirk to the library in order to build performant APIs. Since the 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.

Better way to structure type classes

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.

Typechecking Variables

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)
}

Create Contravariant/Invariant instances for types

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:

Create object type with resolvers on unwrapped type

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

Create a very simple scalar type that inherits from string scalar type

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

Nice progress

If you want to give feedback please open an issue

Congratulations on the latest improvements!

Field and argument deprecation

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.

Fall back to next nullable value on error in a field

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.

Pass in root value to graphql function directly

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...

Errors should contain location in the query document

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.

Cannot use in REPL

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.

Getting Started (v2): Could not match type String with type JsonDecodeError

Hi! I was trying the tutorial https://hendrikniemann.github.io/purescript-graphql/#/getting_started.

Seems like a dependency has changed.

Steps for reproduction:

  1. Init

    mkdir purescript-graphql-example
    cd purescript-graphql-example
    spago --version
    #0.19.0
    spago init
    spago build
  2. 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.

  3. 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    
    

Rename `describe` function to `withDescription`

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.

Application throws if fragments are defined but operation has no name

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
}

Default values for arguments and input type fields

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"

Flip arguments of `arg` and `optionalArg`

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.

Switch operators for withField and describe

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.