git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
Query params accept partial records directly — no opt/Just/Nothing

Generated client functions now accept partial query param records. Missing optional query fields are undefined at JS level, converted to Nothing via FFI readOptional. Zero runtime cost — no justifill or record rebuilding, just an undefined check per query field. api.listUsers { limit: 10 } -- offset is just undefined api.listUsers {} -- all query params default api.getUser { id: 42 } -- path params still required Uses the Union trick: given is a free type variable in each endpoint's function type, constrained by BuildClientFn. PureScript generalizes per-endpoint given variables at the api binding site.

commit79ec177997001d9a29ded948a5bc21923ea99f28 authorMark <mark.eibes@gmail.com> dateSat 18 Apr 2026, 17:43 (5 months ago) parentc78cf65
3 files changed+13−12
src/Yoga/Fetch/Om.purs+1−3 src/Yoga/Fetch/Om/BuildUrl.purs+4−2 test/BuildUrl.Spec.purs+8−7
src/Yoga/Fetch/Om.purs+1−3
@@ -97,8 +97,6 @@ instance
9797 , Row.Union pathParams queryParams pathQuery
9898 , Row.Nub pathQuery pathQuery
9999 , RowToList pathQuery pathQueryRL
100− , RowToList queryParams queryParamsRL
101− , ReadPartialQuery queryParamsRL queryParams
102100 , RowToList headers headersRL
103101 , ToHeaders headersRL headers
104102 , CheckBodyIsUnit body bodyFlag
@@ -113,7 +111,6 @@ instance
113111 convert = unsafeCoerce
114112 impl :: Record pathQuery -> Record headers -> body -> Om { | ctx } errRow result
115113 impl pathQueryRec headersRec bodyVal = widenOm do
116− let queryParamsRec = readPartialQuery @queryParamsRL pathQueryRec
117114 let url = buildUrl baseUrl (Proxy :: _ segments) pathParamsRec queryParamsRec
118115 let hdrs = toHeaders (Proxy :: _ headersRL) headersRec
119116 let ct = encodingContentType (Proxy :: _ encoding)
@@ -122,6 +119,7 @@ instance
122119 variantOrValue (Proxy :: _ successRL) variant # pure
123120 where
124121 pathParamsRec = unsafeCoerce pathQueryRec :: Record pathParams
122+ queryParamsRec = unsafeCoerce pathQueryRec :: Record queryParams
125123 widenOm :: Om (Record ()) routeErrors ~> Om { | ctx } errRow
126124 widenOm = unsafeCoerce
127125
src/Yoga/Fetch/Om/BuildUrl.purs+4−2
@@ -28,6 +28,7 @@ import Prim.RowList as RL
2828import Record as Record
2929import Type.Proxy (Proxy(..))
3030import Yoga.HTTP.API.Path (class PathPattern, pathPattern)
31+import Yoga.Fetch.Om.ReadOptional (readOptional)
3132
3233foreign import encodeURIComponent_ :: String -> String
3334
@@ -113,19 +114,20 @@ class AppendQueryParamsRL rl r where
113114instance AppendQueryParamsRL RL.Nil r where
114115 appendQueryParamsRL _ _ acc = acc
115116
117+-- Maybe ty: read with undefined-awareness — missing field → skip
116118instance
117119 ( IsSymbol name
118120 , SerializeParam ty
119121 , AppendQueryParamsRL tail r
120− , Row.Cons name (Maybe ty) tailRow r
121122 ) =>
122123 AppendQueryParamsRL (RL.Cons name (Maybe ty) tail) r where
123124 appendQueryParamsRL _ rec acc = appendQueryParamsRL (Proxy :: _ tail) rec acc'
124125 where
125− acc' = case Record.get (Proxy :: _ name) rec of
126+ acc' = case (readOptional @name rec :: Maybe ty) of
126127 Nothing -> acc
127128 Just value -> Array.snoc acc (encodeURIComponent_ (reflectSymbol (Proxy :: _ name)) <> "=" <> encodeURIComponent_ (serializeParam value))
128129
130+-- bare ty (Required query param): always present
129131else instance
130132 ( IsSymbol name
131133 , SerializeParam ty
test/BuildUrl.Spec.purs+8−7
@@ -2,10 +2,11 @@ module BuildUrl.Spec where
22
33import Prelude
44
5−import Data.Maybe (Maybe(..))
5+import Data.Maybe (Maybe)
66import Test.Spec (Spec, describe, it)
77import Test.Spec.Assertions (shouldEqual)
88import Type.Proxy (Proxy(..))
9+import Unsafe.Coerce (unsafeCoerce)
910import Yoga.Fetch.Om.BuildUrl (buildUrl, substitutePathParams, appendQueryParams)
1011import Yoga.HTTP.API.Path (Path, type (/), type (:))
1112
@@ -50,24 +51,24 @@ spec = do
5051 "/users"
5152 result `shouldEqual` "/users?limit=10&offset=20"
5253
53− it "handles Maybe params - Just" do
54+ it "handles optional params - provided via partial record" do
5455 let
5556 result = appendQueryParams @(limit :: Maybe Int)
56− { limit: Just 10 }
57+ (unsafeCoerce { limit: 10 } :: Record (limit :: Maybe Int))
5758 "/users"
5859 result `shouldEqual` "/users?limit=10"
5960
60− it "handles Maybe params - Nothing" do
61+ it "handles optional params - omitted via partial record" do
6162 let
6263 result = appendQueryParams @(limit :: Maybe Int)
63− { limit: Nothing }
64+ (unsafeCoerce {} :: Record (limit :: Maybe Int))
6465 "/users"
6566 result `shouldEqual` "/users"
6667
67− it "handles mixed Maybe and required params" do
68+ it "handles mixed optional and required params" do
6869 let
6970 result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
70− { limit: Nothing, offset: 20 }
71+ (unsafeCoerce { offset: 20 } :: Record (limit :: Maybe Int, offset :: Int))
7172 "/users"
7273 result `shouldEqual` "/users?offset=20"
7374