git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
575f708Mark
spago.lock+183
@@ -71,6 +71,91 @@
71 {
71 {
72 "yoga-om-strom": ">=0.4.3 <0.5.0"
72 "yoga-om-strom": ">=0.4.3 <0.5.0"
73 }
73 }
74+ ],
75+ "build_plan": [
76+ "aff",
77+ "aff-retry",
78+ "ansi",
79+ "arraybuffer-types",
80+ "arrays",
81+ "avar",
82+ "bifunctors",
83+ "catenable-lists",
84+ "console",
85+ "const",
86+ "contravariant",
87+ "control",
88+ "datetime",
89+ "distributive",
90+ "effect",
91+ "either",
92+ "enums",
93+ "exceptions",
94+ "exists",
95+ "foldable-traversable",
96+ "foreign",
97+ "foreign-object",
98+ "fork",
99+ "free",
100+ "functions",
101+ "functors",
102+ "gen",
103+ "heterogeneous",
104+ "http-methods",
105+ "identity",
106+ "integers",
107+ "invariant",
108+ "js-bigints",
109+ "js-date",
110+ "js-fetch",
111+ "js-promise",
112+ "js-promise-aff",
113+ "lazy",
114+ "lists",
115+ "literals",
116+ "maybe",
117+ "media-types",
118+ "mmorph",
119+ "newtype",
120+ "nonempty",
121+ "now",
122+ "nullable",
123+ "numbers",
124+ "ordered-collections",
125+ "orders",
126+ "parallel",
127+ "partial",
128+ "pipes",
129+ "prelude",
130+ "profunctor",
131+ "random",
132+ "record",
133+ "record-studio",
134+ "refs",
135+ "safe-coerce",
136+ "spec",
137+ "st",
138+ "strings",
139+ "tailrec",
140+ "transformers",
141+ "tuples",
142+ "type-equality",
143+ "typelevel-prelude",
144+ "uncurried-transformers",
145+ "undefined-is-not-a-problem",
146+ "unfoldable",
147+ "unsafe-coerce",
148+ "untagged-union",
149+ "variant",
150+ "web-events",
151+ "web-file",
152+ "web-streams",
153+ "yoga-http-api",
154+ "yoga-json",
155+ "yoga-om",
156+ "yoga-om-strom",
157+ "yoga-options",
158+ "yoga-tree"
74 ]
159 ]
75 },
160 },
76 "test": {
161 "test": {
@@ -90,6 +175,104 @@
90 {
175 {
91 "yoga-fastify-om": ">=0.4.5 <0.5.0"
176 "yoga-fastify-om": ">=0.4.5 <0.5.0"
92 }
177 }
178+ ],
179+ "build_plan": [
180+ "aff",
181+ "aff-retry",
182+ "ansi",
183+ "argonaut-codecs",
184+ "argonaut-core",
185+ "arraybuffer-types",
186+ "arrays",
187+ "avar",
188+ "bifunctors",
189+ "catenable-lists",
190+ "console",
191+ "const",
192+ "contravariant",
193+ "control",
194+ "datetime",
195+ "distributive",
196+ "effect",
197+ "either",
198+ "enums",
199+ "exceptions",
200+ "exists",
201+ "exitcodes",
202+ "foldable-traversable",
203+ "foreign",
204+ "foreign-object",
205+ "fork",
206+ "free",
207+ "functions",
208+ "functors",
209+ "gen",
210+ "heterogeneous",
211+ "identity",
212+ "integers",
213+ "invariant",
214+ "js-bigints",
215+ "js-date",
216+ "js-promise",
217+ "js-promise-aff",
218+ "js-uri",
219+ "lazy",
220+ "lists",
221+ "literals",
222+ "maybe",
223+ "mmorph",
224+ "newtype",
225+ "node-buffer",
226+ "node-event-emitter",
227+ "node-fs",
228+ "node-path",
229+ "node-process",
230+ "node-streams",
231+ "nonempty",
232+ "now",
233+ "nullable",
234+ "numbers",
235+ "open-memoize",
236+ "optparse",
237+ "ordered-collections",
238+ "orders",
239+ "parallel",
240+ "partial",
241+ "pipes",
242+ "posix-types",
243+ "prelude",
244+ "profunctor",
245+ "random",
246+ "record",
247+ "record-studio",
248+ "refs",
249+ "routing-duplex",
250+ "safe-coerce",
251+ "spec",
252+ "spec-discovery",
253+ "spec-node",
254+ "st",
255+ "strings",
256+ "tailrec",
257+ "transformers",
258+ "tuples",
259+ "type-equality",
260+ "typelevel-prelude",
261+ "uncurried-transformers",
262+ "undefined-is-not-a-problem",
263+ "unfoldable",
264+ "unsafe-coerce",
265+ "untagged-union",
266+ "variant",
267+ "yoga-fastify",
268+ "yoga-fastify-om",
269+ "yoga-http-api",
270+ "yoga-json",
271+ "yoga-om",
272+ "yoga-om-layer",
273+ "yoga-om-strom",
274+ "yoga-options",
275+ "yoga-tree"
93 ]
276 ]
94 }
277 }
95 }
278 }
src/Yoga/Fetch/Om.purs+20−12
@@ -19,7 +19,8 @@ module Yoga.Fetch.Om
19
19
20import Prelude
20import Prelude
21
21
22−import Data.Symbol (class IsSymbol)
22+import Data.Symbol (class IsSymbol, reflectSymbol)
23+import Data.Tuple.Nested ((/\))
23import Data.Variant (Variant)
24import Data.Variant (Variant)
24import Prim.Row as Row
25import Prim.Row as Row
25import Prim.RowList (class RowToList, RowList)
26import Prim.RowList (class RowToList, RowList)
@@ -29,7 +30,6 @@ import Type.Proxy (Proxy(..))
29import Unsafe.Coerce (unsafeCoerce)
30import Unsafe.Coerce (unsafeCoerce)
30import JS.Fetch.Headers (Headers)
31import JS.Fetch.Headers (Headers)
31import JS.Fetch.Headers as Headers
32import JS.Fetch.Headers as Headers
32−import Type.Row.Homogeneous (class Homogeneous)
33import Yoga.Fetch.Om.BuildUrl (class BuildUrl, buildUrl)
33import Yoga.Fetch.Om.BuildUrl (class BuildUrl, buildUrl)
34import Yoga.Fetch.Om.Simple (class DecodeResponse, decodeResponse, FetchError, FetchResponse, get, getWithHeaders, delete, deleteWithHeaders, delete_, post, postWithHeaders, post_, put, putWithHeaders, put_, patch, patchWithHeaders, patch_)
34import Yoga.Fetch.Om.Simple (class DecodeResponse, decodeResponse, FetchError, FetchResponse, get, getWithHeaders, delete, deleteWithHeaders, delete_, post, postWithHeaders, post_, put, putWithHeaders, put_, patch, patchWithHeaders, patch_)
35import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
35import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
@@ -40,7 +40,7 @@ import Yoga.Fetch.Om.ParseResponse (class ParseResponse, parseResponse)
40import Yoga.Fetch.Om.SplitResponses (class SplitResponses)
40import Yoga.Fetch.Om.SplitResponses (class SplitResponses)
41import Yoga.Fetch.Om.Variant (class VariantOrValue, variantOrValue)
41import Yoga.Fetch.Om.Variant (class VariantOrValue, variantOrValue)
42import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParams, Required, type (/), type (:), type (:?), class PathPattern)
42import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParams, Required, type (/), type (:), type (:?), class PathPattern)
43−import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, Response(..), JSON, FormData, PlainText, Streaming, NoBody)
43+import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, Response(..), JSON, FormData, PlainText, Streaming, NoBody, class HeaderValue, printHeader)
44import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
44import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
45import Yoga.Om (class ToOm, Om, toOm)
45import Yoga.Om (class ToOm, Om, toOm)
46
46
@@ -57,8 +57,19 @@ class ToHeaders headersRL headers where
57instance ToHeaders RL.Nil headers where
57instance ToHeaders RL.Nil headers where
58 toHeaders _ _ = Headers.empty
58 toHeaders _ _ = Headers.empty
59
59
60+else instance
61+ ( IsSymbol name
62+ , HeaderValue ty
63+ , Row.Cons name ty tailRow headers
64+ , Row.Lacks name tailRow
65+ , ToHeaders tail tailRow
66+ ) =>
60−else instance Homogeneous headers String => ToHeaders headersRL headers where
67+ ToHeaders (RL.Cons name ty tail) headers where
61− toHeaders _ = Headers.fromRecord
68+ toHeaders _ headers = Headers.fromFoldable ([ headerName /\ headerValue ] <> Headers.toArray rest)
69+ where
70+ headerName = reflectSymbol (Proxy :: Proxy name)
71+ headerValue = printHeader (Record.get (Proxy :: Proxy name) headers)
72+ rest = toHeaders (Proxy :: Proxy tail) (unsafeCoerce headers :: Record tailRow)
62
73
63class DeriveClientFn :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
74class DeriveClientFn :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
64class
75class
@@ -114,23 +125,20 @@ class DeriveClient routesRow clientsRow | routesRow -> clientsRow where
114-- | type UserAPI = { getUser :: Route ... }
125-- | type UserAPI = { getUser :: Route ... }
115-- | api = client @UserAPI "https://api.example.com"
126-- | api = client @UserAPI "https://api.example.com"
116-- | ```
127-- | ```
117−client :: forall @routes routesRow clientsRow polyClientsRow. RecordRow routes routesRow => DeriveClient routesRow clientsRow => String -> Record polyClientsRow
128+client :: forall @routes routesRow clientsRow. RecordRow routes routesRow => DeriveClient routesRow clientsRow => String -> Record clientsRow
118−client baseUrl = polymorphic (deriveClientImpl baseUrl (Proxy :: _ { | routesRow }) :: Record clientsRow)
129+client baseUrl = deriveClientImpl baseUrl (Proxy :: _ { | routesRow })
119
130
120-- | Deprecated: Use `client` with VTA instead
131-- | Deprecated: Use `client` with VTA instead
121-- |
132-- |
122-- | ```purescript
133-- | ```purescript
123-- | api = deriveClient @UserAPI "https://api.example.com"
134-- | api = deriveClient @UserAPI "https://api.example.com"
124-- | ```
135-- | ```
125−deriveClient :: forall @routesRow clientsRow polyClientsRow. DeriveClient routesRow clientsRow => String -> Record polyClientsRow
136+deriveClient :: forall @routesRow clientsRow. DeriveClient routesRow clientsRow => String -> Record clientsRow
126−deriveClient baseUrl = polymorphic (deriveClientImpl baseUrl (Proxy :: _ { | routesRow }) :: Record clientsRow)
137+deriveClient baseUrl = deriveClientImpl baseUrl (Proxy :: _ { | routesRow })
127
138
128plainText :: PlainText -> String
139plainText :: PlainText -> String
129plainText = unsafeCoerce
140plainText = unsafeCoerce
130
141
131−polymorphic :: forall a b. a -> b
132−polymorphic = unsafeCoerce
133−
134instance
142instance
135 ( RowToList routesRow rl
143 ( RowToList routesRow rl
136 , DeriveClientRL rl () clientsRow
144 , DeriveClientRL rl () clientsRow
src/Yoga/Fetch/Om/Simple.purs−6
@@ -102,13 +102,7 @@ simpleFetch method headers url maybeBody = do
102 { method
102 { method
103 , headers: allHeaders
103 , headers: allHeaders
104 , body: reqBody
104 , body: reqBody
105− , credentials: Credentials.SameOrigin
106− , mode: Mode.Cors
107− , referrer: Referrer.ReferrerNone
108− , referrerPolicy: ReferrerPolicy.NoReferrer
109− , integrity: Integrity ""
110 , duplex: Duplex.Half
105 , duplex: Duplex.Half
111− , cache: Cache.Default
112 }
106 }
113
107
114get
108get
test/Complete.Example.purs+11−16
@@ -6,7 +6,8 @@ import Data.Array (length)
6import Data.Maybe (Maybe(..))
6import Data.Maybe (Maybe(..))
7import Effect.Class.Console (log)
7import Effect.Class.Console (log)
8import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, Path, type (/), type (:), type (:?), client)
8import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, Path, type (/), type (:), type (:?), client)
9+import Yoga.HTTP.API.Route (BearerToken(..))
9−import Yoga.Om (Om, handleErrors)
10+import Yoga.Om (Om, expand, handleErrors)
10
11
11type User =
12type User =
12 { id :: Int
13 { id :: Int
@@ -67,32 +68,23 @@ type UserAPI =
67 , createUserAuth ::
68 , createUserAuth ::
68 Route POST
69 Route POST
69 (Path "users")
70 (Path "users")
70− { headers :: Record (authorization :: String), body :: JSON CreateUserRequest }
71+ { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
71 ( created :: { body :: User }
72 ( created :: { body :: User }
72 , badRequest :: { body :: ErrorMessage }
73 , badRequest :: { body :: ErrorMessage }
73 )
74 )
74 }
75 }
75
76
76−-- Generated client with polymorphic type annotation
77−api
78− :: forall ctx err
79− . { createUser :: CreateUserRequest -> Om ctx (badRequest :: ErrorMessage | err) User
80− , createUserAuth :: { authorization :: String } -> CreateUserRequest -> Om ctx (badRequest :: ErrorMessage | err) User
81− , deleteUser :: { id :: Int } -> Om ctx (notFound :: ErrorMessage | err) {}
82− , getUser :: { id :: Int } -> Om ctx (notFound :: ErrorMessage | err) User
83− , listUsers :: { limit :: Maybe Int, offset :: Maybe Int } -> Om ctx err (Array User)
84− , updateUser :: { id :: Int } -> UpdateUserRequest -> Om ctx (badRequest :: ErrorMessage, notFound :: ErrorMessage | err) User
85− }
86api = client @UserAPI "https://api.example.com"
77api = client @UserAPI "https://api.example.com"
87
78
88exampleGetUser :: Om {} (notFound :: ErrorMessage) Unit
79exampleGetUser :: Om {} (notFound :: ErrorMessage) Unit
89exampleGetUser = do
80exampleGetUser = do
90− user <- api.getUser { id: 42 }
81+ user <- api.getUser { id: 42 } # expand
91 log $ "Found user: " <> user.name <> " (" <> user.email <> ")"
82 log $ "Found user: " <> user.name <> " (" <> user.email <> ")"
92
83
93exampleGetUserHandled :: Om {} () Unit
84exampleGetUserHandled :: Om {} () Unit
94exampleGetUserHandled = do
85exampleGetUserHandled = do
95 user <- api.getUser { id: 42 }
86 user <- api.getUser { id: 42 }
87+ # expand
96 # handleErrors
88 # handleErrors
97 { notFound: \err -> do
89 { notFound: \err -> do
98 log $ "User not found: " <> err.error
90 log $ "User not found: " <> err.error
@@ -102,7 +94,7 @@ exampleGetUserHandled = do
102
94
103exampleListUsers :: Om {} () Unit
95exampleListUsers :: Om {} () Unit
104exampleListUsers = do
96exampleListUsers = do
105− users <- api.listUsers { limit: Just 10, offset: Just 0 }
97+ users <- api.listUsers { limit: Just 10, offset: Just 0 } # expand
106 log $ "Found " <> show (length users) <> " users"
98 log $ "Found " <> show (length users) <> " users"
107
99
108exampleCreateUser :: Om {} (badRequest :: ErrorMessage) Unit
100exampleCreateUser :: Om {} (badRequest :: ErrorMessage) Unit
@@ -111,6 +103,7 @@ exampleCreateUser = do
111 { name: "Alice"
103 { name: "Alice"
112 , email: "alice@example.com"
104 , email: "alice@example.com"
113 }
105 }
106+ # expand
114 log $ "Created user with ID: " <> show user.id
107 log $ "Created user with ID: " <> show user.id
115
108
116exampleUpdateUser :: Om {} (notFound :: ErrorMessage, badRequest :: ErrorMessage) Unit
109exampleUpdateUser :: Om {} (notFound :: ErrorMessage, badRequest :: ErrorMessage) Unit
@@ -119,17 +112,19 @@ exampleUpdateUser = do
119 { name: "Alice Updated"
112 { name: "Alice Updated"
120 , email: "alice.new@example.com"
113 , email: "alice.new@example.com"
121 }
114 }
115+ # expand
122 log $ "Updated user: " <> user.name
116 log $ "Updated user: " <> user.name
123
117
124exampleDeleteUser :: Om {} (notFound :: ErrorMessage) Unit
118exampleDeleteUser :: Om {} (notFound :: ErrorMessage) Unit
125exampleDeleteUser = do
119exampleDeleteUser = do
126− _ <- api.deleteUser { id: 42 }
120+ _ <- api.deleteUser { id: 42 } # expand
127 log "User deleted successfully"
121 log "User deleted successfully"
128
122
129exampleCreateUserAuth :: Om {} (badRequest :: ErrorMessage) Unit
123exampleCreateUserAuth :: Om {} (badRequest :: ErrorMessage) Unit
130exampleCreateUserAuth = do
124exampleCreateUserAuth = do
131− user <- api.createUserAuth { authorization: "Bearer abc123" }
125+ user <- api.createUserAuth { authorization: BearerToken "abc123" }
132 { name: "Alice"
126 { name: "Alice"
133 , email: "alice@example.com"
127 , email: "alice@example.com"
134 }
128 }
129+ # expand
135 log $ "Created user with ID: " <> show user.id
130 log $ "Created user with ID: " <> show user.id
test/Example.purs+2−2
@@ -1,6 +1,6 @@
1module Example where
1module Example where
2
2
3−import Yoga.HTTP.API.Route (GET, POST, DELETE, Route)
3+import Yoga.HTTP.API.Route (GET, POST, DELETE, Route, BearerToken)
4import Yoga.HTTP.API.Route.Encoding (JSON)
4import Yoga.HTTP.API.Route.Encoding (JSON)
5import Yoga.HTTP.API.Path (Path, type (/), type (:), type (:?))
5import Yoga.HTTP.API.Path (Path, type (/), type (:), type (:?))
6
6
@@ -39,7 +39,7 @@ type UserAPI =
39 , createUserAuth ::
39 , createUserAuth ::
40 Route POST
40 Route POST
41 (Path "users")
41 (Path "users")
42− { headers :: Record (authorization :: String), body :: JSON CreateUserRequest }
42+ { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
43 ( created :: { body :: User }
43 ( created :: { body :: User }
44 , badRequest :: { body :: ErrorMessage }
44 , badRequest :: { body :: ErrorMessage }
45 )
45 )
test/MakeRequest.Spec.purs+5
@@ -11,6 +11,7 @@ import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
11import Type.Proxy (Proxy(..))
11import Type.Proxy (Proxy(..))
12import Yoga.Fetch.Om (toHeaders)
12import Yoga.Fetch.Om (toHeaders)
13import Yoga.Fetch.Om.MakeRequest (httpMethod)
13import Yoga.Fetch.Om.MakeRequest (httpMethod)
14+import Yoga.HTTP.API.Route (BearerToken(..))
14import Yoga.HTTP.API.Route.Method as Route
15import Yoga.HTTP.API.Route.Method as Route
15
16
16spec :: Spec Unit
17spec :: Spec Unit
@@ -37,3 +38,7 @@ spec = do
37 arr `shouldSatisfy` \a ->
38 arr `shouldSatisfy` \a ->
38 a == [ "authorization" /\ "Bearer abc123", "x-custom" /\ "value" ]
39 a == [ "authorization" /\ "Bearer abc123", "x-custom" /\ "value" ]
39 || a == [ "x-custom" /\ "value", "authorization" /\ "Bearer abc123" ]
40 || a == [ "x-custom" /\ "value", "authorization" /\ "Bearer abc123" ]
41+
42+ it "typed header values are rendered with HeaderValue" do
43+ let hdrs = toHeaders (Proxy :: _ (RL.Cons "authorization" BearerToken RL.Nil)) { authorization: BearerToken "abc123" }
44+ Headers.toArray hdrs `shouldEqual` [ "authorization" /\ "Bearer abc123" ]
test/Polymorphic.Spec.purs+5−6
@@ -12,7 +12,7 @@ import Yoga.Fastify.Fastify (Port(..), Host(..))
12import Yoga.Fastify.Om.API (registerAPI)
12import Yoga.Fastify.Om.API (registerAPI)
13import Yoga.Fastify.Om.Route (Handler, handle, respond)
13import Yoga.Fastify.Om.Route (Handler, handle, respond)
14import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
14import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
15−import Yoga.Om (Om, ask, runOm)
15+import Yoga.Om (Om, ask, expand, runOm)
16
16
17type User = { id :: Int, name :: String }
17type User = { id :: Int, name :: String }
18
18
@@ -22,7 +22,6 @@ type TestAPI =
22 { getUser :: GetUserRoute
22 { getUser :: GetUserRoute
23 }
23 }
24
24
25−api :: forall ctx err. { getUser :: { id :: Int } -> Om ctx err User }
26api = client @TestAPI "http://localhost:44931"
25api = client @TestAPI "http://localhost:44931"
27
26
28spec :: Spec Unit
27spec :: Spec Unit
@@ -30,23 +29,23 @@ spec = around withServer $ describe "polymorphic client" do
30 it "fetches user with { token :: String } context" \_ ->
29 it "fetches user with { token :: String } context" \_ ->
31 runOm { token: "secret123" } { exception: \_ -> pure unit } do
30 runOm { token: "secret123" } { exception: \_ -> pure unit } do
32 ctx <- ask
31 ctx <- ask
33− user <- api.getUser { id: 42 }
32+ user <- api.getUser { id: 42 } # expand
34 liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "secret123:User 42"
33 liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "secret123:User 42"
35
34
36 it "fetches user with { apiKey :: String, userId :: Int } context" \_ ->
35 it "fetches user with { apiKey :: String, userId :: Int } context" \_ ->
37 runOm { apiKey: "mykey", userId: 7 } { exception: \_ -> pure unit } do
36 runOm { apiKey: "mykey", userId: 7 } { exception: \_ -> pure unit } do
38 ctx <- ask
37 ctx <- ask
39− user <- api.getUser { id: ctx.userId }
38+ user <- api.getUser { id: ctx.userId } # expand
40 liftAff $ (ctx.apiKey <> ":" <> user.name) `shouldEqual` "mykey:User 7"
39 liftAff $ (ctx.apiKey <> ":" <> user.name) `shouldEqual` "mykey:User 7"
41
40
42 it "same api.getUser works in two different contexts" \_ -> do
41 it "same api.getUser works in two different contexts" \_ -> do
43 runOm { token: "tok1" } { exception: \_ -> pure unit } do
42 runOm { token: "tok1" } { exception: \_ -> pure unit } do
44 ctx <- ask
43 ctx <- ask
45− user <- api.getUser { id: 1 }
44+ user <- api.getUser { id: 1 } # expand
46 liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "tok1:User 1"
45 liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "tok1:User 1"
47 runOm { endpoint: "prod", region: "eu" } { exception: \_ -> pure unit } do
46 runOm { endpoint: "prod", region: "eu" } { exception: \_ -> pure unit } do
48 ctx <- ask
47 ctx <- ask
49− user <- api.getUser { id: 2 }
48+ user <- api.getUser { id: 2 } # expand
50 liftAff $ (ctx.endpoint <> "-" <> ctx.region <> ":" <> user.name) `shouldEqual` "prod-eu:User 2"
49 liftAff $ (ctx.endpoint <> "-" <> ctx.region <> ":" <> user.name) `shouldEqual` "prod-eu:User 2"
51
50
52withServer :: forall a. (Unit -> _ a) -> _ a
51withServer :: forall a. (Unit -> _ a) -> _ a