git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
Simplify README to 2 examples, point to tests for complete code
8351e04Mark
README.md+57−240
@@ -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
9```
9```
10
10
11−## Progressive Examples
11+## Quick Start
12
12
13### Example 1: Simple GET Request
13### Example 1: Simple GET Request
14
14
15−The simplest possible client - just fetch a user by ID:
16−
17```purescript
15```purescript
18import Yoga.Fetch.Om (GET, Route, Path, type (/), type (:), deriveClient)
16import Yoga.Fetch.Om (GET, Route, Path, type (/), type (:), deriveClient)
19
17
@@ -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
33```
31```
34
32
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
33+### Example 2: Complete CRUD API with Error Handling
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:
175
34
176```purescript
35```purescript
177type UserAPI =
36type UserAPI =
@@ -203,79 +62,44 @@ type UserAPI =
203
62
204api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
63api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
205
64
206−-- Use it anywhere in your app!
207−userWorkflow = do
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" }
210−
67+ # handleErrors
211− -- Read
68+ { badRequest: \err -> do
212− user <- api.getUser { id: user.id }
69+ log $ "Validation error: " <> err.error
213−
70+ throw err
71+ }
72+
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 }
216−
75+ # handleErrors
217− -- Delete
76+ { notFound: \_ -> throw userNotFound
218− _ <- api.deleteUser { id: user.id }
77+ , badRequest: \err -> throw validationError
219−
78+ }
220− log "Workflow complete!"
79+
80+-- Query with pagination
81+users <- api.listUsers { limit: 10, offset: 0 }
221```
82```
222
83
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`)
227
85
228−### Example 7: Integration with Om Monad
86+See the test files for complete, runnable examples with all imports:
229
87
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
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.
269
93
270## Features
94## Features
271
95
272### ✅ Type-Safe Everything
96### ✅ Type-Safe Everything
273
97
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
279
103
280### ✅ Single Source of Truth
104### ✅ Single Source of Truth
281
105
@@ -293,7 +117,7 @@ Changes to routes automatically update both client and server!
293
117
294### ✅ Automatic Derivation
118### ✅ Automatic Derivation
295
119
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:
303
127
304### ✅ Integration with yoga-om
128### ✅ Integration with yoga-om
305
129
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+```
307
139
308## How It Works
140## How It Works
309
141
310The library uses PureScript's type system to:
142The library uses PureScript's type system to:
311
143
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
316
148
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`
321
152
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
326
154
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
331
157
332−All of this happens automatically based on your route types!
158+All automatically based on your route types!
333
159
334## API Reference
160## API Reference
335
161
@@ -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.
343
169
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
347
173
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`
349
175
350## Roadmap
176## Roadmap
351
177
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
358−- ✅ CORS support
359- ✅ Om monad integration
184- ✅ Om monad integration
360
185
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
368−- ⏳ Progress events
369- ⏳ Request cancellation
193- ⏳ Request cancellation
370
194
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−
378## License
195## License
379
196
380MIT
197MIT
@@ -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