npm package: @reasonql/core
Description: Type-safe and simple GraphQL client for ReasonML
Author: sainthkh
Latest version: 0.3.1

# ReasonQL: GraphQL in ReasonML way. 

ReasonQL is a type-safe and simple GraphQL library for ReasonML developers.

ReasonQL does 2 things for you: 

* fetch GraphQL data from server. 
* and decode that data and error messages from JSON to ReasonML record. 

You might think it's too simple and you're now finding cool features like cache, "fetch more", reload, auth, etc.
==> If so, please check ["Why I started this project"](../../WHY.md).

## Installation. 

You need 2 packages: `@reasonql/core` and `@reasonql/compiler`. Install it with npm like below: 

```
npm i @reasonql/core
npm i -D @reasonql/compiler
```

or with yarn

``` 
yarn add @reasonql/core
yarn add @reasonql/compiler --dev
```

And add @reasonql/core in `bs-dependencies` under bsconfig.json.

```json
"bs-dependencies": [
  "@reasonql/core",
  "reason-react",
]
```

## How to use ReasonQL.

This document assumes that you're familiar with ReasonML and GraphQL. If you're not sure what GraphQL is, [check the offical documentation](https://graphql.org/learn/). 

1\. Write Query in a Reason file. 

```reason
let query = ReasonQL.gql({|
  query AppQuery {
    hello {
      message
    }
  }
|})
```

Don't forget to use `ReasonQL.gql` function and `{||}` multiline string. ReasonQL compiler uses them to find if a file has graphql code or not. 

**WARNING:** The query name (`AppQuery` above) is used for the name of the file generated by the compiler. So, do not use duplicate query names. Compiler doesn't warn this and multiple queries will try to overwrite a single type file. 

2\. Set up compiler.

To compile GraphQL queries, we need the path to the GraphQL schema file. Create `reasonql.config.js` at the project root and fill it like below: 

```js
module.exports = {
  schema: "path/to/schema.js",
}
```

3\. Compile the query.

Add the below command to `package.json`:

```json
"scripts": {
  "reasonql": "reasonql-compiler"
}
```

And run the command with `npm run reasonql`.

You can find AppQuery.re under `src/.reasonql`. 

**Note:** As the files under `src/.reasonql` are generated by the ReasonQL Compiler, it is recommended to ignore the folder in .gitignore. 

**Note2:** It is a really tedious job to type in `npm run reasonql` each time when queries are changed. So, when in development, use the option, `-w`, like below:

```json
"scripts": {
  "reasonql": "reasonql-compiler",
  "reasonql:dev": "reasonql-compiler -w"
}
```

It watches reason files and regenerate files **only when** the GraphQL queries are changed. 

4\. Create `Request` module. 

```reason
module Request = ReasonQL.MakeRequest(AppQuery, {
  let url = "http://localhost:4000";
});
```

`MakeRequest` functor receives 2 arguments: a module generated by the ReasonQL compiler and a module that contains the link to the server. 

5\. Send request and handle the response. 

```reason
Request.send(Js.Dict.empty())
->Request.finished(data => {
  Js.log("Data fetched.");
})
Js.log("Loading data...");
```

All you need to remember are these 3 functions: 

* `send(argumentRecord)`
* `finished(promise, data => unit)`
* `finishedWithError(promise, (data, option(error)) => unit)`

As `send` returns a `Js.Promise` and `finished` and `finishedWithError` have promise as their first argument, we can use [the pipe syntax](https://reasonml.github.io/docs/en/pipe-first) here. 

You learned the basics of ReasonQL. Unlike other libraries like Apollo or Relay, you don't need to create React components to use GraphQL. 

If you want to know how to make "hello world" with ReasonQL, [check the example.](../snippets/hello-world) 



## Other Features

### Type Conversions

5 scalar types (`ID`, `Int`, `Float`, `String`, `Boolean`) of GraphQL are converted into appropriate ReasonML types(`string`, `int`, `float`, `string`, `bool`). ([By definition](https://graphql.org/learn/schema/#scalar-types), `ID` type is serialized into `STRING`.)

Object types are compiled into ReasonML types. 

```graphql
# Schema
type Greeting {
  hello: String!
}

type Test1 {
  a: Greeting!
}

# Query
query AppQuery {
  a {
    hello
  }
}
```

```reason
type a = {
  hello: string,
};

type queryResult = {
  a: a,
};
```

**Note:** As you can see, object type name doesn't follow the actual name(`greeting`) in the type definition, but uses the variable name(`a`) to avoid type name conflict. More about name conflict below.

### Nullability

In GraphQL, the field types without `!` are nullable. So, they're translated into `option`-ed types in ReasonML.

Example:

```graphql
# Schema
type Query {
  a: String
  b: String!
}

# Query
query Test1 {
  a
  b
}
```

```reason
type queryResult = {
  a: option(string),
  b: string,
}
```

**Note:** When a type is an `option`, we need to use pattern matching to cover all cases. In ReasonML, it's tedious and sometimes meaningless. So, when you define the schema for your app, always consider when `null` should be used. If you cannot find the meaningful case, add `!`. (Unlike many JavaScript examples, you'll find yourself adding many non-null types in ReasonML apps.)

### Enum types

Conventionally, GraphQL enum values are written in all capital with underscores like `EXTRA_LARGE`. And they're strings internally. 

However, ReasonML uses camel case with the first letter capital-cased. And they're compiled into numbers. 

ReasonQL compiler translates that perfectly. 

```graphql
enum PatchSize {
  SMALL
  MEDIUM
  LARGE
  EXTRA_LARGE
}
```

```reason
type patchSize = 
  | Small
  | Medium
  | Large
  | ExtraLarge
  ;
```

Unlike other types, encoders and decoders of enum types are defined in `EnumTypes.re` file. And the functions are imported to each type file. 

### Name conflict and renaming types

Sometimes, field names of object types can conflict like below: 

```graphql
# Schema
type Query {
  hero: Person!
  villain: Person!
}

type Person {
  name: Name!
  ship: Ship
}

type Name {
  first: String!
  last: String
}

type Ship {
  name: String!
}

# Query
query AppQuery {
  hero {
    name @reasontype(name:"heroName") {
      first
      last
    }
    ship {
      name
    }
  }
  villain {
    name {
      first
    }
    ship @reasontype(name:"villainShip") {
      name
    }
  }
}
```

```reason
type heroName = {
  first: string,
  last: option(string),
};

type hero_ship_Ship = {
  name: string,
};

type hero = {
  name: heroName,
  ship: option(hero_ship_Ship),
};

type villain_name_Name = {
  first: string,
};

type villainShip = {
  name: string,
};

type villain = {
  name: villain_name_Name,
  ship: option(villainShip),
};

type queryResult = {
  hero: hero,
  villain: villain,
};
```

Both hero and villain have `name` and `ship`. In those cases, the type names are generated with the list of the names in the path and schema type name(i.e. `hero_ship_Ship`, `villain_name_Name`).

To avoid this, you can use `@reasontype` directive like `@reasontype(name:"villainShip")`. 


### Define singular name. 

Sometimes, it is logical to name a variable in plural and its type in singular like below: 

```graphql
# Schema
type Query {
  posts: [Post!]!
}

type Post {
  title: String!
  slug: String!
  content: String!
  summary: String
}

# Query
query AppQuery {
  posts @singular(name:"post") {
    title
    slug
    content
    summary
  }
}
```

```reason
type post = {
  title: string,
  slug: string,
  content: string,
  summary: option(string),
};

type queryResult = {
  posts: array(post),
};
```

### Automatically merges fragments into the main query.

Borrowed the idea from Relay. When writing code, it is always a good idea to put related things together in one place. With `fragment`s, you can define the data a component needs in the same file like below: 

```reason
let query = ReasonQL.gql({|
  fragment PostFragment_post on Post {
    title
    summary
    slug
    ...ButtonFragment_post
  }
|})

let component = ReasonReact.statelessComponent("Post")

let make = (
  ~post: PostFragment.post,
  _children
) => {
  ...component,
  /* Code here */
}
```
[You can read the full code here.](../snippets/fragments/client/src/Post.re)

Then, ReasonQL compiler magically merges fragments into the main query. 

### Mutation and Arguments

Mutations work in the same way like queries. Use `MakeRequest` functor and `send`, `finished` functions. But in mutations, you need to use arguments a lot. 

When there are arguments, the type of the argument of `send` function changes from `Js.Dict` to a specific variables. 

So, we need to write code like below: 

```reason
let saveTweet = ReasonQL.gql({|
  mutation SaveTweetMutation($tweet: TweetInput!) {
    saveTweet(tweet: $tweet) {
      success
      id
      tempId
      text
    }
  }
|})

module SaveTweet = ReasonQL.MakeRequest(SaveTweetMutation, Client);

SaveTweet.send({
  tweet: {
    text: tweet.text,
    tempId: tweet.id,
  }
})
->SaveTweet.finished(data => {
  Js.log("data recieved");
})
```
[You can read the full code here.](../snippets/mutation/client/src/App.re)

### Errors

Apollo Server provides a lot of [error types](https://blog.apollographql.com/full-stack-error-handling-with-graphql-apollo-5c12da407210). Among them, you need to provide your own type definition for `UserInputError`. So, we need decoders for those errors.

To do so, create special GraphQL schema file: `errors.graphql`.

And write down error types like below: 

```graphql
type LoginFormError {
  code: String!
  email: String
  password: String
}
```

And add the path to `errors.graphql` to `reasonql.config.js` like below: 

```js
module.exports = {
  schema: "../server/src/schema.js",
  errors: "../server/src/errors.graphql",
}
```

Then, the decoder will be generated at `.reasonql/QueryErrors.re`. Now, you can decode error contents like below: 

```reason
Login.send({ email, password })
->Login.finishedWithError((result, errors) => {
  switch(errors) {
  | None => {
    login(Belt.Option.getExn(result.login));
    ReasonReact.Router.push("/");
  }
  | Some(errors) => {
    let {email, password}: QueryErrors.loginFormError 
      = QueryErrors.decodeLoginFormError(errors[0].extensions);
    self.send(ShowError(email, password));
  }
  }
})
```

## Compiler options

* Commandline options
 - `-w`, `--watch`: watch reason files and generate reasonql type files only when query code changes. 

* Config file options
 - `schema`: **required**. The path to the GraphQL schema file. 
 - `errors`: The path to the error schema file. 
 - `src`: The root path of the reasonml files. Default: `./src`.
 - `include`: The files should be included from compilation. Default: `**`.
 - `exclude`: The files should be excluded from compilation. Default: `[ '**/node_modules/**', '**/__mocks__/**', '**/__tests__/**', '**/.*/**', ]`
 - `watch`: Watch files. Default: `false`.

## Contribution

Helps are always welcome. If you want to check how to contribute to the project, [check this document](../../CONTRIBUTION.md).