@@ -8,12 +8,10 @@ Derive type-safe fetch clients from `purescript-yoga-http-api` route definitions
88spago install yoga-fetch-om
99```
1010
11−## Progressive Examples
11+## Quick Start
1212
1313### Example 1: Simple GET Request
1414
15−The simplest possible client - just fetch a user by ID:
16−
1715```purescript
1816import Yoga.Fetch.Om (GET, Route, Path, type (/), type (:), deriveClient)
1917
@@ -32,146 +30,7 @@ main = do
3230 log user.name -- Type-safe! Compiler knows user is a User
3331```
3432
35−**Key concepts:**
36−- `Path ("users" / "id" : Int)` defines URL structure with path parameter
37−- `{ id: 42 }` becomes `/users/42`
38−- Response is automatically parsed to `User`
39−
40−### Example 2: Handling Multiple Response Types
41−
42−Real APIs return different responses for success vs errors:
43−
44−```purescript
45−type UserAPI =
46− { getUser ::
47− Route GET
48− (Path ("users" / "id" : Int))
49− {}
50− ( ok :: { body :: User }
51− , notFound :: { body :: ErrorMessage }
52− )
53− }
54−
55−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
56−
57−main = do
58− user <- api.getUser { id: 42 }
59− # handleErrors
60− { notFound: \err -> do
61− log $ "User not found: " <> err.error
62− pure defaultUser
63− }
64− log user.name
65−```
66−
67−**Key concepts:**
68−- Variant response types: `(ok :: ..., notFound :: ...)`
69−- `handleErrors` to convert error variants into values
70−- Type-safe error handling - can't forget a case!
71−
72−### Example 3: Query Parameters
73−
74−Add pagination with query strings:
75−
76−```purescript
77−type UserAPI =
78− { listUsers ::
79− Route GET
80− (Path "users" :? { limit :: Int, offset :: Int })
81− {}
82− ( ok :: { body :: Array User } )
83− }
84−
85−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
86−
87−main = do
88− users <- api.listUsers { limit: 10, offset: 20 }
89− -- Calls /users?limit=10&offset=20
90− log $ "Found " <> show (length users) <> " users"
91−```
92−
93−**Key concepts:**
94−- `:?` operator adds query parameters
95−- `{ limit: 10, offset: 20 }` becomes `?limit=10&offset=20`
96−- Optional parameters use `Maybe` types
97−
98−### Example 4: POST with JSON Body
99−
100−Creating resources with request bodies:
101−
102−```purescript
103−type UserAPI =
104− { createUser ::
105− Route POST
106− (Path "users")
107− { body :: JSON CreateUserRequest }
108− ( created :: { body :: User }
109− , badRequest :: { body :: ErrorMessage }
110− )
111− }
112−
113−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
114−
115−main = do
116− user <- api.createUser
117− { name: "Alice"
118− , email: "alice@example.com"
119− }
120− # handleErrors
121− { badRequest: \err -> do
122− log $ "Validation error: " <> err.error
123− throw err
124− }
125− log $ "Created user with ID: " <> show user.id
126−```
127−
128−**Key concepts:**
129−- `{ body :: JSON CreateUserRequest }` defines request body
130−- Automatic JSON serialization with `yoga-json`
131−- Multiple response variants for different statuses
132−
133−### Example 5: PUT/PATCH with Path Parameters + Body
134−
135−Updating resources combines path params and body:
136−
137−```purescript
138−type UserAPI =
139− { updateUser ::
140− Route PUT
141− (Path ("users" / "id" : Int))
142− { body :: JSON UpdateUserRequest }
143− ( ok :: { body :: User }
144− , notFound :: { body :: ErrorMessage }
145− , badRequest :: { body :: ErrorMessage }
146− )
147− }
148−
149−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
150−
151−main = do
152− user <- api.updateUser { id: 42 }
153− { name: "Alice Updated"
154− , email: "alice.new@example.com"
155− }
156− # handleErrors
157− { notFound: \_ -> do
158− log "User doesn't exist"
159− throw userNotFound
160− , badRequest: \err -> do
161− log $ "Invalid update: " <> err.error
162− throw validationError
163− }
164− log $ "Updated: " <> user.name
165−```
166−
167−**Key concepts:**
168−- Path params (`{ id: 42 }`) and body are separate parameters
169−- Multiple error variants can be handled independently
170−- Exhaustive pattern matching ensures all cases handled
171−
172−### Example 6: Complete CRUD API
173−
174−Putting it all together - a full CRUD API:
33+### Example 2: Complete CRUD API with Error Handling
17534
17635```purescript
17736type UserAPI =
@@ -203,79 +62,44 @@ type UserAPI =
20362
20463api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
20564
206−-- Use it anywhere in your app!
207−userWorkflow = do
208− -- Create
209− user <- api.createUser { name: "Alice", email: "alice@example.com" }
210−
211− -- Read
212− user <- api.getUser { id: user.id }
213−
214− -- Update
215− user <- api.updateUser { id: user.id } { name: "Alice Updated", email: user.email }
216−
217− -- Delete
218− _ <- api.deleteUser { id: user.id }
219−
220− log "Workflow complete!"
65+-- Create a user
66+user <- api.createUser { name: "Alice", email: "alice@example.com" }
67+ # handleErrors
68+ { badRequest: \err -> do
69+ log $ "Validation error: " <> err.error
70+ throw err
71+ }
72+
73+-- Update with path param + body
74+updated <- api.updateUser { id: user.id } { name: "Alice Updated", email: user.email }
75+ # handleErrors
76+ { notFound: \_ -> throw userNotFound
77+ , badRequest: \err -> throw validationError
78+ }
79+
80+-- Query with pagination
81+users <- api.listUsers { limit: 10, offset: 0 }
22182```
22283
223−**Key concepts:**
224−- Single source of truth for your API
225−- All CRUD operations type-safe and derived automatically
226−- Share route definitions between client and server (with `yoga-fastify-om`)
84+## More Examples
22785
228−### Example 7: Integration with Om Monad
86+See the test files for complete, runnable examples with all imports:
22987
230−Using with `yoga-om` for effects and dependency injection:
231−
232−```purescript
233−type AppContext =
234− { api :: UserAPI
235− , userId :: Int
236− }
237−
238−type AppErrors =
239− ( userNotFound :: Int
240− , unauthorized :: String
241− )
242−
243−getUserProfile :: Om AppContext AppErrors User
244−getUserProfile = do
245− { api, userId } <- ask
246− api.getUser { id: userId }
247− # handleErrors
248− { notFound: \_ -> throw { userNotFound: userId }
249− }
250−
251−updateProfile :: UpdateUserRequest -> Om AppContext AppErrors User
252−updateProfile updates = do
253− { api, userId } <- ask
254− api.updateUser { id: userId } updates
255− # handleErrors
256− { notFound: \_ -> throw { userNotFound: userId }
257− , badRequest: \err -> throw { unauthorized: err.error }
258− }
259−```
260−
261−**Key concepts:**
262−- API client lives in context (dependency injection)
263−- Error handling with typed variants
264−- Composable with other Om-based effects
265−
266−## Complete Example
267−
268−See [`test/Complete.Example.purs`](test/Complete.Example.purs) for a complete working example with all patterns.
88+- **[`test/Simple.Spec.purs`](test/Simple.Spec.purs)** - Basic GET/POST/PUT/PATCH/DELETE requests
89+- **[`test/Complete.Example.purs`](test/Complete.Example.purs)** - Full CRUD API with error handling
90+- **[`test/BuildUrl.Spec.purs`](test/BuildUrl.Spec.purs)** - Path parameters and query strings
91+- **[`test/Variant.Spec.purs`](test/Variant.Spec.purs)** - Response variant handling
92+- **[`test/SplitParams.Spec.purs`](test/SplitParams.Spec.purs)** - Parameter extraction patterns
26993
27094## Features
27195
27296### ✅ Type-Safe Everything
27397
274−- **Path parameters**: Compiler ensures `/users/:id` gets an `id`
275−- **Query parameters**: Type-safe query strings
98+- **Path parameters**: `/users/:id` requires `{ id: Int }`
99+- **Query parameters**: Type-safe query strings with `?`
276100- **Request bodies**: Automatic JSON serialization
277101- **Response bodies**: Automatic JSON parsing
278−- **Error handling**: Exhaustive pattern matching on response variants
102+- **Error handling**: Exhaustive pattern matching on variants
279103
280104### ✅ Single Source of Truth
281105
@@ -293,7 +117,7 @@ Changes to routes automatically update both client and server!
293117
294118### ✅ Automatic Derivation
295119
296−No manual client code needed:
120+No manual client code:
297121- ✅ URL building with path parameter substitution
298122- ✅ Query string construction
299123- ✅ JSON request serialization
@@ -303,33 +127,35 @@ No manual client code needed:
303127
304128### ✅ Integration with yoga-om
305129
306−Works seamlessly with the Om monad for effects and dependency injection.
130+Works seamlessly with the Om monad:
131+
132+```purescript
133+getUserProfile :: Om AppContext AppErrors User
134+getUserProfile = do
135+ { api, userId } <- ask
136+ api.getUser { id: userId }
137+ # handleErrors { notFound: \_ -> throw { userNotFound: userId } }
138+```
307139
308140## How It Works
309141
310142The library uses PureScript's type system to:
311143
3121441. **Extract parameters** from route definitions at compile time
313− - Path params from segments: `"users" / "id" : Int`
314− - Query params from `:?` operator: `:? { limit :: Int }`
315− - Body params from Request type: `{ body :: JSON User }`
145+ - Path params: `"users" / "id" : Int` → `{ id :: Int }`
146+ - Query params: `:? { limit :: Int }` → `{ limit :: Int }`
147+ - Body params: `{ body :: JSON User }` → request body
316148
317−2. **Build URLs** by:
318− - Getting the path pattern: `/users/:id`
319− - Substituting path params: `/users/42`
320− - Appending query params: `?limit=10&offset=20`
149+2. **Build URLs** automatically
150+ - Pattern: `/users/:id` + params: `{ id: 42 }` → `/users/42`
151+ - Query: `?limit=10&offset=20`
321152
322−3. **Make requests** using:
323− - `js-fetch` for the fetch API
324− - `js-promise-aff` to convert Promises → Aff
325− - `yoga-json` for JSON serialization
153+3. **Make requests** with `js-fetch` and `js-promise-aff`
326154
327−4. **Parse responses** by:
328− - Mapping status codes to variant labels (200 → "ok", 404 → "notFound")
329− - Parsing JSON response bodies
330− - Constructing typed Response values
155+4. **Parse responses** by mapping status codes to variant labels
156+ - `200` → `"ok"`, `404` → `"notFound"`, etc.
331157
332−All of this happens automatically based on your route types!
158+All automatically based on your route types!
333159
334160## API Reference
335161
@@ -342,39 +168,30 @@ deriveClient :: String -> Proxy (Record routes) -> Record clients
342168Derives a record of client functions from a record of routes.
343169
344170**Parameters:**
345−- `baseUrl` - The base URL of the API (e.g., `"https://api.example.com"`)
171+- `baseUrl` - Base URL (e.g., `"https://api.example.com"`)
346172- `routes` - Proxy of your API route definitions
347173
348−**Returns:** A record where each route becomes a function taking params and returning `Om context errors result`
174+**Returns:** Record where each route becomes a function returning `Om context errors result`
349175
350176## Roadmap
351177
352−### Current Version (0.1.0)
178+### Current (0.1.0)
353179- ✅ Core client derivation
354180- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
355181- ✅ Path, query, and body parameters
356182- ✅ JSON encoding/decoding
357183- ✅ Variant response handling
358−- ✅ CORS support
359184- ✅ Om monad integration
360185
361186### Future
362−- ⏳ Request/response headers support
187+- ⏳ Request/response headers
363188- ⏳ Authentication (Bearer tokens, API keys)
364−- ⏳ Request/response interceptors
365−- ⏳ Retry logic with exponential backoff
189+- ⏳ Interceptors
190+- ⏳ Retry logic
366191- ⏳ Timeout configuration
367−- ⏳ FormData and file upload support
368−- ⏳ Progress events
192+- ⏳ FormData/file uploads
369193- ⏳ Request cancellation
370194
371−## Contributing
372−
373−Contributions welcome! This library follows the patterns established by the yoga ecosystem:
374−- Type-level programming for automatic derivation
375−- Integration with yoga-json for serialization
376−- Consistent API design with yoga-fastify-om
377−
378195## License
379196
380197MIT
@@ -384,4 +201,4 @@ MIT
384201- [purescript-yoga-http-api](https://github.com/rowtype-yoga/purescript-yoga-http-api) - HTTP API type definitions
385202- [purescript-yoga-fastify-om](https://github.com/rowtype-yoga/purescript-yoga-fastify-om) - Server-side counterpart
386203- [purescript-yoga-json](https://github.com/rowtype-yoga/purescript-yoga-json) - JSON serialization
387−- [purescript-yoga-om](https://github.com/rowtype-yoga/purescript-yoga-om) - Om monad for effects and DI
204+- [purescript-yoga-om](https://github.com/rowtype-yoga/purescript-yoga-om) - Om monad