git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
Add VTA support and fix API parameter ordering
9ef3e99Mark
README.md+31−37
@@ -10,57 +10,54 @@ spago install yoga-fetch-om
10
10
11## Quick Start
11## Quick Start
12
12
13−### Example 1: Simple GET Request
13+### Example 1: Simple GET and POST Requests
14
14
15```purescript
15```purescript
16−import Yoga.Fetch.Om (GET, Route, Path, type (/), type (:), deriveClient)
16+import Yoga.Fetch.Om.Simple (get, post)
17
17
18−type SimpleAPI =
18+type User = { id :: Int, name :: String, email :: String }
19− { getUser ::
20− Route GET
21− (Path ("users" / "id" : Int))
22− {}
23− ( ok :: { body :: User } )
24− }
25−
26−api = deriveClient "https://api.example.com" (Proxy :: _ SimpleAPI)
27
19
28main = do
20main = do
29− user <- api.getUser { id: 42 }
21+ -- Simple GET request
30− log user.name -- Type-safe! Compiler knows user is a User
22+ user <- get @User "https://api.example.com/users/42" {}
23+ log user.name
24+
25+ -- Simple POST request with JSON body
26+ newUser <- post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
27+ log $ "Created user with ID: " <> show newUser.id
31```
28```
32
29
33−### Example 2: Complete CRUD API with Error Handling
30+### Example 2: Type-Safe API Client with Full CRUD
34
31
35```purescript
32```purescript
36type UserAPI =
33type UserAPI =
37 { getUser ::
34 { getUser ::
38− Route GET (Path ("users" / "id" : Int)) {}
35+ Route GET ("users" / "id" : Int) {}
39 ( ok :: { body :: User }
36 ( ok :: { body :: User }
40 , notFound :: { body :: ErrorMessage }
37 , notFound :: { body :: ErrorMessage }
41 )
38 )
42 , listUsers ::
39 , listUsers ::
43− Route GET (Path "users" :? { limit :: Int, offset :: Int }) {}
40+ Route GET ("users" :? { limit :: Int, offset :: Int }) {}
44 ( ok :: { body :: Array User } )
41 ( ok :: { body :: Array User } )
45 , createUser ::
42 , createUser ::
46− Route POST (Path "users") { body :: JSON CreateUserRequest }
43+ Route POST "users" { body :: JSON CreateUserRequest }
47 ( created :: { body :: User }
44 ( created :: { body :: User }
48 , badRequest :: { body :: ErrorMessage }
45 , badRequest :: { body :: ErrorMessage }
49 )
46 )
50 , updateUser ::
47 , updateUser ::
51− Route PUT (Path ("users" / "id" : Int)) { body :: JSON UpdateUserRequest }
48+ Route PUT ("users" / "id" : Int) { body :: JSON UpdateUserRequest }
52 ( ok :: { body :: User }
49 ( ok :: { body :: User }
53 , notFound :: { body :: ErrorMessage }
50 , notFound :: { body :: ErrorMessage }
54 , badRequest :: { body :: ErrorMessage }
51 , badRequest :: { body :: ErrorMessage }
55 )
52 )
56 , deleteUser ::
53 , deleteUser ::
57− Route DELETE (Path ("users" / "id" : Int)) {}
54+ Route DELETE ("users" / "id" : Int) {}
58 ( noContent :: { body :: {} }
55 ( noContent :: { body :: {} }
59 , notFound :: { body :: ErrorMessage }
56 , notFound :: { body :: ErrorMessage }
60 )
57 )
61 }
58 }
62
59
63−api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
60+api = client @UserAPI "https://api.example.com"
64
61
65-- Create a user
62-- Create a user
66user <- api.createUser { name: "Alice", email: "alice@example.com" }
63user <- api.createUser { name: "Alice", email: "alice@example.com" }
@@ -110,7 +107,7 @@ Define your API once, use it everywhere:
110server = buildServer apiRoutes handlers
107server = buildServer apiRoutes handlers
111
108
112-- Client (yoga-fetch-om)
109-- Client (yoga-fetch-om)
113−client = deriveClient baseUrl (Proxy :: _ apiRoutes)
110+apiClient = client @apiRoutes baseUrl
114```
111```
115
112
116Changes to routes automatically update both client and server!
113Changes to routes automatically update both client and server!
@@ -159,39 +156,36 @@ All automatically based on your route types!
159
156
160## API Reference
157## API Reference
161
158
162−### `deriveClient`
159+### `client`
163
160
164```purescript
161```purescript
165−deriveClient :: String -> Proxy (Record routes) -> Record clients
162+client :: forall @routes. String -> Record clients
166```
163```
167
164
168−Derives a record of client functions from a record of routes.
165+Derives a record of client functions from a record of routes using Visible Type Application.
169
166
170**Parameters:**
167**Parameters:**
168+- `@routes` - Your API route type (provided via VTA syntax)
171- `baseUrl` - Base URL (e.g., `"https://api.example.com"`)
169- `baseUrl` - Base URL (e.g., `"https://api.example.com"`)
172−- `routes` - Proxy of your API route definitions
173
170
174**Returns:** Record where each route becomes a function returning `Om context errors result`
171**Returns:** Record where each route becomes a function returning `Om context errors result`
175
172
176−## Roadmap
173+**Example:**
174+```purescript
175+api = client @UserAPI "https://api.example.com"
176+```
177+
178+## Features
177
179
178−### Current (0.1.0)
179−- ✅ Core client derivation
180+- ✅ Core client derivation with VTA syntax
180- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
181- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
181- ✅ Path, query, and body parameters
182- ✅ Path, query, and body parameters
183+- ✅ Request/response headers
182- ✅ JSON encoding/decoding
184- ✅ JSON encoding/decoding
185+- ✅ FormData support
183- ✅ Variant response handling
186- ✅ Variant response handling
184- ✅ Om monad integration
187- ✅ Om monad integration
185
188
186−### Future
187−- ⏳ Request/response headers
188−- ⏳ Authentication (Bearer tokens, API keys)
189−- ⏳ Interceptors
190−- ⏳ Retry logic
191−- ⏳ Timeout configuration
192−- ⏳ FormData/file uploads
193−- ⏳ Request cancellation
194−
195## License
189## License
196
190
197MIT
191MIT
package.json+2−2
@@ -1,11 +1,11 @@
1{
1{
2 "name": "purescript-yoga-fetch-om",
2 "name": "purescript-yoga-fetch-om",
3− "version": "0.1.0",
3+ "version": "0.2.0",
4 "description": "Derive type-safe fetch clients from yoga HTTP API definitions",
4 "description": "Derive type-safe fetch clients from yoga HTTP API definitions",
5 "type": "module",
5 "type": "module",
6 "scripts": {
6 "scripts": {
7 "build": "spago build",
7 "build": "spago build",
8− "test": "spago test",
8+ "test": "spago build && bunx vitest run",
9 "clean": "rm -rf output .spago",
9 "clean": "rm -rf output .spago",
10 "watch": "spago build --watch",
10 "watch": "spago build --watch",
11 "repl": "spago repl",
11 "repl": "spago repl",
spago.yaml+1−2
@@ -3,7 +3,7 @@ workspace: {}
3package:
3package:
4 name: yoga-fetch-om
4 name: yoga-fetch-om
5 publish:
5 publish:
6− version: 0.1.0
6+ version: 0.2.0
7 license: MIT
7 license: MIT
8 location:
8 location:
9 githubOwner: rowtype-yoga
9 githubOwner: rowtype-yoga
@@ -21,7 +21,6 @@ package:
21 - exceptions: ">=6.1.0 <7.0.0"
21 - exceptions: ">=6.1.0 <7.0.0"
22 - http-methods: ">=6.0.0 <7.0.0"
22 - http-methods: ">=6.0.0 <7.0.0"
23 - js-fetch: ">=0.2.1 <0.3.0"
23 - js-fetch: ">=0.2.1 <0.3.0"
24− - js-promise-aff: ">=1.0.0 <2.0.0"
25 - maybe: ">=6.0.0 <7.0.0"
24 - maybe: ">=6.0.0 <7.0.0"
26 - prelude: ">=6.0.2 <7.0.0"
25 - prelude: ">=6.0.2 <7.0.0"
27 - record: ">=4.0.0 <5.0.0"
26 - record: ">=4.0.0 <5.0.0"
src/Yoga/Fetch/Om.purs+22−6
@@ -1,11 +1,13 @@
1module Yoga.Fetch.Om
1module Yoga.Fetch.Om
2+ ( client
2− ( deriveClient
3+ , deriveClient
3 , class DeriveClient
4 , class DeriveClient
4 , deriveClientImpl
5 , deriveClientImpl
5 , class DeriveClientRL
6 , class DeriveClientRL
6 , deriveClientRL
7 , deriveClientRL
7 , class DeriveClientFn
8 , class DeriveClientFn
8 , deriveClientFn
9 , deriveClientFn
10+ , class RecordRow
9 , module Yoga.HTTP.API.Route
11 , module Yoga.HTTP.API.Route
10 , module Yoga.HTTP.API.Path
12 , module Yoga.HTTP.API.Path
11 , module Yoga.Fetch.Om.Simple
13 , module Yoga.Fetch.Om.Simple
@@ -34,6 +36,12 @@ import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, Response(.
34import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
36import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
35import Yoga.Om (Om, fromAff)
37import Yoga.Om (Om, fromAff)
36
38
39+-- | Extract row type from Record type
40+class RecordRow :: Type -> Row Type -> Constraint
41+class RecordRow t r | t -> r
42+
43+instance RecordRow (Record r) r
44+
37class DeriveClientFn :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
45class DeriveClientFn :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
38class
46class
39 DeriveClientFn method segments request response errorRow successRow result fn
47 DeriveClientFn method segments request response errorRow successRow result fn
@@ -78,14 +86,22 @@ class DeriveClient :: Row Type -> Row Type -> Constraint
78class DeriveClient routesRow clientsRow | routesRow -> clientsRow where
86class DeriveClient routesRow clientsRow | routesRow -> clientsRow where
79 deriveClientImpl :: String -> Proxy (Record routesRow) -> Record clientsRow
87 deriveClientImpl :: String -> Proxy (Record routesRow) -> Record clientsRow
80
88
81−-- | Derive API client functions from route definitions
89+-- | Derive API client functions from route definitions using VTA
90+-- |
91+-- | ```purescript
92+-- | type UserAPI = { getUser :: Route ... }
93+-- | api = client @UserAPI "https://api.example.com"
94+-- | ```
95+client :: forall @routes routesRow clientsRow. RecordRow routes routesRow => DeriveClient routesRow clientsRow => String -> Record clientsRow
96+client baseUrl = deriveClientImpl baseUrl (Proxy :: _ { | routesRow })
97+
98+-- | Deprecated: Use `client` with VTA instead
82-- |
99-- |
83−-- | Use a Proxy with a wildcard to avoid repetition:
84-- | ```purescript
100-- | ```purescript
85−-- | api = deriveClient "https://api.example.com" (Proxy :: _ UserAPI)
101+-- | api = deriveClient @UserAPI "https://api.example.com"
86-- | ```
102-- | ```
87−deriveClient :: forall routesRow clientsRow. DeriveClient routesRow clientsRow => String -> Proxy (Record routesRow) -> Record clientsRow
103+deriveClient :: forall @routesRow clientsRow. DeriveClient routesRow clientsRow => String -> Record clientsRow
88−deriveClient = deriveClientImpl
104+deriveClient baseUrl = deriveClientImpl baseUrl (Proxy :: _ { | routesRow })
89
105
90instance
106instance
91 ( RowToList routesRow rl
107 ( RowToList routesRow rl
src/Yoga/Fetch/Om/BuildUrl.purs+14−6
@@ -3,8 +3,10 @@ module Yoga.Fetch.Om.BuildUrl
3 , buildUrl
3 , buildUrl
4 , class SubstitutePathParams
4 , class SubstitutePathParams
5 , substitutePathParams
5 , substitutePathParams
6+ , substitutePathParamsImpl
6 , class AppendQueryParams
7 , class AppendQueryParams
7 , appendQueryParams
8 , appendQueryParams
9+ , appendQueryParamsImpl
8 , class AppendQueryParamsRL
10 , class AppendQueryParamsRL
9 , appendQueryParamsRL
11 , appendQueryParamsRL
10 , class SubstitutePathParamsRL
12 , class SubstitutePathParamsRL
@@ -43,19 +45,22 @@ instance
43 withBase = case baseUrl of
45 withBase = case baseUrl of
44 "" -> pattern
46 "" -> pattern
45 base -> base <> pattern
47 base -> base <> pattern
46− withPathParams = substitutePathParams (Proxy :: _ pathParams) pathParamsRec withBase
48+ withPathParams = substitutePathParamsImpl (Proxy :: _ pathParams) pathParamsRec withBase
47− withQueryParams = appendQueryParams (Proxy :: _ queryParams) queryParamsRec withPathParams
49+ withQueryParams = appendQueryParamsImpl (Proxy :: _ queryParams) queryParamsRec withPathParams
48
50
49class SubstitutePathParams :: Row Type -> Constraint
51class SubstitutePathParams :: Row Type -> Constraint
50class SubstitutePathParams params where
52class SubstitutePathParams params where
51− substitutePathParams :: Proxy params -> Record params -> String -> String
53+ substitutePathParamsImpl :: Proxy params -> Record params -> String -> String
54+
55+substitutePathParams :: forall @params. SubstitutePathParams params => Record params -> String -> String
56+substitutePathParams = substitutePathParamsImpl (Proxy :: _ params)
52
57
53instance
58instance
54 ( RowToList params rl
59 ( RowToList params rl
55 , SubstitutePathParamsRL rl params
60 , SubstitutePathParamsRL rl params
56 ) =>
61 ) =>
57 SubstitutePathParams params where
62 SubstitutePathParams params where
58− substitutePathParams _ = substitutePathParamsRL (Proxy :: _ rl)
63+ substitutePathParamsImpl _ = substitutePathParamsRL (Proxy :: _ rl)
59
64
60class SubstitutePathParamsRL :: RowList Type -> Row Type -> Constraint
65class SubstitutePathParamsRL :: RowList Type -> Row Type -> Constraint
61class SubstitutePathParamsRL rl r where
66class SubstitutePathParamsRL rl r where
@@ -79,14 +84,17 @@ instance
79
84
80class AppendQueryParams :: Row Type -> Constraint
85class AppendQueryParams :: Row Type -> Constraint
81class AppendQueryParams params where
86class AppendQueryParams params where
82− appendQueryParams :: Proxy params -> Record params -> String -> String
87+ appendQueryParamsImpl :: Proxy params -> Record params -> String -> String
88+
89+appendQueryParams :: forall @params. AppendQueryParams params => Record params -> String -> String
90+appendQueryParams = appendQueryParamsImpl (Proxy :: _ params)
83
91
84instance
92instance
85 ( RowToList params rl
93 ( RowToList params rl
86 , AppendQueryParamsRL rl params
94 , AppendQueryParamsRL rl params
87 ) =>
95 ) =>
88 AppendQueryParams params where
96 AppendQueryParams params where
89− appendQueryParams _ rec url = url <> queryString
97+ appendQueryParamsImpl _ rec url = url <> queryString
90 where
98 where
91 pairs = appendQueryParamsRL (Proxy :: _ rl) rec []
99 pairs = appendQueryParamsRL (Proxy :: _ rl) rec []
92 queryString = case pairs of
100 queryString = case pairs of
src/Yoga/Fetch/Om/Simple.purs+42−42
@@ -109,138 +109,138 @@ get
109 :: forall @a result headers ctx err
109 :: forall @a result headers ctx err
110 . DecodeResponse a result
110 . DecodeResponse a result
111 => Homogeneous headers String
111 => Homogeneous headers String
112+ => String
112− => Record headers
113+ -> Record headers
113− -> String
114 -> Om ctx (fetchError :: FetchError | err) result
114 -> Om ctx (fetchError :: FetchError | err) result
115−get headers url = _.body <$> simpleFetch @a GET headers url Nothing
115+get url headers = _.body <$> simpleFetch @a GET headers url Nothing
116
116
117getWithHeaders
117getWithHeaders
118 :: forall @a result headers ctx err
118 :: forall @a result headers ctx err
119 . DecodeResponse a result
119 . DecodeResponse a result
120 => Homogeneous headers String
120 => Homogeneous headers String
121+ => String
121− => Record headers
122+ -> Record headers
122− -> String
123 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
123 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
124−getWithHeaders headers url = simpleFetch @a GET headers url Nothing
124+getWithHeaders url headers = simpleFetch @a GET headers url Nothing
125
125
126delete
126delete
127 :: forall @a result headers ctx err
127 :: forall @a result headers ctx err
128 . DecodeResponse a result
128 . DecodeResponse a result
129 => Homogeneous headers String
129 => Homogeneous headers String
130+ => String
130− => Record headers
131+ -> Record headers
131− -> String
132 -> Om ctx (fetchError :: FetchError | err) result
132 -> Om ctx (fetchError :: FetchError | err) result
133−delete headers url = _.body <$> simpleFetch @a DELETE headers url Nothing
133+delete url headers = _.body <$> simpleFetch @a DELETE headers url Nothing
134
134
135deleteWithHeaders
135deleteWithHeaders
136 :: forall @a result headers ctx err
136 :: forall @a result headers ctx err
137 . DecodeResponse a result
137 . DecodeResponse a result
138 => Homogeneous headers String
138 => Homogeneous headers String
139+ => String
139− => Record headers
140+ -> Record headers
140− -> String
141 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
141 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
142−deleteWithHeaders headers url = simpleFetch @a DELETE headers url Nothing
142+deleteWithHeaders url headers = simpleFetch @a DELETE headers url Nothing
143
143
144delete_
144delete_
145 :: forall headers ctx err
145 :: forall headers ctx err
146 . Homogeneous headers String
146 . Homogeneous headers String
147+ => String
147− => Record headers
148+ -> Record headers
148− -> String
149 -> Om ctx (fetchError :: FetchError | err) Unit
149 -> Om ctx (fetchError :: FetchError | err) Unit
150−delete_ headers url = void $ simpleFetch @PlainTextResponse DELETE headers url Nothing
150+delete_ url headers = void $ simpleFetch @PlainTextResponse DELETE headers url Nothing
151
151
152post
152post
153 :: forall @a result headers body ctx err
153 :: forall @a result headers body ctx err
154 . DecodeResponse a result
154 . DecodeResponse a result
155 => Homogeneous headers String
155 => Homogeneous headers String
156 => WriteForeign body
156 => WriteForeign body
157+ => String
157− => Record headers
158+ -> Record headers
158− -> String
159 -> body
159 -> body
160 -> Om ctx (fetchError :: FetchError | err) result
160 -> Om ctx (fetchError :: FetchError | err) result
161−post headers url body = _.body <$> simpleFetch @a POST headers url (Just (writeJSON body))
161+post url headers body = _.body <$> simpleFetch @a POST headers url (Just (writeJSON body))
162
162
163postWithHeaders
163postWithHeaders
164 :: forall @a result headers body ctx err
164 :: forall @a result headers body ctx err
165 . DecodeResponse a result
165 . DecodeResponse a result
166 => Homogeneous headers String
166 => Homogeneous headers String
167 => WriteForeign body
167 => WriteForeign body
168+ => String
168− => Record headers
169+ -> Record headers
169− -> String
170 -> body
170 -> body
171 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
171 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
172−postWithHeaders headers url body = simpleFetch @a POST headers url (Just (writeJSON body))
172+postWithHeaders url headers body = simpleFetch @a POST headers url (Just (writeJSON body))
173
173
174post_
174post_
175 :: forall headers body ctx err
175 :: forall headers body ctx err
176 . Homogeneous headers String
176 . Homogeneous headers String
177 => WriteForeign body
177 => WriteForeign body
178+ => String
178− => Record headers
179+ -> Record headers
179− -> String
180 -> body
180 -> body
181 -> Om ctx (fetchError :: FetchError | err) Unit
181 -> Om ctx (fetchError :: FetchError | err) Unit
182−post_ headers url body = void $ simpleFetch @PlainTextResponse POST headers url (Just (writeJSON body))
182+post_ url headers body = void $ simpleFetch @PlainTextResponse POST headers url (Just (writeJSON body))
183
183
184put
184put
185 :: forall @a result headers body ctx err
185 :: forall @a result headers body ctx err
186 . DecodeResponse a result
186 . DecodeResponse a result
187 => Homogeneous headers String
187 => Homogeneous headers String
188 => WriteForeign body
188 => WriteForeign body
189+ => String
189− => Record headers
190+ -> Record headers
190− -> String
191 -> body
191 -> body
192 -> Om ctx (fetchError :: FetchError | err) result
192 -> Om ctx (fetchError :: FetchError | err) result
193−put headers url body = _.body <$> simpleFetch @a PUT headers url (Just (writeJSON body))
193+put url headers body = _.body <$> simpleFetch @a PUT headers url (Just (writeJSON body))
194
194
195putWithHeaders
195putWithHeaders
196 :: forall @a result headers body ctx err
196 :: forall @a result headers body ctx err
197 . DecodeResponse a result
197 . DecodeResponse a result
198 => Homogeneous headers String
198 => Homogeneous headers String
199 => WriteForeign body
199 => WriteForeign body
200+ => String
200− => Record headers
201+ -> Record headers
201− -> String
202 -> body
202 -> body
203 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
203 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
204−putWithHeaders headers url body = simpleFetch @a PUT headers url (Just (writeJSON body))
204+putWithHeaders url headers body = simpleFetch @a PUT headers url (Just (writeJSON body))
205
205
206put_
206put_
207 :: forall headers body ctx err
207 :: forall headers body ctx err
208 . Homogeneous headers String
208 . Homogeneous headers String
209 => WriteForeign body
209 => WriteForeign body
210+ => String
210− => Record headers
211+ -> Record headers
211− -> String
212 -> body
212 -> body
213 -> Om ctx (fetchError :: FetchError | err) Unit
213 -> Om ctx (fetchError :: FetchError | err) Unit
214−put_ headers url body = void $ simpleFetch @PlainTextResponse PUT headers url (Just (writeJSON body))
214+put_ url headers body = void $ simpleFetch @PlainTextResponse PUT headers url (Just (writeJSON body))
215
215
216patch
216patch
217 :: forall @a result headers body ctx err
217 :: forall @a result headers body ctx err
218 . DecodeResponse a result
218 . DecodeResponse a result
219 => Homogeneous headers String
219 => Homogeneous headers String
220 => WriteForeign body
220 => WriteForeign body
221+ => String
221− => Record headers
222+ -> Record headers
222− -> String
223 -> body
223 -> body
224 -> Om ctx (fetchError :: FetchError | err) result
224 -> Om ctx (fetchError :: FetchError | err) result
225−patch headers url body = _.body <$> simpleFetch @a PATCH headers url (Just (writeJSON body))
225+patch url headers body = _.body <$> simpleFetch @a PATCH headers url (Just (writeJSON body))
226
226
227patchWithHeaders
227patchWithHeaders
228 :: forall @a result headers body ctx err
228 :: forall @a result headers body ctx err
229 . DecodeResponse a result
229 . DecodeResponse a result
230 => Homogeneous headers String
230 => Homogeneous headers String
231 => WriteForeign body
231 => WriteForeign body
232+ => String
232− => Record headers
233+ -> Record headers
233− -> String
234 -> body
234 -> body
235 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
235 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
236−patchWithHeaders headers url body = simpleFetch @a PATCH headers url (Just (writeJSON body))
236+patchWithHeaders url headers body = simpleFetch @a PATCH headers url (Just (writeJSON body))
237
237
238patch_
238patch_
239 :: forall headers body ctx err
239 :: forall headers body ctx err
240 . Homogeneous headers String
240 . Homogeneous headers String
241 => WriteForeign body
241 => WriteForeign body
242+ => String
242− => Record headers
243+ -> Record headers
243− -> String
244 -> body
244 -> body
245 -> Om ctx (fetchError :: FetchError | err) Unit
245 -> Om ctx (fetchError :: FetchError | err) Unit
246−patch_ headers url body = void $ simpleFetch @PlainTextResponse PATCH headers url (Just (writeJSON body))
246+patch_ url headers body = void $ simpleFetch @PlainTextResponse PATCH headers url (Just (writeJSON body))
test/BuildUrl.Spec.purs+10−10
@@ -22,26 +22,26 @@ testSubstitutePathParams :: Effect ViTest
22testSubstitutePathParams = describe "substitutePathParams" $ do
22testSubstitutePathParams = describe "substitutePathParams" $ do
23 _ <- test "substitutes single path param" do
23 _ <- test "substitutes single path param" do
24 let
24 let
25− result = substitutePathParams (Proxy :: _ (id :: Int)) { id: 42 } "/users/:id"
25+ result = substitutePathParams @(id :: Int) { id: 42 } "/users/:id"
26 expectToEqual "/users/42" result
26 expectToEqual "/users/42" result
27
27
28 _ <- test "substitutes multiple path params" do
28 _ <- test "substitutes multiple path params" do
29 let
29 let
30− result = substitutePathParams (Proxy :: _ (userId :: Int, postId :: Int))
30+ result = substitutePathParams @(userId :: Int, postId :: Int)
31 { userId: 1, postId: 99 }
31 { userId: 1, postId: 99 }
32 "/users/:userId/posts/:postId"
32 "/users/:userId/posts/:postId"
33 expectToEqual "/users/1/posts/99" result
33 expectToEqual "/users/1/posts/99" result
34
34
35 _ <- test "handles String params" do
35 _ <- test "handles String params" do
36 let
36 let
37− result = substitutePathParams (Proxy :: _ (slug :: String))
37+ result = substitutePathParams @(slug :: String)
38 { slug: "hello-world" }
38 { slug: "hello-world" }
39 "/posts/:slug"
39 "/posts/:slug"
40 expectToEqual "/posts/hello-world" result
40 expectToEqual "/posts/hello-world" result
41
41
42 test "handles empty params" do
42 test "handles empty params" do
43 let
43 let
44− result = substitutePathParams (Proxy :: _ ()) {} "/users"
44+ result = substitutePathParams @() {} "/users"
45 expectToEqual "/users" result
45 expectToEqual "/users" result
46
46
47--------------------------------------------------------------------------------
47--------------------------------------------------------------------------------
@@ -52,40 +52,40 @@ testAppendQueryParams :: Effect ViTest
52testAppendQueryParams = describe "appendQueryParams" $ do
52testAppendQueryParams = describe "appendQueryParams" $ do
53 _ <- test "appends single query param" do
53 _ <- test "appends single query param" do
54 let
54 let
55− result = appendQueryParams (Proxy :: _ (limit :: Int)) { limit: 10 } "/users"
55+ result = appendQueryParams @(limit :: Int) { limit: 10 } "/users"
56 expectToEqual "/users?limit=10" result
56 expectToEqual "/users?limit=10" result
57
57
58 _ <- test "appends multiple query params" do
58 _ <- test "appends multiple query params" do
59 let
59 let
60− result = appendQueryParams (Proxy :: _ (limit :: Int, offset :: Int))
60+ result = appendQueryParams @(limit :: Int, offset :: Int)
61 { limit: 10, offset: 20 }
61 { limit: 10, offset: 20 }
62 "/users"
62 "/users"
63 expectToEqual "/users?limit=10&offset=20" result
63 expectToEqual "/users?limit=10&offset=20" result
64
64
65 _ <- test "handles Maybe params - Just" do
65 _ <- test "handles Maybe params - Just" do
66 let
66 let
67− result = appendQueryParams (Proxy :: _ (limit :: Maybe Int))
67+ result = appendQueryParams @(limit :: Maybe Int)
68 { limit: Just 10 }
68 { limit: Just 10 }
69 "/users"
69 "/users"
70 expectToEqual "/users?limit=10" result
70 expectToEqual "/users?limit=10" result
71
71
72 _ <- test "handles Maybe params - Nothing" do
72 _ <- test "handles Maybe params - Nothing" do
73 let
73 let
74− result = appendQueryParams (Proxy :: _ (limit :: Maybe Int))
74+ result = appendQueryParams @(limit :: Maybe Int)
75 { limit: Nothing }
75 { limit: Nothing }
76 "/users"
76 "/users"
77 expectToEqual "/users" result
77 expectToEqual "/users" result
78
78
79 _ <- test "handles mixed Maybe and required params" do
79 _ <- test "handles mixed Maybe and required params" do
80 let
80 let
81− result = appendQueryParams (Proxy :: _ (limit :: Maybe Int, offset :: Int))
81+ result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
82 { limit: Nothing, offset: 20 }
82 { limit: Nothing, offset: 20 }
83 "/users"
83 "/users"
84 expectToEqual "/users?offset=20" result
84 expectToEqual "/users?offset=20" result
85
85
86 test "handles empty query params" do
86 test "handles empty query params" do
87 let
87 let
88− result = appendQueryParams (Proxy :: _ ()) {} "/users"
88+ result = appendQueryParams @() {} "/users"
89 expectToEqual "/users" result
89 expectToEqual "/users" result
90
90
91--------------------------------------------------------------------------------
91--------------------------------------------------------------------------------
test/Complete.Example.purs+2−3
@@ -5,8 +5,7 @@ import Prelude
5import Data.Array (length)
5import Data.Array (length)
6import Data.Maybe (Maybe(..))
6import Data.Maybe (Maybe(..))
7import Effect.Class.Console (log)
7import Effect.Class.Console (log)
8−import Type.Proxy (Proxy(..))
9−import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, Path, type (/), type (:), type (:?), deriveClient)
8+import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, Path, type (/), type (:), type (:?), client)
10import Yoga.Om (Om, handleErrors)
9import Yoga.Om (Om, handleErrors)
11
10
12type User =
11type User =
@@ -74,7 +73,7 @@ api
74 , listUsers :: { limit :: Maybe Int, offset :: Maybe Int } -> Om {} () (Array User)
73 , listUsers :: { limit :: Maybe Int, offset :: Maybe Int } -> Om {} () (Array User)
75 , updateUser :: { id :: Int } -> UpdateUserRequest -> Om {} (badRequest :: ErrorMessage, notFound :: ErrorMessage) User
74 , updateUser :: { id :: Int } -> UpdateUserRequest -> Om {} (badRequest :: ErrorMessage, notFound :: ErrorMessage) User
76 }
75 }
77−api = deriveClient "https://api.example.com" (Proxy :: Proxy UserAPI)
76+api = client @UserAPI "https://api.example.com"
78
77
79exampleGetUser :: Om {} (notFound :: ErrorMessage) Unit
78exampleGetUser :: Om {} (notFound :: ErrorMessage) Unit
80exampleGetUser = do
79exampleGetUser = do
test/Simple.Spec.purs+16−16
@@ -10,59 +10,59 @@ type User = { id :: Int, name :: String, email :: String }
10type CreateUser = { name :: String, email :: String }
10type CreateUser = { name :: String, email :: String }
11
11
12exampleGet :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
12exampleGet :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
13−exampleGet = get @User {} "https://api.example.com/users/42"
13+exampleGet = get @User "https://api.example.com/users/42" {}
14
14
15exampleGetWithAuth :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
15exampleGetWithAuth :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
16−exampleGetWithAuth = get @User { authorization: "Bearer abc123" } "https://api.example.com/users/42"
16+exampleGetWithAuth = get @User "https://api.example.com/users/42" { authorization: "Bearer abc123" }
17
17
18exampleGetPlainText :: forall ctx err. Om ctx (fetchError :: FetchError | err) String
18exampleGetPlainText :: forall ctx err. Om ctx (fetchError :: FetchError | err) String
19−exampleGetPlainText = get @String {} "https://api.example.com/health"
19+exampleGetPlainText = get @String "https://api.example.com/health" {}
20
20
21exampleGetWithHeaders :: forall ctx err. Om ctx (fetchError :: FetchError | err) (FetchResponse User)
21exampleGetWithHeaders :: forall ctx err. Om ctx (fetchError :: FetchError | err) (FetchResponse User)
22exampleGetWithHeaders = do
22exampleGetWithHeaders = do
23− { headers, body: user } <- getWithHeaders @User {} "https://api.example.com/users/42"
23+ { headers, body: user } <- getWithHeaders @User "https://api.example.com/users/42" {}
24 log $ "Got user: " <> user.name
24 log $ "Got user: " <> user.name
25 pure { headers, body: user }
25 pure { headers, body: user }
26
26
27examplePost :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
27examplePost :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
28−examplePost = post @User {} "https://api.example.com/users" { name: "Alice", email: "alice@example.com" }
28+examplePost = post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
29
29
30examplePostFireAndForget :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
30examplePostFireAndForget :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
31−examplePostFireAndForget = post_ {} "https://api.example.com/users" { name: "Alice", email: "alice@example.com" }
31+examplePostFireAndForget = post_ "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
32
32
33examplePut :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
33examplePut :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
34−examplePut = put @User {} "https://api.example.com/users/42" { name: "Bob", email: "bob@example.com" }
34+examplePut = put @User "https://api.example.com/users/42" {} { name: "Bob", email: "bob@example.com" }
35
35
36examplePut_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
36examplePut_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
37−examplePut_ = put_ {} "https://api.example.com/users/42" { name: "Bob", email: "bob@example.com" }
37+examplePut_ = put_ "https://api.example.com/users/42" {} { name: "Bob", email: "bob@example.com" }
38
38
39examplePatch :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
39examplePatch :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
40−examplePatch = patch @User {} "https://api.example.com/users/42" { name: "Bob" }
40+examplePatch = patch @User "https://api.example.com/users/42" {} { name: "Bob" }
41
41
42examplePatch_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
42examplePatch_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
43−examplePatch_ = patch_ {} "https://api.example.com/users/42" { name: "Bob" }
43+examplePatch_ = patch_ "https://api.example.com/users/42" {} { name: "Bob" }
44
44
45exampleDelete :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
45exampleDelete :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
46−exampleDelete = delete @User {} "https://api.example.com/users/42"
46+exampleDelete = delete @User "https://api.example.com/users/42" {}
47
47
48exampleDelete_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
48exampleDelete_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
49−exampleDelete_ = delete_ {} "https://api.example.com/users/42"
49+exampleDelete_ = delete_ "https://api.example.com/users/42" {}
50
50
51exampleHandleError :: forall ctx. Om ctx () Unit
51exampleHandleError :: forall ctx. Om ctx () Unit
52exampleHandleError = do
52exampleHandleError = do
53− user <- get @User {} "https://api.example.com/users/42"
53+ user <- get @User "https://api.example.com/users/42" {}
54 # handleErrors { fetchError: \_ -> pure { id: 0, name: "unknown", email: "" } }
54 # handleErrors { fetchError: \_ -> pure { id: 0, name: "unknown", email: "" } }
55 log $ "Got user: " <> user.name
55 log $ "Got user: " <> user.name
56
56
57exampleComposition :: forall ctx. Om ctx () Unit
57exampleComposition :: forall ctx. Om ctx () Unit
58exampleComposition = do
58exampleComposition = do
59− user <- post @User {} "https://api.example.com/users" { name: "Alice", email: "alice@example.com" }
59+ user <- post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
60 # handleErrors
60 # handleErrors
61 { fetchError: \e -> do
61 { fetchError: \e -> do
62 log $ "Failed with status " <> show e.status <> ": " <> e.body
62 log $ "Failed with status " <> show e.status <> ": " <> e.body
63 pure { id: 0, name: "", email: "" }
63 pure { id: 0, name: "", email: "" }
64 }
64 }
65− _ <- put @User {} ("https://api.example.com/users/" <> show user.id) { name: "Alice Updated", email: user.email }
65+ _ <- put @User ("https://api.example.com/users/" <> show user.id) {} { name: "Alice Updated", email: user.email }
66 # handleErrors { fetchError: \_ -> pure user }
66 # handleErrors { fetchError: \_ -> pure user }
67− delete_ {} ("https://api.example.com/users/" <> show user.id)
67+ delete_ ("https://api.example.com/users/" <> show user.id) {}
68 # handleErrors { fetchError: \_ -> pure unit }
68 # handleErrors { fetchError: \_ -> pure unit }