Type-safe GraphQL client generator for Gleam
1# Squall
2
3A type-safe **sans-io** GraphQL client generator for Gleam.
4
5
6
7> This project is in early development and may contain bugs/unsupported GraphQL queries.
8
9## What is Sans-IO?
10
11Squall generates functions to **build HTTP requests** and **parse HTTP responses**, but doesn't send them. You control the HTTP layer.
12
13**Benefits:**
14- Can be used on both erlang and javascript runtimes
15- Use any HTTP client you want
16- Easy to test without mocking
17- Full control over retries, timeouts, logging
18
19## Installation
20
21```bash
22gleam add squall
23```
24
25## Quick Start
26
271. **Create a `.gql` file** at `src/my_app/graphql/get_user.gql`:
28
29```graphql
30query GetUser($id: ID!) {
31 user(id: $id) {
32 id
33 name
34 email
35 }
36}
37```
38
392. **Generate code:**
40
41```bash
42gleam run -m squall generate https://api.example.com/graphql
43```
44
453. **Use it:**
46
47```gleam
48import squall
49import my_app/graphql/get_user
50import gleam/httpc
51
52pub fn main() {
53 // 1. Create client (just config)
54 let client = squall.new("https://api.example.com/graphql", [])
55
56 // 2. Build request (no I/O)
57 let assert Ok(request) = get_user.get_user(client, "123")
58
59 // 3. Send request (you control this)
60 let assert Ok(response) = httpc.send(request)
61
62 // 4. Parse response (no I/O)
63 let assert Ok(data) = get_user.parse_get_user_response(response.body)
64
65 // 5. Use the data
66 io.debug(data.user)
67}
68```
69
70## How It Works
71
72For each `.gql` file, Squall generates two functions:
73
74```gleam
75// Builds HTTP request - no I/O
76pub fn get_user(client: Client, id: String) -> Result(Request(String), String)
77
78// Parses response - no I/O
79pub fn parse_get_user_response(body: String) -> Result(GetUserResponse, String)
80```
81
82You send the request with your own HTTP client.
83
84## JavaScript Target
85
86Same generated code works on JavaScript. Just use a different HTTP client:
87
88```gleam
89import gleam/fetch
90import gleam/javascript/promise
91
92let client = squall.new("https://api.example.com/graphql", [])
93let assert Ok(request) = get_user.get_user(client, "123")
94
95fetch.send(request)
96|> promise.try_await(fetch.read_text_body)
97|> promise.map(fn(result) {
98 case result {
99 Ok(response) -> {
100 let assert Ok(data) = get_user.parse_get_user_response(response.body)
101 io.debug(data.user)
102 }
103 Error(_) -> io.println("Request failed")
104 }
105})
106```
107
108## Type Mapping
109
110| GraphQL Type | Gleam Type |
111|--------------|------------|
112| `String` | `String` |
113| `Int` | `Int` |
114| `Float` | `Float` |
115| `Boolean` | `Bool` |
116| `ID` | `String` |
117| `[Type]` | `List(Type)` |
118| `Type` (nullable) | `Option(Type)` |
119| `Type!` (non-null) | `Type` |
120| Custom Objects | Custom Gleam types |
121
122## CLI Commands
123
124```bash
125# Generate code
126gleam run -m squall generate <endpoint>
127
128# Example
129gleam run -m squall generate https://rickandmortyapi.com/graphql
130```
131
132## Examples
133
134See the [examples/](./examples/) directory for complete working examples:
135
136- **[01-basic](./examples/01-basic/)** - Basic usage with Erlang/JavaScript targets
137- **[02-lustre](./examples/02-lustre/)** - Frontend app using Lustre
138
139## Roadmap
140
141- [ ] Directive handling (`@include`, `@skip`)
142- [ ] Custom scalar type mapping
143- [ ] Schema caching
144
145## License
146
147Apache-2.0