@@ -8,12 +8,10 @@ Derive type-safe fetch clients from `purescript-yoga-http-api` route definitions
8spago install yoga-fetch-om
8spago install yoga-fetch-om
11−## Progressive Examples
11+## Quick Start
13### Example 1: Simple GET Request
13### Example 1: Simple GET Request
15−The simplest possible client - just fetch a user by ID:
17```purescript
15```purescript
18import Yoga.Fetch.Om (GET, Route, Path, type (/), type (:), deriveClient)
16import Yoga.Fetch.Om (GET, Route, Path, type (/), type (:), deriveClient)
@@ -32,146 +30,7 @@ main = do
32 log user.name -- Type-safe! Compiler knows user is a User
30 log user.name -- Type-safe! Compiler knows user is a User
36−- `Path ("users" / "id" : Int)` defines URL structure with path parameter
37−- `{ id: 42 }` becomes `/users/42`
38−- Response is automatically parsed to `User`
40−### Example 2: Handling Multiple Response Types
33+### Example 2: Complete CRUD API with Error Handling
42−Real APIs return different responses for success vs errors:
48− (Path ("users" / "id" : Int))
50− ( ok :: { body :: User }
51− , notFound :: { body :: ErrorMessage }
55−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
58− user <- api.getUser { id: 42 }
60− { notFound: \err -> do
61− log $ "User not found: " <> err.error
68−- Variant response types: `(ok :: ..., notFound :: ...)`
69−- `handleErrors` to convert error variants into values
70−- Type-safe error handling - can't forget a case!
72−### Example 3: Query Parameters
74−Add pagination with query strings:
80− (Path "users" :? { limit :: Int, offset :: Int })
82− ( ok :: { body :: Array User } )
85−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
88− users <- api.listUsers { limit: 10, offset: 20 }
89− -- Calls /users?limit=10&offset=20
90− log $ "Found " <> show (length users) <> " users"
94−- `:?` operator adds query parameters
95−- `{ limit: 10, offset: 20 }` becomes `?limit=10&offset=20`
96−- Optional parameters use `Maybe` types
98−### Example 4: POST with JSON Body
100−Creating resources with request bodies:
107− { body :: JSON CreateUserRequest }
108− ( created :: { body :: User }
109− , badRequest :: { body :: ErrorMessage }
113−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
116− user <- api.createUser
118− , email: "alice@example.com"
121− { badRequest: \err -> do
122− log $ "Validation error: " <> err.error
125− log $ "Created user with ID: " <> show user.id
129−- `{ body :: JSON CreateUserRequest }` defines request body
130−- Automatic JSON serialization with `yoga-json`
131−- Multiple response variants for different statuses
133−### Example 5: PUT/PATCH with Path Parameters + Body
135−Updating resources combines path params and body:
141− (Path ("users" / "id" : Int))
142− { body :: JSON UpdateUserRequest }
143− ( ok :: { body :: User }
144− , notFound :: { body :: ErrorMessage }
145− , badRequest :: { body :: ErrorMessage }
149−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
152− user <- api.updateUser { id: 42 }
153− { name: "Alice Updated"
154− , email: "alice.new@example.com"
157− { notFound: \_ -> do
158− log "User doesn't exist"
160− , badRequest: \err -> do
161− log $ "Invalid update: " <> err.error
162− throw validationError
164− log $ "Updated: " <> user.name
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
172−### Example 6: Complete CRUD API
174−Putting it all together - a full CRUD API:
176```purescript
35```purescript
177type UserAPI =
36type UserAPI =
@@ -203,79 +62,44 @@ type UserAPI =
204api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
63api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
206−-- Use it anywhere in your app!
208− -- Create
65+-- Create a user
209− user <- api.createUser { name: "Alice", email: "alice@example.com" }
66+user <- api.createUser { name: "Alice", email: "alice@example.com" }
211− -- Read
68+ { badRequest: \err -> do
212− user <- api.getUser { id: user.id }
69+ log $ "Validation error: " <> err.error
214− -- Update
73+-- Update with path param + body
215− user <- api.updateUser { id: user.id } { name: "Alice Updated", email: user.email }
74+updated <- api.updateUser { id: user.id } { name: "Alice Updated", email: user.email }
217− -- Delete
76+ { notFound: \_ -> throw userNotFound
218− _ <- api.deleteUser { id: user.id }
77+ , badRequest: \err -> throw validationError
220− log "Workflow complete!"
79+
80+-- Query with pagination
81+users <- api.listUsers { limit: 10, offset: 0 }
223−**Key concepts:**
84+## More Examples
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`)
228−### Example 7: Integration with Om Monad
86+See the test files for complete, runnable examples with all imports:
230−Using with `yoga-om` for effects and dependency injection:
88+- **[`test/Simple.Spec.purs`](test/Simple.Spec.purs)** - Basic GET/POST/PUT/PATCH/DELETE requests
231−
89+- **[`test/Complete.Example.purs`](test/Complete.Example.purs)** - Full CRUD API with error handling
232−```purescript
90+- **[`test/BuildUrl.Spec.purs`](test/BuildUrl.Spec.purs)** - Path parameters and query strings
233−type AppContext =
91+- **[`test/Variant.Spec.purs`](test/Variant.Spec.purs)** - Response variant handling
234− { api :: UserAPI
92+- **[`test/SplitParams.Spec.purs`](test/SplitParams.Spec.purs)** - Parameter extraction patterns
239− ( userNotFound :: Int
240− , unauthorized :: String
243−getUserProfile :: Om AppContext AppErrors User
245− { api, userId } <- ask
246− api.getUser { id: userId }
248− { notFound: \_ -> throw { userNotFound: userId }
251−updateProfile :: UpdateUserRequest -> Om AppContext AppErrors User
252−updateProfile updates = do
253− { api, userId } <- ask
254− api.updateUser { id: userId } updates
256− { notFound: \_ -> throw { userNotFound: userId }
257− , badRequest: \err -> throw { unauthorized: err.error }
262−- API client lives in context (dependency injection)
263−- Error handling with typed variants
264−- Composable with other Om-based effects
268−See [`test/Complete.Example.purs`](test/Complete.Example.purs) for a complete working example with all patterns.
270## Features
94## Features
272### ✅ Type-Safe Everything
96### ✅ Type-Safe Everything
274−- **Path parameters**: Compiler ensures `/users/:id` gets an `id`
98+- **Path parameters**: `/users/:id` requires `{ id: Int }`
275−- **Query parameters**: Type-safe query strings
99+- **Query parameters**: Type-safe query strings with `?`
276- **Request bodies**: Automatic JSON serialization
100- **Request bodies**: Automatic JSON serialization
277- **Response bodies**: Automatic JSON parsing
101- **Response bodies**: Automatic JSON parsing
278−- **Error handling**: Exhaustive pattern matching on response variants
102+- **Error handling**: Exhaustive pattern matching on variants
280### ✅ Single Source of Truth
104### ✅ Single Source of Truth
@@ -293,7 +117,7 @@ Changes to routes automatically update both client and server!
294### ✅ Automatic Derivation
118### ✅ Automatic Derivation
296−No manual client code needed:
120+No manual client code:
297- ✅ URL building with path parameter substitution
121- ✅ URL building with path parameter substitution
298- ✅ Query string construction
122- ✅ Query string construction
299- ✅ JSON request serialization
123- ✅ JSON request serialization
@@ -303,33 +127,35 @@ No manual client code needed:
304### ✅ Integration with yoga-om
128### ✅ Integration with yoga-om
306−Works seamlessly with the Om monad for effects and dependency injection.
130+Works seamlessly with the Om monad:
133+getUserProfile :: Om AppContext AppErrors User
135+ { api, userId } <- ask
136+ api.getUser { id: userId }
137+ # handleErrors { notFound: \_ -> throw { userNotFound: userId } }
308## How It Works
140## How It Works
310The library uses PureScript's type system to:
142The library uses PureScript's type system to:
3121. **Extract parameters** from route definitions at compile time
1441. **Extract parameters** from route definitions at compile time
313− - Path params from segments: `"users" / "id" : Int`
145+ - Path params: `"users" / "id" : Int` → `{ id :: Int }`
314− - Query params from `:?` operator: `:? { limit :: Int }`
146+ - Query params: `:? { limit :: Int }` → `{ limit :: Int }`
315− - Body params from Request type: `{ body :: JSON User }`
147+ - Body params: `{ body :: JSON User }` → request body
317−2. **Build URLs** by:
149+2. **Build URLs** automatically
318− - Getting the path pattern: `/users/:id`
319− - Substituting path params: `/users/42`
150+ - Pattern: `/users/:id` + params: `{ id: 42 }` → `/users/42`
320− - Appending query params: `?limit=10&offset=20`
151+ - Query: `?limit=10&offset=20`
322−3. **Make requests** using:
153+3. **Make requests** with `js-fetch` and `js-promise-aff`
323− - `js-fetch` for the fetch API
324− - `js-promise-aff` to convert Promises → Aff
325− - `yoga-json` for JSON serialization
327−4. **Parse responses** by:
328− - Mapping status codes to variant labels (200 → "ok", 404 → "notFound")
155+4. **Parse responses** by mapping status codes to variant labels
329− - Parsing JSON response bodies
156+ - `200` → `"ok"`, `404` → `"notFound"`, etc.
330− - Constructing typed Response values
332−All of this happens automatically based on your route types!
158+All automatically based on your route types!
334## API Reference
160## API Reference
@@ -342,39 +168,30 @@ deriveClient :: String -> Proxy (Record routes) -> Record clients
342Derives a record of client functions from a record of routes.
168Derives a record of client functions from a record of routes.
344**Parameters:**
170**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"`)
346- `routes` - Proxy of your API route definitions
172- `routes` - Proxy of your API route definitions
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`
350## Roadmap
176## Roadmap
352−### Current Version (0.1.0)
178+### Current (0.1.0)
353- ✅ Core client derivation
179- ✅ Core client derivation
354- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
180- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
355- ✅ Path, query, and body parameters
181- ✅ Path, query, and body parameters
356- ✅ JSON encoding/decoding
182- ✅ JSON encoding/decoding
357- ✅ Variant response handling
183- ✅ Variant response handling
359- ✅ Om monad integration
184- ✅ Om monad integration
361### Future
186### Future
362−- ⏳ Request/response headers support
187+- ⏳ Request/response headers
363- ⏳ Authentication (Bearer tokens, API keys)
188- ⏳ Authentication (Bearer tokens, API keys)
364−- ⏳ Request/response interceptors
189+- ⏳ Interceptors
365−- ⏳ Retry logic with exponential backoff
190+- ⏳ Retry logic
366- ⏳ Timeout configuration
191- ⏳ Timeout configuration
367−- ⏳ FormData and file upload support
192+- ⏳ FormData/file uploads
369- ⏳ Request cancellation
193- ⏳ Request cancellation
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
378## License
195## License
@@ -384,4 +201,4 @@ MIT
384- [purescript-yoga-http-api](https://github.com/rowtype-yoga/purescript-yoga-http-api) - HTTP API type definitions
201- [purescript-yoga-http-api](https://github.com/rowtype-yoga/purescript-yoga-http-api) - HTTP API type definitions
385- [purescript-yoga-fastify-om](https://github.com/rowtype-yoga/purescript-yoga-fastify-om) - Server-side counterpart
202- [purescript-yoga-fastify-om](https://github.com/rowtype-yoga/purescript-yoga-fastify-om) - Server-side counterpart
386- [purescript-yoga-json](https://github.com/rowtype-yoga/purescript-yoga-json) - JSON serialization
203- [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