git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
v0.6.0: Add Streaming response support, update deps

- Add Streaming response support to ParseResponse - Add StreamDecode module - Update to local yoga-om-layer, yoga-om-strom, yoga-fastify, ref-lifted - Widen dep ranges for yoga-http-api, yoga-om-strom, yoga-fastify-om

commitf26d55a54e723478eccac933cacb1729770c6704 authorMark <mark.eibes@gmail.com> dateWed 11 Feb 2026, 22:47 (8 months ago) parenta9819b4
11 files changed+427−56
spago.lock+279−23 spago.yaml+17−8 src/Yoga/Fetch/Om.purs+11−4 src/Yoga/Fetch/Om/BuildUrl.purs+4−1 src/Yoga/Fetch/Om/ExtractParams.purs+2−1 src/Yoga/Fetch/Om/MakeRequest.purs+19−3 src/Yoga/Fetch/Om/ParseResponse.purs+70−12 src/Yoga/Fetch/Om/Simple.purs+3−3 src/Yoga/Fetch/Om/StreamDecode.js+1 src/Yoga/Fetch/Om/StreamDecode.purs+20 test/Polymorphic.Spec.purs+1−1
spago.lock+279−23
@@ -8,6 +8,9 @@
88 {
99 "aff": ">=7.0.0 <9.0.0"
1010 },
11+ {
12+ "arraybuffer-types": ">=3.0.2 <4.0.0"
13+ },
1114 {
1215 "arrays": ">=7.3.0 <8.0.0"
1316 },
@@ -57,14 +60,103 @@
5760 "variant": ">=8.0.0 <9.0.0"
5861 },
5962 {
60− "yoga-http-api": ">=0.1.0 <0.2.0"
63+ "yoga-http-api": ">=0.1.0 <0.3.0"
6164 },
6265 {
6366 "yoga-json": ">=5.2.0 <6.0.0"
6467 },
6568 {
6669 "yoga-om": ">=1.2.2 <2.0.0"
70+ },
71+ {
72+ "yoga-om-strom": ">=0.2.0 <0.5.0"
6773 }
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+ "ref-lifted",
135+ "refs",
136+ "safe-coerce",
137+ "spec",
138+ "st",
139+ "strings",
140+ "tailrec",
141+ "transformers",
142+ "tuples",
143+ "type-equality",
144+ "typelevel-prelude",
145+ "uncurried-transformers",
146+ "undefined-is-not-a-problem",
147+ "unfoldable",
148+ "unsafe-coerce",
149+ "untagged-union",
150+ "variant",
151+ "web-events",
152+ "web-file",
153+ "web-streams",
154+ "yoga-http-api",
155+ "yoga-json",
156+ "yoga-om",
157+ "yoga-om-strom",
158+ "yoga-options",
159+ "yoga-tree"
68160 ]
69161 },
70162 "test": {
@@ -82,8 +174,107 @@
82174 "spec-discovery": ">=8.0.0 <9.0.0"
83175 },
84176 {
85− "yoga-fastify-om": ">=0.1.0 <0.2.0"
177+ "yoga-fastify-om": ">=0.1.0 <0.4.0"
86178 }
179+ ],
180+ "build_plan": [
181+ "aff",
182+ "aff-retry",
183+ "ansi",
184+ "argonaut-codecs",
185+ "argonaut-core",
186+ "arraybuffer-types",
187+ "arrays",
188+ "avar",
189+ "bifunctors",
190+ "catenable-lists",
191+ "console",
192+ "const",
193+ "contravariant",
194+ "control",
195+ "datetime",
196+ "distributive",
197+ "effect",
198+ "either",
199+ "enums",
200+ "exceptions",
201+ "exists",
202+ "exitcodes",
203+ "foldable-traversable",
204+ "foreign",
205+ "foreign-object",
206+ "fork",
207+ "free",
208+ "functions",
209+ "functors",
210+ "gen",
211+ "heterogeneous",
212+ "identity",
213+ "integers",
214+ "invariant",
215+ "js-bigints",
216+ "js-date",
217+ "js-promise",
218+ "js-promise-aff",
219+ "js-uri",
220+ "lazy",
221+ "lists",
222+ "literals",
223+ "maybe",
224+ "mmorph",
225+ "newtype",
226+ "node-buffer",
227+ "node-event-emitter",
228+ "node-fs",
229+ "node-path",
230+ "node-process",
231+ "node-streams",
232+ "nonempty",
233+ "now",
234+ "nullable",
235+ "numbers",
236+ "open-memoize",
237+ "optparse",
238+ "ordered-collections",
239+ "orders",
240+ "parallel",
241+ "partial",
242+ "pipes",
243+ "posix-types",
244+ "prelude",
245+ "profunctor",
246+ "random",
247+ "record",
248+ "record-studio",
249+ "ref-lifted",
250+ "refs",
251+ "routing-duplex",
252+ "safe-coerce",
253+ "spec",
254+ "spec-discovery",
255+ "spec-node",
256+ "st",
257+ "strings",
258+ "tailrec",
259+ "transformers",
260+ "tuples",
261+ "type-equality",
262+ "typelevel-prelude",
263+ "uncurried-transformers",
264+ "undefined-is-not-a-problem",
265+ "unfoldable",
266+ "unsafe-coerce",
267+ "untagged-union",
268+ "variant",
269+ "yoga-fastify",
270+ "yoga-fastify-om",
271+ "yoga-http-api",
272+ "yoga-json",
273+ "yoga-om",
274+ "yoga-om-layer",
275+ "yoga-om-strom",
276+ "yoga-options",
277+ "yoga-tree"
87278 ]
88279 }
89280 }
@@ -680,15 +871,28 @@
680871 }
681872 },
682873 "extra_packages": {
683− "yoga-fastify": "0.2.3",
874+ "ref-lifted": {
875+ "path": "../purescript-ref-lifted"
876+ },
877+ "yoga-fastify": {
878+ "path": "../purescript-yoga-fastify"
879+ },
684880 "yoga-fastify-om": {
685881 "path": "../purescript-yoga-fastify-om"
686882 },
687− "yoga-http-api": "0.1.6",
883+ "yoga-http-api": {
884+ "path": "../purescript-yoga-http-api"
885+ },
688886 "yoga-json": "5.2.0",
689− "yoga-om": "1.2.2",
690− "yoga-om-layer": "1.0.6",
691− "yoga-om-strom": "0.2.0",
887+ "yoga-om": {
888+ "path": "../purescript-yoga-om"
889+ },
890+ "yoga-om-layer": {
891+ "path": "../purescript-yoga-om-layer"
892+ },
893+ "yoga-om-strom": {
894+ "path": "../purescript-yoga-om-strom"
895+ },
692896 "yoga-options": "0.1.0"
693897 }
694898 },
@@ -714,6 +918,26 @@
714918 "unsafe-coerce"
715919 ]
716920 },
921+ "aff-retry": {
922+ "type": "registry",
923+ "version": "2.0.0",
924+ "integrity": "sha256-rKZNcP4IKSyBFQBVzrCvE0ULoJt0N/uDLLJ0dPotr4k=",
925+ "dependencies": [
926+ "aff",
927+ "arrays",
928+ "datetime",
929+ "effect",
930+ "either",
931+ "exceptions",
932+ "integers",
933+ "maybe",
934+ "newtype",
935+ "numbers",
936+ "prelude",
937+ "random",
938+ "transformers"
939+ ]
940+ },
717941 "ansi": {
718942 "type": "registry",
719943 "version": "7.0.0",
@@ -1637,6 +1861,16 @@
16371861 "tuples"
16381862 ]
16391863 },
1864+ "random": {
1865+ "type": "registry",
1866+ "version": "6.0.0",
1867+ "integrity": "sha256-7sZRo6P9UIiXDZ9s2QL9T8h3wlIwLdQrR4+oZKOijQw=",
1868+ "dependencies": [
1869+ "effect",
1870+ "integers",
1871+ "prelude"
1872+ ]
1873+ },
16401874 "record": {
16411875 "type": "registry",
16421876 "version": "4.0.0",
@@ -1660,6 +1894,15 @@
16601894 "unsafe-coerce"
16611895 ]
16621896 },
1897+ "ref-lifted": {
1898+ "type": "local",
1899+ "path": "../purescript-ref-lifted",
1900+ "dependencies": [
1901+ "effect",
1902+ "prelude",
1903+ "refs"
1904+ ]
1905+ },
16631906 "refs": {
16641907 "type": "registry",
16651908 "version": "6.0.0",
@@ -2012,9 +2255,8 @@
20122255 ]
20132256 },
20142257 "yoga-fastify": {
2015− "type": "registry",
2016− "version": "0.2.3",
2017− "integrity": "sha256-150ocqfhYCc38HeChWbJXie0wn2bjKpORkZFQCRxICo=",
2258+ "type": "local",
2259+ "path": "../purescript-yoga-fastify",
20182260 "dependencies": [
20192261 "aff",
20202262 "arrays",
@@ -2054,6 +2296,7 @@
20542296 "node-buffer",
20552297 "prelude",
20562298 "record",
2299+ "ref-lifted",
20572300 "refs",
20582301 "routing-duplex",
20592302 "transformers",
@@ -2068,9 +2311,8 @@
20682311 ]
20692312 },
20702313 "yoga-http-api": {
2071− "type": "registry",
2072− "version": "0.1.6",
2073− "integrity": "sha256-wI2aiM7S99gq7pPCBI8ijemMNV5vqjToLHVLr9nCtog=",
2314+ "type": "local",
2315+ "path": "../purescript-yoga-http-api",
20742316 "dependencies": [
20752317 "aff",
20762318 "arrays",
@@ -2133,11 +2375,11 @@
21332375 ]
21342376 },
21352377 "yoga-om": {
2136− "type": "registry",
2137− "version": "1.2.2",
2138− "integrity": "sha256-uDV3/o8mUOTIgSEQFqIATPyladzL8BcjURvfS901hgI=",
2378+ "type": "local",
2379+ "path": "../purescript-yoga-om",
21392380 "dependencies": [
21402381 "aff",
2382+ "aff-retry",
21412383 "avar",
21422384 "console",
21432385 "control",
@@ -2152,6 +2394,8 @@
21522394 "prelude",
21532395 "record",
21542396 "record-studio",
2397+ "ref-lifted",
2398+ "refs",
21552399 "tailrec",
21562400 "transformers",
21572401 "tuples",
@@ -2162,24 +2406,35 @@
21622406 ]
21632407 },
21642408 "yoga-om-layer": {
2165− "type": "registry",
2166− "version": "1.0.6",
2167− "integrity": "sha256-RrCaTvkiMwCFarWKbu0Dkq+yFz4hDM7x18gZok2OxV4=",
2409+ "type": "local",
2410+ "path": "../purescript-yoga-om-layer",
21682411 "dependencies": [
21692412 "aff",
2170− "newtype",
2413+ "aff-retry",
2414+ "arrays",
2415+ "effect",
2416+ "exceptions",
2417+ "foldable-traversable",
2418+ "maybe",
2419+ "nullable",
2420+ "ordered-collections",
2421+ "parallel",
21712422 "prelude",
21722423 "record",
21732424 "record-studio",
2425+ "ref-lifted",
2426+ "refs",
2427+ "transformers",
21742428 "type-equality",
2429+ "typelevel-prelude",
21752430 "unsafe-coerce",
2431+ "variant",
21762432 "yoga-om"
21772433 ]
21782434 },
21792435 "yoga-om-strom": {
2180− "type": "registry",
2181− "version": "0.2.0",
2182− "integrity": "sha256-WMrhLQ71RFObD4Vv/X5qef+uIkJ4zgCv/K8l986g1wE=",
2436+ "type": "local",
2437+ "path": "../purescript-yoga-om-strom",
21832438 "dependencies": [
21842439 "aff",
21852440 "arrays",
@@ -2197,6 +2452,7 @@
21972452 "maybe",
21982453 "now",
21992454 "prelude",
2455+ "ref-lifted",
22002456 "refs",
22012457 "spec",
22022458 "st",
spago.yaml+17−8
@@ -4,18 +4,25 @@ workspace:
44 extraPackages:
55 yoga-fastify-om:
66 path: ../purescript-yoga-fastify-om
7− yoga-fastify: "0.2.3"
8− yoga-http-api: "0.1.6"
9− yoga-om: "1.2.2"
10− yoga-om-layer: "1.0.6"
11− yoga-om-strom: "0.2.0"
7+ yoga-fastify:
8+ path: ../purescript-yoga-fastify
9+ yoga-http-api:
10+ path: ../purescript-yoga-http-api
11+ yoga-om:
12+ path: ../purescript-yoga-om
13+ yoga-om-layer:
14+ path: ../purescript-yoga-om-layer
15+ yoga-om-strom:
16+ path: ../purescript-yoga-om-strom
1217 yoga-options: "0.1.0"
1318 yoga-json: "5.2.0"
19+ ref-lifted:
20+ path: ../purescript-ref-lifted
1421
1522package:
1623 name: yoga-fetch-om
1724 publish:
18− version: 0.5.2
25+ version: 0.6.0
1926 license: MIT
2027 location:
2128 githubOwner: rowtype-yoga
@@ -44,14 +51,16 @@ package:
4451 - typelevel-prelude: ">=7.0.0 <8.0.0"
4552 - unsafe-coerce: ">=6.0.0 <7.0.0"
4653 - variant: ">=8.0.0 <9.0.0"
47− - yoga-http-api: ">=0.1.0 <0.2.0"
54+ - yoga-http-api: ">=0.1.0 <0.3.0"
4855 - yoga-json: ">=5.2.0 <6.0.0"
56+ - arraybuffer-types: ">=3.0.2 <4.0.0"
4957 - yoga-om: ">=1.2.2 <2.0.0"
58+ - yoga-om-strom: ">=0.2.0 <0.5.0"
5059 test:
5160 main: Test.Main
5261 dependencies:
5362 - spec: ">=8.0.0 <9.0.0"
5463 - spec-discovery: ">=8.0.0 <9.0.0"
5564 - console: ">=6.0.0 <7.0.0"
56− - yoga-fastify-om: ">=0.1.0 <0.2.0"
65+ - yoga-fastify-om: ">=0.1.0 <0.4.0"
5766 - refs: ">=6.0.0 <7.0.0"
src/Yoga/Fetch/Om.purs+11−4
@@ -12,6 +12,8 @@ module Yoga.Fetch.Om
1212 , toHeaders
1313 , module Yoga.HTTP.API.Route
1414 , module Yoga.HTTP.API.Path
15+ , plainText
16+ , module Yoga.Fetch.Om.StreamDecode
1517 , module Yoga.Fetch.Om.Simple
1618 ) where
1719
@@ -30,16 +32,17 @@ import JS.Fetch.Headers as Headers
3032import Type.Row.Homogeneous (class Homogeneous)
3133import Yoga.Fetch.Om.BuildUrl (class BuildUrl, buildUrl)
3234import Yoga.Fetch.Om.Simple (class DecodeResponse, decodeResponse, FetchError, FetchResponse, get, getWithHeaders, delete, deleteWithHeaders, delete_, post, postWithHeaders, post_, put, putWithHeaders, put_, patch, patchWithHeaders, patch_)
35+import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
3336import Yoga.Fetch.Om.ClientFunction (class BuildClientFn, class CheckBodyIsUnit, buildClientFn)
3437import Yoga.Fetch.Om.ExtractParams (class ExtractRequestBody, class ExtractRequestHeaders)
35−import Yoga.Fetch.Om.MakeRequest (class MakeRequest, class SerializeBody, makeRequest, serializeBody)
38+import Yoga.Fetch.Om.MakeRequest (class MakeRequest, class SerializeBody, class ContentType, makeRequest, serializeBody, contentType)
3639import Yoga.Fetch.Om.ParseResponse (class ParseResponse, parseResponse)
3740import Yoga.Fetch.Om.SplitResponses (class SplitResponses)
3841import Yoga.Fetch.Om.Variant (class VariantOrValue, variantOrValue)
3942import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParams, Required, type (/), type (:), type (:?), class PathPattern)
40−import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, Response(..), JSON, FormData, NoBody)
43+import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, Response(..), JSON, FormData, PlainText, Streaming, NoBody)
4144import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
42−import Yoga.Om (Om, fromAff)
45+import Yoga.Om (class ToOm, Om, toOm)
4346
4447-- | Extract row type from Record type
4548class RecordRow :: Type -> Row Type -> Constraint
@@ -72,6 +75,7 @@ instance
7275 , BuildUrl segments pathParams queryParams
7376 , MakeRequest method
7477 , SerializeBody body
78+ , ContentType body
7579 , SplitResponses response successRow routeErrors
7680 , ParseResponse routeErrors successRow
7781 , RowToList successRow successRL
@@ -92,7 +96,7 @@ instance
9296 impl pathQueryRec headersRec bodyVal = do
9397 let url = buildUrl baseUrl (Proxy :: _ segments) pathParamsRec queryParamsRec
9498 let hdrs = toHeaders (Proxy :: _ headersRL) headersRec
95− fetchResp <- makeRequest (Proxy :: _ method) url hdrs (serializeBody bodyVal) # fromAff
99+ fetchResp <- makeRequest (Proxy :: _ method) url hdrs (contentType (Proxy :: _ body)) (serializeBody bodyVal) # toOm
96100 variant <- parseResponse fetchResp :: Om (Record ()) routeErrors (Variant successRow)
97101 variantOrValue (Proxy :: _ successRL) variant # pure
98102 where
@@ -120,6 +124,9 @@ client baseUrl = polymorphic (deriveClientImpl baseUrl (Proxy :: _ { | routesRow
120124deriveClient :: forall @routesRow clientsRow polyClientsRow. DeriveClient routesRow clientsRow => String -> Record polyClientsRow
121125deriveClient baseUrl = polymorphic (deriveClientImpl baseUrl (Proxy :: _ { | routesRow }) :: Record clientsRow)
122126
127+plainText :: PlainText -> String
128+plainText = unsafeCoerce
129+
123130polymorphic :: forall a b. a -> b
124131polymorphic = unsafeCoerce
125132
src/Yoga/Fetch/Om/BuildUrl.purs+4−1
@@ -46,7 +46,10 @@ instance
4646 pattern = pathPattern segmentsProxy
4747 withBase = case baseUrl of
4848 "" -> pattern
49− base -> base <> pattern
49+ base -> dropTrailingSlash base <> pattern
50+ dropTrailingSlash s = case String.stripSuffix (Pattern "/") s of
51+ Just s' -> dropTrailingSlash s'
52+ Nothing -> s
5053 withPathParams = substitutePathParamsImpl (Proxy :: _ pathParams) pathParamsRec withBase
5154 withQueryParams = appendQueryParamsImpl (Proxy :: _ queryParams) queryParamsRec withPathParams
5255
src/Yoga/Fetch/Om/ExtractParams.purs+2−1
@@ -9,7 +9,7 @@ module Yoga.Fetch.Om.ExtractParams
99import Data.Unit (Unit)
1010import Prim.RowList (class RowToList, RowList)
1111import Prim.RowList as RL
12−import Yoga.HTTP.API.Route.Encoding (JSON, FormData, NoBody)
12+import Yoga.HTTP.API.Route.Encoding (JSON, FormData, NoBody, PlainText)
1313import Yoga.HTTP.API.Route.Handler (Request)
1414
1515class ExtractRequestBody :: Type -> Type -> Constraint
@@ -50,6 +50,7 @@ class UnwrapEncoding encoding body | encoding -> body
5050instance UnwrapEncoding (JSON ty) ty
5151instance UnwrapEncoding (FormData ty) ty
5252instance UnwrapEncoding NoBody Unit
53+instance UnwrapEncoding PlainText String
5354
5455class FindHeaders :: RowList Type -> Row Type -> Constraint
5556class FindHeaders rl headers | rl -> headers
src/Yoga/Fetch/Om/MakeRequest.purs+19−3
@@ -4,6 +4,8 @@ module Yoga.Fetch.Om.MakeRequest
44 , makeRequest
55 , class SerializeBody
66 , serializeBody
7+ , class ContentType
8+ , contentType
79 ) where
810
911import Prelude
@@ -58,9 +60,10 @@ makeRequest
5860 => Proxy method
5961 -> String
6062 -> Headers
63+ -> String
6164 -> Maybe String
6265 -> Aff FetchResponse.Response
63−makeRequest proxy url customHeaders maybeBody = do
66+makeRequest proxy url customHeaders defaultContentType maybeBody = do
6467 request <- Request.new url options # liftEffect
6568 Promise.toAffE $ Fetch.fetch request
6669 where
@@ -70,7 +73,7 @@ makeRequest proxy url customHeaders maybeBody = do
7073 contentTypeArr = case maybeBody of
7174 Nothing -> []
7275 Just _ | hasContentType -> []
73− Just _ -> [ "Content-Type" /\ "application/json" ]
76+ Just _ -> [ "Content-Type" /\ defaultContentType ]
7477 allHeaders = Headers.fromFoldable (contentTypeArr <> customArr)
7578 body = case maybeBody of
7679 Nothing -> Body.empty
@@ -88,11 +91,24 @@ makeRequest proxy url customHeaders maybeBody = do
8891 , cache: Cache.Default
8992 }
9093
94+class ContentType :: Type -> Constraint
95+class ContentType body where
96+ contentType :: Proxy body -> String
97+
98+instance ContentType String where
99+ contentType _ = "text/plain"
100+else instance ContentType Unit where
101+ contentType _ = "application/json"
102+else instance ContentType body where
103+ contentType _ = "application/json"
104+
91105class SerializeBody :: Type -> Constraint
92106class SerializeBody body where
93107 serializeBody :: body -> Maybe String
94108
95−instance SerializeBody Unit where
109+instance SerializeBody String where
110+ serializeBody s = Just s
111+else instance SerializeBody Unit where
96112 serializeBody _ = Nothing
97113else instance WriteForeign body => SerializeBody body where
98114 serializeBody b = Just (writeJSON b)
src/Yoga/Fetch/Om/ParseResponse.purs+70−12
@@ -10,11 +10,13 @@ module Yoga.Fetch.Om.ParseResponse
1010import Prelude
1111
1212import Control.Monad.Error.Class (throwError)
13+import Data.Either (Either(..))
1314import Data.Symbol (class IsSymbol)
1415import Data.Variant (Variant)
1516import Data.Variant as Variant
1617import Effect.Aff (Aff)
1718import Effect.Aff as Aff
19+import Effect.Class (liftEffect)
1820import Effect.Exception as Exception
1921import JS.Fetch.Response (Response) as FetchResponse
2022import JS.Fetch.Response as Fetch
@@ -23,12 +25,16 @@ import Prim.RowList (class RowToList, RowList)
2325import Prim.RowList as RL
2426import Promise.Aff as Promise
2527import Type.Proxy (Proxy(..))
28+import Unsafe.Coerce (unsafeCoerce)
29+import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
30+import Yoga.HTTP.API.Route.Encoding (PlainText, Streaming)
2631import Yoga.HTTP.API.Route.StatusCode (class StatusCodeMap, statusCodeFor, StatusCode(..))
27−import Data.Either (Either(..))
2832import Yoga.JSON (class ReadForeign, readJSON)
2933import Yoga.JSON.Error (withStringErrors)
30−import Yoga.Om (Om, fromAff)
34+import Yoga.Om (class ToOm, Om, toOm)
3135import Yoga.Om.Error (Exception)
36+import Yoga.Om.Strom (Strom)
37+import Yoga.Om.Strom.WebStream as WebStream
3238
3339class ParseResponse (errorRow :: Row Type) (successRow :: Row Type) where
3440 parseResponse :: FetchResponse.Response -> Om {} errorRow (Variant successRow)
@@ -42,21 +48,55 @@ instance
4248 ParseResponse errorRow successRow where
4349 parseResponse fetchResp = do
4450 let status = Fetch.status fetchResp
45− jsonText <- Promise.toAffE (Fetch.text fetchResp) # fromAff
4651 if status >= 200 && status < 300 then
47− parseSuccessRL (Proxy :: _ successRL) status jsonText # fromAff
48− else if status >= 400 then
49− parseErrorRL (Proxy :: _ errorRL) (Proxy :: _ errorRow) status jsonText
52+ parseSuccessRL (Proxy :: _ successRL) status fetchResp # toOm
53+ else if status >= 400 then do
54+ errorText <- Promise.toAffE (Fetch.text fetchResp) # toOm
55+ parseErrorRL (Proxy :: _ errorRL) (Proxy :: _ errorRow) status errorText
5056 else
5157 throwError (Variant.inj (Proxy :: _ "exception") (Exception.error ("Unexpected HTTP status code: " <> show status)))
5258
5359class ParseSuccessRL (rl :: RowList Type) (successRow :: Row Type) | rl -> successRow where
54− parseSuccessRL :: Proxy rl -> Int -> String -> Aff (Variant successRow)
60+ parseSuccessRL :: Proxy rl -> Int -> FetchResponse.Response -> Aff (Variant successRow)
5561
5662instance ParseSuccessRL RL.Nil successRow where
5763 parseSuccessRL _ status _ =
5864 Aff.throwError (Exception.error ("Unexpected success status code: " <> show status))
5965
66+else instance
67+ ( IsSymbol label
68+ , StatusCodeMap label
69+ , Row.Cons label PlainText tailRow successRow
70+ , Row.Lacks label tailRow
71+ , ParseSuccessRL tailRL successRow
72+ ) =>
73+ ParseSuccessRL (RL.Cons label PlainText tailRL) successRow where
74+ parseSuccessRL _ actualStatus fetchResp = do
75+ let StatusCode expected = statusCodeFor (Proxy :: _ label)
76+ if actualStatus == expected then do
77+ text <- liftEffect (Fetch.text fetchResp) >>= Promise.toAff
78+ pure (Variant.inj (Proxy :: _ label) (unsafeCoerce text))
79+ else
80+ parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
81+
82+else instance
83+ ( IsSymbol label
84+ , StatusCodeMap label
85+ , StreamDecode a
86+ , Row.Cons label (Streaming a) tailRow successRow
87+ , Row.Lacks label tailRow
88+ , ParseSuccessRL tailRL successRow
89+ ) =>
90+ ParseSuccessRL (RL.Cons label (Streaming a) tailRL) successRow where
91+ parseSuccessRL _ actualStatus fetchResp = do
92+ let StatusCode expected = statusCodeFor (Proxy :: _ label)
93+ if actualStatus == expected then do
94+ webStream <- liftEffect (Fetch.body fetchResp)
95+ let strom = decodeStream (WebStream.fromReadableStream (unsafeCoerce webStream)) :: Strom {} () a
96+ pure (Variant.inj (Proxy :: _ label) (unsafeCoerce strom))
97+ else
98+ parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
99+
60100else instance
61101 ( IsSymbol label
62102 , StatusCodeMap label
@@ -66,13 +106,15 @@ else instance
66106 , ParseSuccessRL tailRL successRow
67107 ) =>
68108 ParseSuccessRL (RL.Cons label body tailRL) successRow where
69− parseSuccessRL _ actualStatus jsonText = do
109+ parseSuccessRL _ actualStatus fetchResp = do
70110 let StatusCode expected = statusCodeFor (Proxy :: _ label)
71− if actualStatus == expected then case withStringErrors (readJSON jsonText) of
72− Left errs -> Aff.throwError (Exception.error ("Failed to parse response (status " <> show actualStatus <> "):\n" <> errs))
73− Right parsed -> pure (Variant.inj (Proxy :: _ label) parsed)
111+ if actualStatus == expected then do
112+ jsonText <- liftEffect (Fetch.text fetchResp) >>= Promise.toAff
113+ case withStringErrors (readJSON jsonText) of
114+ Left errs -> Aff.throwError (Exception.error ("Failed to parse response (status " <> show actualStatus <> "):\n" <> errs))
115+ Right parsed -> pure (Variant.inj (Proxy :: _ label) parsed)
74116 else
75− parseSuccessRL (Proxy :: _ tailRL) actualStatus jsonText
117+ parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
76118
77119class ParseErrorRL (rl :: RowList Type) (errorRow :: Row Type) | rl -> errorRow where
78120 parseErrorRL
@@ -87,6 +129,22 @@ instance ParseErrorRL RL.Nil errorRow where
87129 parseErrorRL _ _ status _ =
88130 throwError (Variant.inj (Proxy :: _ "exception") (Exception.error ("Unexpected error status code: " <> show status)))
89131
132+else instance
133+ ( IsSymbol label
134+ , StatusCodeMap label
135+ , Row.Cons label PlainText tailRow errorRow
136+ , Row.Lacks label tailRow
137+ , Row.Cons label PlainText tailExc (Exception errorRow)
138+ , ParseErrorRL tailRL errorRow
139+ ) =>
140+ ParseErrorRL (RL.Cons label PlainText tailRL) errorRow where
141+ parseErrorRL _ errorProxy actualStatus text = do
142+ let StatusCode expected = statusCodeFor (Proxy :: _ label)
143+ if actualStatus == expected then
144+ throwError (Variant.inj (Proxy :: _ label) (unsafeCoerce text))
145+ else
146+ parseErrorRL (Proxy :: _ tailRL) errorProxy actualStatus text
147+
90148else instance
91149 ( IsSymbol label
92150 , StatusCodeMap label
src/Yoga/Fetch/Om/Simple.purs+3−3
@@ -46,7 +46,7 @@ import Type.Row.Homogeneous (class Homogeneous)
4646-- PlainText removed, using local phantom type
4747import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON, writeJSON)
4848import Yoga.JSON.Error (withStringErrors)
49−import Yoga.Om (Om, fromAff, throw)
49+import Yoga.Om (class ToOm, Om, toOm, throw)
5050
5151type FetchError = { status :: Int, body :: String }
5252
@@ -74,8 +74,8 @@ simpleFetch
7474 -> Maybe String
7575 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
7676simpleFetch method headers url maybeBody = do
77− resp <- doFetch # fromAff
78− respText <- Promise.toAffE (Resp.text resp) # fromAff
77+ resp <- doFetch # toOm
78+ respText <- Promise.toAffE (Resp.text resp) # toOm
7979 let status = Resp.status resp
8080 let respHeaders = Resp.headers resp
8181 if status >= 200 && status < 300 then
src/Yoga/Fetch/Om/StreamDecode.jsnew file+1
@@ -0,0 +1 @@
1+export const decodeUtf8 = (uint8array) => new TextDecoder().decode(uint8array);
src/Yoga/Fetch/Om/StreamDecode.pursnew file+20
@@ -0,0 +1,20 @@
1+module Yoga.Fetch.Om.StreamDecode
2+ ( class StreamDecode
3+ , decodeStream
4+ ) where
5+
6+import Prelude
7+import Data.ArrayBuffer.Types (Uint8Array)
8+import Yoga.Om.Strom (Strom)
9+import Yoga.Om.Strom as Strom
10+
11+class StreamDecode a where
12+ decodeStream :: Strom {} () Uint8Array -> Strom {} () a
13+
14+instance StreamDecode Uint8Array where
15+ decodeStream = identity
16+
17+else instance StreamDecode String where
18+ decodeStream = Strom.mapStrom decodeUtf8
19+
20+foreign import decodeUtf8 :: Uint8Array -> String
test/Polymorphic.Spec.purs+1−1
@@ -62,7 +62,7 @@ withServer test = bracket acquire release (\_ -> test unit)
6262
6363 release = F.close
6464
65−getUserHandler :: Handler GetUserRoute
65+getUserHandler :: Handler GetUserRoute ()
6666getUserHandler = handle do
6767 { path } <- ask
6868 respond @"ok" { id: path.id, name: "User " <> show path.id }