1# purescript-yoga-fetch-om
3Derive type-safe fetch clients from `purescript-yoga-http-api` route definitions.
8spago install yoga-fetch-om
11Optional query examples below use [`justifill`](https://pursuit.purescript.org/packages/purescript-justifill):
14spago install justifill
19Version 0.8 exposes optional query parameters as explicit `Maybe` fields. Calls that passed plain values no longer compile:
23users <- api.listUsers { limit: 10 }
25-- 0.8: every optional field is explicit
26users <- api.listUsers { limit: Just 10, offset: Nothing }
27allUsers <- api.listUsers { limit: Nothing, offset: Nothing }
30Import `Maybe(..)` from `Data.Maybe` when constructing these records directly. Alternatively, [`justifill`](https://pursuit.purescript.org/packages/purescript-justifill) wraps supplied values in `Just` and fills omitted fields with `Nothing`:
33-- offset remains omitted at the call site
34users <- api.listUsers (justifill { limit: 10 })
37Path parameters and request bodies keep their existing argument shapes.
41### Example 1: Simple GET and POST Requests
44import Yoga.Fetch.Om.Simple (get, post)
46type User = { id :: Int, name :: String, email :: String }
50 user <- get @User "https://api.example.com/users/42" {}
53 -- Simple POST request with JSON body
54 newUser <- post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
55 log $ "Created user with ID: " <> show newUser.id
58### Example 2: Type-Safe API Client with Full CRUD
61import Justifill (justifill)
66 ( ok :: { body :: { status :: String } } )
68 Route GET ("users" / "id" : Int) {}
69 ( ok :: { body :: User }
70 , notFound :: { body :: ErrorMessage }
73 Route GET ("users" :? { limit :: Int, offset :: Int }) {}
74 ( ok :: { body :: Array User } )
76 Route POST "users" { body :: JSON CreateUserRequest }
77 ( created :: { body :: User }
78 , badRequest :: { body :: ErrorMessage }
81 Route PUT ("users" / "id" : Int) { body :: JSON UpdateUserRequest }
82 ( ok :: { body :: User }
83 , notFound :: { body :: ErrorMessage }
84 , badRequest :: { body :: ErrorMessage }
87 Route DELETE ("users" / "id" : Int) {}
88 ( noContent :: { body :: {} }
89 , notFound :: { body :: ErrorMessage }
93api = client @UserAPI "https://api.example.com"
95-- Generated methods use ordinary function application.
97user <- api.createUser { name: "Alice", email: "alice@example.com" }
99 { badRequest: \err -> do
100 log $ "Validation error: " <> err.error
104-- Path parameters and request bodies remain separate arguments.
105updated <- api.updateUser
107 { name: "Alice Updated", email: user.email }
109 { notFound: \_ -> throw userNotFound
110 , badRequest: \err -> throw validationError
113-- Optional query parameters are explicit `Maybe` fields. Justifill can wrap
114-- supplied values in `Just` and fill omitted fields with `Nothing`.
115users <- api.listUsers (justifill { limit: 10 })
117-- The equivalent call without Justifill:
118allUsers <- api.listUsers { limit: Nothing, offset: Nothing }
123See the test files for complete, runnable examples with all imports:
125- **[`test/Simple.Spec.purs`](test/Simple.Spec.purs)** - Basic GET/POST/PUT/PATCH/DELETE requests
126- **[`test/Complete.Example.purs`](test/Complete.Example.purs)** - Full CRUD API with error handling
127- **[`test/BuildUrl.Spec.purs`](test/BuildUrl.Spec.purs)** - Path parameters and query strings
128- **[`test/Variant.Spec.purs`](test/Variant.Spec.purs)** - Response variant handling
129- **[`test/SplitParams.Spec.purs`](test/SplitParams.Spec.purs)** - Parameter extraction patterns
133### ✅ Type-Safe Everything
135- **Path parameters**: `/users/:id` requires `{ id :: Int }`; names and values are percent-encoded
136- **Query parameters**: Type-safe, percent-encoded query strings with explicit `Maybe` fields for optional values
137- **Request bodies**: `JSON`, `PlainText`, and URL-encoded `FormData`
138- **Response bodies**: Parsed JSON, `PlainText` as `String`, and `Streaming a` as `Strom {} () a`
139- **Error handling**: Exhaustive pattern matching on variants
141### ✅ Single Source of Truth
143Define your API once, use it everywhere:
146-- Server (yoga-fastify-om)
147server = buildServer apiRoutes handlers
149-- Client (yoga-fetch-om)
150apiClient = client @apiRoutes baseUrl
153Changes to routes automatically update both client and server!
155### ✅ Automatic Derivation
157No manual client code:
158- ✅ URL building with segment-safe path substitution
159- ✅ Query construction that preserves existing queries and fragments
160- ✅ JSON, plain-text, and URL-encoded request serialization
161- ✅ JSON and plain-text response decoding
162- ✅ Incremental UTF-8 and binary response streams
163- ✅ Status code → variant mapping
165### ✅ Integration with yoga-om
167Works seamlessly with the Om monad:
170getUserProfile :: Om AppContext AppErrors User
172 { api, userId } <- ask
173 api.getUser { id: userId }
174 # handleErrors { notFound: \_ -> throw { userNotFound: userId } }
179The library uses PureScript's type system to:
1811. **Extract parameters** from route definitions at compile time
182 - Path params: `"users" / "id" : Int` → `{ id :: Int }`
183 - Optional query params: `:? { limit :: Int }` → `{ limit :: Maybe Int }`
184 - Required query params: `:? { limit :: Required Int }` → `{ limit :: Int }`
185 - Body params: `{ body :: JSON User }` → `User`
1872. **Build URLs** automatically
188 - Pattern: `/users/:id` + params: `{ id: 42 }` → `/users/42`
189 - Query: `?limit=10&offset=20`
1913. **Make requests** with `js-fetch` and `js-promise-aff`
1934. **Parse responses** by mapping status codes to variant labels
194 - `200` → `"ok"`, `404` → `"notFound"`, etc.
196All automatically based on your route types!
203client :: forall @routes. String -> Record clients
206Derives a record of client functions from a record of routes using Visible Type Application.
209- `@routes` - Your API route type (provided via VTA syntax)
210- `baseUrl` - Base URL (e.g., `"https://api.example.com"`)
212**Returns:** Record where each route becomes a function returning `Om context errors result`
216api = client @UserAPI "https://api.example.com"
221- ✅ Core client derivation with VTA syntax
222- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
223- ✅ Path, query, and body parameters
224- ✅ Request/response headers
225- ✅ JSON encoding/decoding
227- ✅ Variant response handling
228- ✅ Om monad integration
234bun run test:compile-fail
237The suite combines example-based specifications with QuickCheck properties for
238URL encoding, suffix preservation, repeated parameters, optional fields, and
239query-separator invariants.
241The coverage command uses compiler source maps to report PureScript source
242coverage, includes JavaScript FFI modules, enforces regression thresholds, and
243writes HTML and LCOV reports to `coverage/`.
251- [purescript-yoga-http-api](https://github.com/rowtype-yoga/purescript-yoga-http-api) - HTTP API type definitions
252- [purescript-yoga-fastify-om](https://github.com/rowtype-yoga/purescript-yoga-fastify-om) - Server-side counterpart
253- [purescript-yoga-json](https://github.com/rowtype-yoga/purescript-yoga-json) - JSON serialization
254- [purescript-yoga-om](https://github.com/rowtype-yoga/purescript-yoga-om) - Om monad
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list