Type-safe GraphQL client generator for Gleam
1# Squall 2 3A type-safe **sans-io** GraphQL client generator for Gleam. 4 5![Squall](https://images.unsplash.com/photo-1698115989309-16f9d34c04e9?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=2340) 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