Giter VIP home page Giter VIP logo

graphql_ppx's Introduction

GraphQL syntax extension for Bucklescript/ReasonML

Build Status Build Status npm

This library lets you construct type-safe and validated queries at compile time, and generates response validation code for you. If you're writing a Bucklescript app that talks to a GraphQL server, this library will cut down on the boilerplate you have to write.

It is compatible with both OCaml and ReasonML syntax. There are no runtime dependencies except for Js.Json and Js.Dict, both included in the Bucklescript standard library.

Installation

Assuming that you've already got a Bucklescript project set up, installing this syntax extension consists of two steps:

First, add this package as a dependency to your package.json:

yarn add --dev graphql_ppx
# or, if you use npm:
npm install --saveDev graphql_ppx

Second, add the PPX to your bsconfig.json:

{
    "ppx-flags": [
        "graphql_ppx/ppx"
    ]
}

Examples

If you add a field that does not exist, you'll get a compiler error on the exact location this happens. This automatically works with Merlin, giving you immediate feedback in your editor:

Misspelled field, immediate compiler errors

Variables sent to queries and mutations are of course typed too. Nullable variables are translated to optional labelled arguments, while non-null variables become mandatory arguments:

Remove a variable argument, get a compiler error

(The Api.sendQuery function here is a small wrapper around bs-fetch, check it out below)

The result of a query is turned into a typed Js.t object, which will generate compiler errors if you try to access fields that don't exist:

Remove a field, get compiler errors

While these examples use the ReasonML syntax, using the standard OCaml syntax works as well.

Usage

Download the server schema

This plugin requires a graphql_schema.json file to exist somewhere in the project hierarchy, containing the result of sending an introspection query to your backend. To help you with this, a simple script is included to send this query to a server and save the result as graphql_schema.json in the current directory:

yarn send-introspection-query http://my-api.example.com/api
# or, if you use npm
npm run send-introspection-query http://my-api.example.com/api

Custom schema name

If you've already got a schema file downloaded for other purposes, you can tell graphql_ppx to use that one by updating the "ppx-flags" in bsconfig.json. Note: no space around the equal sign!

{
  "ppx-flags": [
    "graphql_ppx/ppx\\ -schema=your_schema.json"
  ]
}

While you can pass a path higher up the folder structure, like -schema=../somedir/your_schema.json, you might result into some path parsing problems with BuckleScript or Merlin.

Ignore .graphql_ppx_cache in your version control

This plugin will generate a .graphql_ppx_cache folder alongside your JSON schema to optimize parsing performance for BuckleScript and Merlin. If you're using a version control system, you don't need to check it in.

Send queries

To define a query, you declare a new module and type the query as a string inside the graphql extension:

module HeroQuery = [%graphql {|
{
  hero {
    name
  }
}
|}];

This module exposes a few functions, but the most useful one is make, which takes all arguments to the query/mutation as labelled function arguments, ending with (). It an object containing three things: query, which is a string containing the query itself; variables, a Js.Json.t object containing the serialized variables for the query; and parse, a function that takes a Js.Json.t instance and returns a typed object corresponding to the query.

A simple example might make this a bit clear:

module HeroQuery = [%graphql {| { hero { name } } |}];

/* Construct a "packaged" query; HeroQuery takes no arguments: */
let heroQuery = HeroQuery.make();

/* Send this query string to the server */
let query = heroQuery##query;

/* Let's assume that this was the result we got back from the server */
let sampleResponse = "{ \"hero\": {\"name\": \"R2-D2\"} }";

/* Convert the response to JSON and parse the result */
let result = Js.Json.parseExn(sampleResponse) |> query##parse;

/* Now you've got a well-typed object! */
Js.log("The hero of the story is " ++ result##hero##name);

Integrating with the Fetch API

bs-fetch is a wrapper around the Fetch API. I've been using this simple function to send/parse queries:

exception Graphql_error(string);

let sendQuery = q =>
  Bs_fetch.(
    fetchWithInit(
      "/graphql",
      RequestInit.make(
        ~method_=Post,
        ~body=
          Js.Dict.fromList([
            ("query", Js.Json.string(q##query)),
            ("variables", q##variables)
          ])
          |> Js.Json.object_
          |> Js.Json.stringify
          |> BodyInit.make,
        ~credentials=Include,
        ~headers=
          HeadersInit.makeWithArray([|("content-type", "application/json")|]),
        ()
      )
    )
    |> Js.Promise.then_(resp =>
         if (Response.ok(resp)) {
           Response.json(resp)
           |> Js.Promise.then_(data =>
                switch (Js.Json.decodeObject(data)) {
                | Some(obj) =>
                  Js.Dict.unsafeGet(obj, "data")
                  |> q##parse
                  |> Js.Promise.resolve
                | None =>
                  Js.Promise.reject(Graphql_error("Response is not an object"))
                }
              );
         } else {
           Js.Promise.reject(
             Graphql_error("Request failed: " ++ Response.statusText(resp))
           );
         }
       )
  );

Features

  • Objects are converted into Js.t objects
  • Enums are converted into polymorphic variants
  • Floats, ints, strings, booleans, id are converted into their corresponding native OCaml types.
  • Custom scalars are parsed as Js.Json.t
  • Arguments with input objects
  • Using @skip and @include will force non-optional fields to become optional.
  • Unions are converted to polymorphic variants, with exhaustiveness checking. This only works for object types, not for unions containing interfaces.
  • Interfaces are also converted into polymorphic variants. Overlapping interface selections and other more uncommon use cases are not yet supported.
  • Basic fragment support

Limitations

While graphql_ppx covers a large portion of the GraphQL spec, there are still some unsupported areas:

  • Not all GraphQL validations are implemented. It will not validate argument types and do other sanity-checking of the queries. The fact that a query compiles does not mean that it will pass server-side validation.
  • Fragment support is limited and not 100% safe - because graphql_ppx only can perform local reasoning on queries, you can construct queries with fragments that are invalid.

Extra features

By using some directives prefixed bs, graphql_ppx lets you modify how the result of a query is parsed. All these directives will be removed from the query at compile time, so your server doesn't have to support them.

Record conversion

While Js.t objects often have their advantages, they also come with some limitations. For example, you can't create new objects using the spread (...) syntax or pattern match on their contents. Since they are not named, they also result in quite large type error messages when there are mismatches.

OCaml records, on the other hand, can be pattern matched, created using the spread syntax, and give nicer error messages when they mismatch. graphql_ppx gives you the option to decode a field as a record using the @bsRecord directive:

type hero = {
  name: string,
  height: number,
  mass: number
};

module HeroQuery = [%graphql {|
{
  hero @bsRecord {
    name
    height
    mass
  }
}
|}];

Note that the record has to already exist and be in scope for this to work. graphql_ppx will not create the record. Even though this involves some duplication of both names and types, type errors will be generated if there are any mismatches.

Custom field decoders

If you've got a custom scalar, or just want to convert e.g. an integer to a string to properly fit a record type (see above), you can use the @bsDecoder directive to insert a custom function in the decoder:

module HeroQuery = [%graphql {|
{
  hero {
    name
    height @bsDecoder(fn: "string_of_float")
    mass
  }
}
|}];

In this example, height will be converted from a number to a string in the result. Using the fn argument, you can specify any function literal you want.

Non-union variant conversion

If you've got an object which in practice behave like a variant - like signUp above, where you either get a user or a list of errors - you can add a @bsVariant directive to the field to turn it into a polymorphic variant:

module SignUpQuery = [%graphql
  {|
mutation($name: String!, $email: String!, $password: String!) {
  signUp(email: $email, email: $email, password: $password) @bsVariant {
    user {
      name
    }

    errors {
      field
      message
    }
  }
}
|}
];

let x =
  SignUpQuery.make(~name="My name", ~email="[email protected]", ~password="secret", ())
  |> Api.sendQuery |> Promise.then_(response =>
    switch (response##signUp) {
    | `User(user) => Js.log2("Signed up a user with name ", user##name)
    | `Errors(errors) => Js.log2("Errors when signing up: ", errors)
    } |> Promise.resolve);

This helps with the fairly common pattern for mutations that can fail with user-readable errors.

Alternative Query.make syntax

When you define a query with variables, the make function will take corresponding labelled arguments. This is convenient when constructing and sending the queries yourself, but might be problematic when trying to abstract over multiple queries.

For this reason, another function called makeWithVariables is also generated. This function takes a single Js.t object containing all variables.

module MyQuery = [%graphql {|
  mutation ($username: String!, $password: String!) {
    ...
  }
|}];

/* You can either use `make` with labelled arguments: */
let query = MyQuery.make(~username="testUser", password="supersecret", ());

/* Or, you can use `makeWithVariables`: */
let query = MyQuery.makeWithVariables({ "username": "testUser", "password": "supersecret" });

Getting the type of the parsed value

If you want to get the type of the parsed and decoded value - useful in places where you can't use OCaml's type inference - use the t type of the query module:

module MyQuery = [%graphql {| { hero { name height }} |}];


/* This is something like Js.t({ . hero: Js.t({ name: string, weight: float }) }) */
type resultType = MyQuery.t;

Verbose mode (Contributors only)

You can pass -verbose in bsconfig.json to turn on the verbose mode. You can also use the Log module to log into verbose mode.

Experimental: graphql-tag replacement

To simplify integration with e.g. Apollo, this PPX can write the query AST instead of the raw source, in a way that should be compatible with how graphql-tag works.

To enable this, change your bsconfig.json to:

{
    "ppx-flags": [
        "graphql_ppx/ppx\\ -ast-out"
    ]
}

Now, the query field will be a Js.Json.t structure instead of a string, ready to be sent to Apollo.

module HeroQuery = [%graphql {| { hero { name } } |}];

/* Construct a "packaged" query; HeroQuery takes no arguments: */
let heroQuery = HeroQuery.make();

/* This is no longer a string, but rather an object structure */
let query = heroQuery##query;

Building manually on unsupported platforms

graphql_ppx supports 64 bit Linux, Windows, and macOS, as well as 32 bit Windows out of the box. If you're on any other platform, please open an issue on this repository so we can support it.

graphql_ppx's People

Contributors

anmonteiro avatar baransu avatar huxpro avatar jordwalke avatar mhallin avatar neitsch avatar sainthkh avatar szymonzmyslony avatar ulrikstrid avatar wokalski avatar wyze 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  avatar  avatar  avatar  avatar  avatar

Watchers

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

graphql_ppx's Issues

Add .exe extension copy of ppx file on build

Hello,

I'm working on windows with reason-apollo it actually runs the ppx command, but it don't work on windows since it needs to have the .exe extension, I can add it manually but every time that I add a new package or remove one from my project the package is rebuild and the .exe file is gone,

So I hope if is possible to add a copy of the file ppx with .exe extension on release build ^^/

Greetings

Cache issues

Reproduction:

  • yarn send-introspection-query api-url-X
  • write queries -> passes
  • yarn send-introspection-query api-url-Y (doesn't contains queries or mutations of API X)
  • yarn start -> still working
  • bsb -clean-world -> still working
  • changing the query -> recognise the different graphql_schema.json

In general, you see that the graphql_schema.json is cached in the ppx. Only a query change will trigger the new schema

Generate type for variables

I'm trying to improve the apollo graphql client to use a functor module to create a query fetching component and the last piece missing is the type of the variables of the query, similar to the one added for the return type.

The api I'm going for would look like:

module AppQuery = [%graphql {| ... |}];

module QueryRenderer = Client.Query.Create(AppQuery);

<QueryRenderer variables={"a": a}>...</Query>

This would allow to type the variables prop easily.

bsRecord on inline fragment

Hi,

I am trying to write a query containing union types. I first wrote a separated fragment and got a clear message: only inline fragments are supported with union types. So I wrote my query like this:

module Search = [%graphql
  {|
    query Search($query: String!, $searchType: SearchType!) {
      search(first: 10, query: $query, searchType: $searchType) @bsRecord {
        edges @bsRecord {
          node {
            ... on Dataset @bsRecord {
              id
              dataType
            }
          }
        }
      }
    }
  |}
];

But the bsRecord on the ... on Dataset @bsRecord does not work: the fragment is parsed as an object instead of a record. Is this a bug or am I doing something wrong?

Thanks :)

Comment support

It would be nice to support comments inside the GraphQL parser. Currently something like this doesn't parse at all:

module UserQuery = [%graphql
  {|
    query {
      thing {
        name # comment
      }
    }
  |}
];

It also gives a pretty vague error - "Unknown character" on the line after the #.

make arguments are note validated

module HeroQuery = [%graphql {|
query getPokemon($name: String!){
pokemon(name: $name) {
id
}
}
|}];

let query = HeroQuery.make(~name="Pikachu") /* Pass /
let query = HeroQuery.make() /
Should fail, but compiles */

Incompatibility with Node < v8.5.0

I got the following error while trying to install the package:

/foo/node_modules/graphql_ppx/copyPlatformBinaryInPlace.js:41
    fs.copyFileSync(filename, 'ppx');
       ^
TypeError: fs.copyFileSync is not a function
    at Object.<anonymous> (/foo/node_modules/graphql_ppx/copyPlatformBinaryInPlace.js:41:8)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:383:7)
    at startup (bootstrap_node.js:149:9)
    at bootstrap_node.js:496:3

I had Node v6.11.4, which doesn't support fs.copyFileSync (Added in: v8.5.0).

fs.copyFileSync(filename, 'ppx');

Since the error message isn't really helpful, I would suggest to either check the Node version or use some kind of polyfill.

Nested Unions makes compiler hang

I'm having an issue with GraphQL queries with nested unions causing the compiler to hang.

I'm trying to do something like the following

module Notifications = [%graphql {|
  query Notifications($limit: Int) {
  notifications(limit: $limit) {
    id
    createdAt {
      notification {
        ... on TypeA {
          aField
        }
        ... on TypeB {
          bField
          matches {
            ... on MatchC {
              cField
            }
            ... on MatchD {
              dField
            }
          }
        }
      }
    }
  }
}
|}];

If I remove the matches field, the query compiles in a few seconds (which seems a bit slow compared to normal).

I can't quite figure out whether this is an unsupported feature mentioned in the README but I wouldn't think so, given that it's normal unions of objects all the way down.

I wanted to try to diagnose it by setting the -verbose flag in bsconfig.json following the example given for -ast-out, but that just produced an error

Fatal error: exception Failure("graphql_ppx/ppx\\ -verbose not found when resolving ppx-flags")

Error while installing package

Hi,

I'm having such error while trying to install package:

The following actions will be performed:
  ∗  install graphql_ppx 0.0.3

=-=- Gathering sources =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=  🐫
[graphql_ppx] Archive in cache

=-=- Processing actions -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=  🐫
[ERROR] The compilation of graphql_ppx failed at "make install".
Processing  1/1: [graphql_ppx: make uninstall]
#=== ERROR while installing graphql_ppx.0.0.3 =================================#
# opam-version 1.2.2
# os           darwin
# command      make install
# path         /Users/pokora/.opam/4.02.3/build/graphql_ppx.0.0.3
# compiler     4.02.3
# exit-code    2
# env-file     /Users/pokora/.opam/4.02.3/build/graphql_ppx.0.0.3/graphql_ppx-31146-58c514.env
# stdout-file  /Users/pokora/.opam/4.02.3/build/graphql_ppx.0.0.3/graphql_ppx-31146-58c514.out
# stderr-file  /Users/pokora/.opam/4.02.3/build/graphql_ppx.0.0.3/graphql_ppx-31146-58c514.err
### stdout ###
# ocamlbuild -use-ocamlfind \
# 		-package ocaml-migrate-parsetree \
# 		-package result \
# 		-package yojson \
# 		-package ppx_tools.metaquot \
# 		-I src \
# 		graphql_ppx.native
### stderr ###
# make: opam-installer: No such file or directory
# make: *** [install] Error 1



=-=- Error report -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=  🐫
The following actions failed
  ∗  install graphql_ppx 0.0.3
No changes have been performed

Mutation optional variables

Hi @mhallin,

I have a question about forcing None values inside mutation variables as null. Is this correct?

[%expr fun v -> match v with None -> Js.Json.null | Some v -> [%e child_parser] v] [@metaloc loc]

I think it will be more correct to treat None as undefined, and Some(Js.Nullable.null) as null inside make function.

itemInput = {
  id: Int, // <----- Optional
  name: String!
};
mutation createItem($input: itemInput!) 

with CreateItem.make(~item={ "id": None, name: "foo" })" request payload will be {item: { id: null, name: "foo"}}.

if None will be treated as undefined - JSON.stringify will drop keys from request payload. I guess this can be tricky since Js.Json.t is used and undefined is not a JSON of course. We can filter them out then?

Array fields are always optional

In my schema I have fields like this:

  images: [Image!]! @relation(name: "SpotifySongOnImage")

PPX marks the type of this field as (in Reason syntax)

  images: option (array (Js.t {...}))

I don't think it's desired behavior. Here's the specification

Support (Free)BSD

BTW, how does one build graphql_ppx from source? The README doesn't say, it just suggests filing an issue.

Bug if a parameter has "query" as its name

Bug if a parameter has "query" as its name.

Example:

module SearchQuery = [%graphql
    {| query search_users($query: String!) {
            search(query: $query, page: 1) {
                id
                name
            }
        }
    |} ];

Interface support

Any plans to add Interface support soon? I wanted to use the package in a native app to query the github API, but it uses Interfaces heavily.

Extract mutation arguments params as a Js.t bag

Thanks for the great lib.

I want to use this lib with an Apollo binding I did, but I can't extract the right types right away to call the mutate Apollo function. Currently, I still need to manually like:

module SignUpQuery = [%graphql
  {|
  mutation signUp($input: SignUpInput!) {
    signUp(input: $input) {
      error
      token
    }
  }
|}
];

let query = GraphQLTag.gql(SignUpQuery.query);

module Types = {
  type signUpInput = {
    .
    "password": string, "firstName": string, "lastName": string, "email": string
  };
  type signUp = {. "error": Js.null_undefined(string), "token": Js.null_undefined(string)};
  type error = {. "message": string};
  type data = {. "loading": Js.boolean, "error": error, "signUp": signUp};
};

type signUp = {
  error: option(string),
  token: option(string)
};

let convert = (signUp: Types.signUp) : signUp => {
  error: Js.Null_undefined.to_opt(signUp##error),
  token: Js.Null_undefined.to_opt(signUp##token)
};

type data = Types.data;

type input = Types.signUpInput;

type response = Js.Promise.t(signUp);

I wished something like this was possible:

module SignUpQuery = [%graphql
  {|
  mutation signUp($input: SignUpInput!) {
    signUp(input: $input) {
      error
      token
    }
  }
|}
];

let query = GraphQLTag.gql(SignUpQuery.query);

module Types = {
  type signUpInput = SignUpQuery.queryInputBag; /* Have this bag */
  type signUp = SignUpQuery.queryPayloadBag; /* And this one */
  type error = {. "message": string};
  type data = {. "loading": Js.boolean, "error": error, "signUp": signUp};
};

type signUp = {
  error: option(string),
  token: option(string)
};

let convertInputToJS = query.parseInputToJS; /* Would get OCaml record and convert to input type */
let convert = query.parseResponse;

type data = Types.data;

type input = Types.signUpInput;

type response = Js.Promise.t(signUp);

Standalone type parsers

It'd be useful to have standalone type parsers so that one can define such parser wherever they want to parse an object of a given GraphQL type. I took a very simple approach of generating a parser for a type name in #14 . It was promising however it yielded huge objects which took forever to compile.

Later I thought about using type names for simple types and inline fragments for objects. It's not ideal since we might want to generate a recursive parser however I think it's good enough for most use cases. Generating such parsers should not be too challenging. For simple types one could take the approach from #14 and for Objects it's possible to parse the fragment using Document.inline_fragment and then call directly to unify_selection_set. The second case involves some internal refactorings but should be pretty straightforward.

trying to query array of scalars as object results in error

example of query:

{
  arrayOfStrings: {
    id
  }
}

 There's been an error running a preprocessor before the compilation of a file.
  This was the command:

 ./node_modules/graphql_ppx/ppx '/var/folders/c1/v8_9j1yn65dcm4g1ltl9968h0000gn/T/camlppx4cafcc' '/var/folders/c1/v8_9j1yn65dcm4g1ltl9968h0000gn/T/camlppxf84920'

Support arm64 platform

I get this message

error /home/user/[..]/node_modules/graphql_ppx: Command failed.
Exit code: 1
Command: node copyPlatformBinaryInPlace.js
Arguments:
Directory: /home/user/[..]/node_modules/graphql_ppx
Output:
graphql_ppx does not support this platform :(
graphql_ppx comes prepacked as built binaries to avoid large
dependencies at build-time.
If you want graphql_ppx to support this platform natively,
please open an issue at our repository, linked above. Please
specify that you are on the linux platform,

Would be awesome if this could support the arm64 (armv8) platform, I am running on a https://www.scaleway.com/ server.

Btw, thank you for a awesome library!

@bsField is showing up in query output

When I create a fragment

module Fragment = [%graphql
  {|
    fragment testFragment on Testing {
        item
    }
    |}
];

and then I use it in another query

module Query = [%graphql
  {|
  query getAll {
    testing {
       ...Fragment.TestFragment @bsField(name: "testing")
    }
  }
     
|}
];

The output includes the @bsField(name: "testing"):

query getAll {
    testing {
      ...testFragment @bsField(name: "testing")
   }
}


fragment testFragment on Testing {
  item
}

This is on "[email protected]"

Need bsField name when spreading fragments

Migrating from 0.2.7 from 0.2.8 creates errors when sprearding fragments:

  80 ┆ edges @bsRecord {
  81 ┆   node @bsRecord {
  82 ┆     ...Document
  83 ┆   }
  84 ┆ }

  You must use @bsField(name: "fieldName") to use fragment spreads

This is really strange because I've seen a test for this:

module ExternalFragmentQuery = [%graphql {|
  fragment Fragment on VariousScalars @bsRecord {
    string
    int
  }
   {
    variousScalars {
      ...Fragment
    }
  }
|}];

Not sure what is the difference between my code and the test.

Validation for (required) query / mutation arguments

The same way this PPX validates if a queried field is not present in the schema, I think it could validate that a required argument isn't passed to a query or a mutation?

I just ran into this and the server threw a runtime error. I think it could be caught at compile time. Is this on the roadmap?

None's are converted to null, which Postgres doesn't like

I'm using postgraphile on the backend to speed up data development.

I'm creating a new object, and the type in the module generated from the %graphql expression results in option(Js.Json.t) for my id. I pass None for the id, and it's sent across in the graphql request in the variables as "id": null.

Postgraphile sees that "id": null and tries to set the id field in the database to null, which Postgres understandably complains about: null value in column "id" violates not-null constraint

Here's my module:

module CreateEvent = [%graphql
  {|
    mutation CreateEvent($input: CreateEventInput!) {
      createEvent(input: $input) {
        event {
          id
          createdAt
          updatedAt
          name
          date
          eventType
          location
          description
          owner: userByOwner {
            id
            name
            headline
          }
        }
      }
    }
  |}
];

And the resulting signature of the make function's arguments:

(
  ~input: Js.t(
    {
      ..
      clientMutationId : option(string),
      event : Js.t(
        {
          ..
          createdAt : option(Js.Json.t),
          date : Js.Json.t,
          description : option(string),
          eventType : [< `DEFAULT | `MEETUP ],
          id : option(Js.Json.t),
          location : option(string),
          name : string,
          owner : option(Js.Json.t),
          updatedAt : option(Js.Json.t)
        }
      )
    }
  ),
  unit
)

If I pass None for any of those optional values, they're sent across to the server as null. The server thinks I'm intentionally nullifying those fields. How can I work around?

Thanks for a great project!

schema.json not found

I have a schema.json at root of project using apollo schema:download.
When running npm start, i got error:

Fatal error: exception Failure("graphql_ppx/ppx\\ -schema=schema.json not found when resolving ppx-flags")

My bsconfig.json:

"bs-dependencies": [
    "reason-react",
    "reason-apollo",
    "@glennsl/bs-json"
  ],
  "ppx-flags": [
    "graphql_ppx/ppx\\ -schema=schema.json"
  ],

When creating a parametrised query the leading $ is discarded

When I create a parametrised query like this:

   query ($contentType: String!) {
     File(contentType: $contentType) {
       id
     }
   }

The result is:

"query (contentType: String!)  {\\nFile(contentType: $contentType)  {\\nid  \\n}\\n}"

It is invalid, because the desired result is:

"query ($contentType: String!)  {\\nFile(contentType: $contentType)  {\\nid  \\n}\\n}"

Mutations not compiling: `Option.Option_unwrap_error`

I'm getting some inscrutable errors. When I paste the following example from the README:

module SignUpQuery = [%graphql
  {|
mutation($name: String!, $email: String!, $password: String!) {
  signUp(email: $email, email: $email, password: $password) @bsVariant {
    user {
      name
    }

    errors {
      field
      message
    }
  }
}
|}
];

I get the following output:

>>>> Start compiling
Rebuilding since [ [ 'change', 'Query.re' ] ]
ninja: Entering directory `lib/bs'
[1/2] Building client/src/Query.mlast
FAILED: client/src/Query.mlast
/Users/jessep/Dropbox/Code/movement/node_modules/bs-platform/lib/bsc.exe -pp "/Users/jessep/Dropbox/Code/movement/node_modules/bs-platform/lib/refmt.exe --print binary" -ppx '/Users/jessep/Dropbox/Code/movement/node_modules/bs-platform/lib/reactjs_jsx_ppx_2.exe'  -ppx /Users/jessep/Dropbox/Code/movement/node_modules/graphql_ppx/ppx -w -30-40+6+7+27+32..39+44+45+101 -bs-suffix -nostdlib -I '/Users/jessep/Dropbox/Code/movement/node_modules/bs-platform/lib/ocaml' -no-alias-deps -color always -c -o client/src/Query.mlast -bs-syntax-only -bs-binary-ast -impl /Users/jessep/Dropbox/Code/movement/client/src/Query.re
Option.Option_unwrap_error
File "/Users/jessep/Dropbox/Code/movement/client/src/Query.re", line 1, characters 0-0:
Error: Error while running external preprocessor
Command line: /Users/jessep/Dropbox/Code/movement/node_modules/graphql_ppx/ppx '/var/folders/5x/w6vwvtzn1ts97f42_0kf74200000gn/T/camlppx803ef4' '/var/folders/5x/w6vwvtzn1ts97f42_0kf74200000gn/T/camlppx4a607c'

ninja: error: rebuilding 'build.ninja': subcommand failed
>>>> Finish compiling(exit: 1)

I have no idea what to do here. Here are versions of bs-platform and graphql_ppx From my package.json

 "bs-platform": "^3.1.5",
  "graphql_ppx": "^0.2.4"

signature of parse

Hi @mhallin

I encountered problem trying to update Apollo cache with readQuery/writeQuery. I need to know shape of Js.Json.t which is passed to parse (Js.Json.t => t). Parse guarantees that JSON shape is valid in the runtime but it should be possible to have it as a static type based on gql query string?

Cache holds Js.Json.t. It's type is unknown currently and it will be incorrect to use t if @bsRecord is used.

module MyQuery: {
  type t;
  let make: unit => Js.t({ . parse: Js.Json.t => t, query: string, variables: Js.Json.t });
};

Would it be possible to have alternative version that trusts that data comes from graphql endpoint? In other words less runtime type save (which should not be the case if data comes from graphql server) but more exact and performant. So it will parse json only when needed, for instance for @bsRecord.

module MyQuery: {
  type jsT = <JSON type parsed from graphql query string>
  type t;
  let make: unit => Js.t({ . parse: jsT => t, query: string, variables: Js.Json.t });
};

Or at least some raw jsT could be introduced to use with apollo cache

types of input objects?

I'm a little confused about what if any record types are generated for input objects defined in a schema. If I have a mutation with an input object, how do I go about creating an instance to pass in my variables? Just a pointer to a bit of sample code would probably do the trick.

Treat custom scalars as Js.Json.t

Custom scalars are sometimes unavoidable if working with an existing system. I suggest that we should treat custom scalars as Js.Json.t and let the user decode/encode them accordingly. In the future we're probably gonna need another solution but it's good enough for now IMO.

@ulrikstrid played with a POC implementation which works fine but we couldn't get parameters to work. What do you think?

Collaborate

Hey! reaching out to see how we can collaborate between graphql_ppx and regql(a client) to provide reason users a great experience. I've gone ahead an pinged you on discord. I'd love to exchange ideas 😀

Don't generate field names starting with an uppercase letter

E.g. for the following GraphQL query:

{
  hero {
    Name
  }
}

It seems the PPX is generating the following type:

Js.t {. hero: Js.t {. Name : string } }

This is leading to a compile error when trying to extract the Name field, since field names in OCaml can't start with uppercase letters.

The solution is to check for an uppercase first letter in a query field and prefix it with an underscore character (_). BuckleScript will strip the underscore from JS output so it will have no effect on the final output.

The interim workaround is to define a 'getter' for a field that starts with an uppercase letter, e.g.:

external getName : Js.t 'a => string = "Name" [@@bs.get];

This workaround is future-proof against the proposed fix, so users can upgrade without breakage.

cc @ulrikstrid

OCaml support

Is there any reason why this package does not support native OCaml? I quickly perused the source code and I didn't see anything JavaScript specific.

Generated AST is not compat with graphql-tag

This is a list of differences between the AST generated by this ppx and graphql-tag

1. Simple query

Query:

module PatientsQuery = [%graphql
  {|
  query allPatients {
    patients: patientsConnection(first: 20) {
      edges {
        node {
          id
          patientId: externalId
          phone
          address
          name
          gender
          ageInYears
        }
      }
    }
  }
  |}
];

ppx : https://gist.github.com/thangngoc89/707524bb00dcc573f69f60bb0a776ac9#file-1-ppx-json
tag: https://gist.github.com/thangngoc89/707524bb00dcc573f69f60bb0a776ac9#file-1-tag-json

Diff (left: ppx, right: tag)
image

After I edit the generated JS, apollo client could use this without issue

2. Query with arguments

This is the same query with above but with arguments and input

query allPatients($first: Int!) {
    patients: patientsConnection(first: $first) {
      edges {
        node {
          id
          patientId: externalId
          phone
          address
          name
          gender
          ageInYears
        }
      }
    }
  }

ppx : https://gist.github.com/thangngoc89/707524bb00dcc573f69f60bb0a776ac9#file-2-ppx-json
tag: https://gist.github.com/thangngoc89/707524bb00dcc573f69f60bb0a776ac9#file-2-tag-json

Diff (left: ppx, right: tag)
image

In this case, even if I edited this generated JS, apollo-client stills send an malformed query like this (notice the [Object object] )

image

Parsing error when using fragments

I am trying to build a query with fragments, it looks like this:

module Fragments = [%graphql
  {|
    fragment documentFields on Document {
      sourceText
    }
  |}
];

module GetDataset = [%graphql
  {|
    query dataset($owner: String!, $name: String!) {
      dataset(name: $name, owner: $owner) {
        name
        dataType
        datapoints(first: 100) {
          edges {
            node {
              ...Fragments.DocumentFields @bsField(name: "document")
            }
          }
        }
      }
    }
  |}
];

Unfortunately, running this yields a GraphQLError: Syntax Error: Cannot parse the unexpected character ".".

Am I doing something wrong with the query?

Skip/Include directives are parsed incorrectly

Hi!

module MyQuery = [%graphql
{|
query ($var: Boolean!) {
v1: variousScalars {
nullableString @skip(if: $var)
string @skip(if: $var)
}
v2: variousScalars {
nullableString @include(if: $var)
string @include(if: $var)
}
}
|}
];

Using this directives results in a runtime error because parse code assumes that field should be null or correct type

        var match$7 = value$1["committedFields"];
        var tmp$3;
        if (match$7 !== undefined) {
          var match$8 = Js_json.decodeNull(match$7);
          ...
        } else {
          throw [
                Graphql_error$2,
                "Field committedFields on type Module is missing"
              ];
        }

Installation fails in CircleCI

Thanks for the great package.
I'm tried to setup a CD in CircleCI and I'm always getting this:

Arguments: -c if [ ! -f ./ppx ]; then ln -s "$(opam config var graphql_ppx:bin)/graphql_ppx.native" ./ppx && chmod +x ppx; fi
Directory: /home/circleci/repo/node_modules/graphql_ppx
Output:
chmod: cannot operate on dangling symlink ‘ppx’

this is my .circleci.yml

version: 2
jobs:
  build:
    environment:
      - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'
      - TERM: xterm
    docker:
      - image: circleci/node:8.0
      - image: circleci/android:api-23-alpha

    working_directory: ~/repo

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "package.json" }}
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-

      - run:
          name: Install opam
          command: wget https://raw.github.com/ocaml/opam/master/shell/opam_installer.sh -O - | sh -s /usr/local/bin
      - run: opam switch 4.02.3
      - run:
          name: Install opam deps
          command: eval $(opam config env) && opam update && opam install -y jbuilder
      - run:
          name: Install node deps
          command: eval $(opam config env) && yarn install

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}
        
      # run tests!
      - run: yarn test

      - run:
          command: |
            yarn android:release:generate

      - store_artifacts:
          path: android/app/build/outputs/apk/
          destination: android
      - store_artifacts:
          path: android/app/build/reports
          destination: android

Linux (manjaro) bug

Greetings!

I have ran into an issue where in i was able to build a working version of my query on my mac but when i switched to a linux computer using manjaro i cannot get it to build. The error i get when building is:

ppx: loadlocale.c:129: _nl_intern_locale_data: Assertion cnt < (sizeof (_nl_value_type_LC_TIME) / sizeof (_nl_value_type_LC_TIME[0]))' failed.`

I have found a few other instances of libraries having issues around locale with arch linux and derivatives like manjaro. Have you heard any reports of this and have any guidance on how to resolve?

schema change is not detected

Hi.
I use latest version, and saw caching introduced with it. I noticed that when the server schema changes (and update at client through yarn send-introspection-query ..) "bsb -make-world" doesn't report any graphql error. If I change the query (within [%graphql .... |}];), it triggers error. As if the ppx triggers only when the graphql usage changes. Not a big deal, but wonder a way to force it when schema changes during a build or not in watch mode.

Thanks.
bsr

add __typename for apollo-client

Per apollo-codegen docs for TypeScript and Flow:

https://github.com/apollographql/apollo-codegen

If you're using a client like apollo-client that does this automatically for your GraphQL operations, pass in the --add-typename option to apollo-codegen to make sure the generated Typescript and Flow types have the __typename field as well. This is required to ensure proper type generation support for GraphQLUnionType and GraphQLInterfaceType fields.

It would be good to add this in graphql_ppx too. I could have more reusable types in reason-apollo

Qn: nested polymorphic type with @bsRecord

Hi,
I wonder whether I can use polymorphic record with @bsRecord. When working with a relayjs response format, there are lot of nesting and which result in that many option checking. I tried something like below, but got error. I am new to Reason, and hope didn't make silly mistakes.


  We've found a bug for you!
  (No file name)

  This has type:
    option(string)
  But somewhere wanted:
    string

type repo_t = {
  id: string,
  name: string,
};

type edge_t('a) = {node: 'a};

type viewer_t('a) = {
  name: string,
  email: string,
  repositories: array(edge_t('a)),
};

type viewer = viewer_t(repo_t);

module GetRepositories = [%graphql
  {|
  query GetRepositories {
    viewer @bsRecord {
      name
      email
      repositories(first: 10) {
        edges {
          node {
            id
            name
          }
        }
      }
    }
  }
|}
];

Any help is greatly appreciated.
thanks.
bsr.

GraphqlObjectType __typename

Hi @mhallin, thanks for this project! ❤️

I think GraphqlObjectType also allows __typename.

let add_typename = match ty with | Interface _ | Union _ -> true | _ -> false in

https://graphqlbin.com/v2/7L82Tn (not sure if it is object there) but my local graphql sends it with Objects.

Currently I am not allowed to use it in my code

  16 ┆ renderer(id: $id) {
  17 ┆   label
  18 ┆   __typename
  19 ┆
  20 ┆ }

  Unknown field on type Renderer

Parsing array result type

Given this common pattern for an array in GraphQL:

type FooConnection {
  edges: [String]!
}

This means edges always return an array but it could contains 0 or more elements.
The parsed type from graphql_ppx is:

{
  .
  "edges": array(option(string))
}

IMHO, this is incorrect type and it makes it annoying to work with. But still, I can't think of a clever way to express the above GraphQL type in ReasonML

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.