on master help sign in
Merge Vessel stuff
commit0b214e6fe87f9ceca85353415feb5e5116596ed3 authorRyan Trinkle <ryan.trinkle@gmail.com> dateSun 2 Dec 2018, 18:28 (8 years ago) parent485d583
21 files changed+501−199
default.nix+25−12 dep/constraints-extras/default.nix+7 dep/constraints-extras/github.json+7 dep/dependent-map/default.nix+7 dep/dependent-map/github.json+7 dep/dependent-monoidal-map/default.nix+7 dep/dependent-monoidal-map/github.json+7 dep/dependent-sum-aeson-orphans/default.nix+7 dep/dependent-sum-aeson-orphans/github.json+7 dep/reflex-aeson-orphans/default.nix+7 dep/reflex-aeson-orphans/github.json+7 dep/vessel/default.nix+7 dep/vessel/github.json+7 lib/backend/obelisk-backend.cabal+4 lib/backend/src/Data/Aeson/GADT.hs+184 lib/backend/src/Obelisk/Api.hs+26−95 lib/backend/src/Obelisk/Api/Pipeline.hs+44−43 lib/request/obelisk-request.cabal+2−1 lib/request/src/Obelisk/Request.hs+7−19 skeleton/backend/src/Backend.hs+98−7 skeleton/common/src/Common/Api.hs+27−22
default.nix+25−12
@@ -16,11 +16,17 @@ let
1616 openssh
1717 ];
1818
19− beam-src = hackGet ./dep/beam;
20−
21− gargoyle-src = hackGet ./dep/gargoyle;
22−
23− postgresql-simple-src = hackGet ./dep/postgresql-simple;
19+ src = {
20+ beam = hackGet ./dep/beam;
21+ constraints-extras = hackGet ./dep/constraints-extras;
22+ dependent-map = hackGet ./dep/dependent-map;
23+ dependent-monoidal-map = hackGet ./dep/dependent-monoidal-map;
24+ dependent-sum-aeson-orphans = hackGet ./dep/dependent-sum-aeson-orphans;
25+ gargoyle = hackGet ./dep/gargoyle;
26+ postgresql-simple = hackGet ./dep/postgresql-simple;
27+ reflex-aeson-orphans = hackGet ./dep/reflex-aeson-orphans;
28+ vessel = hackGet ./dep/vessel;
29+ };
2430
2531 getReflexPlatform = getReflexPlatform' __useLegacyCompilers;
2632 getReflexPlatform' = __useLegacyCompilers: sys: import ./dep/reflex-platform {
@@ -71,11 +77,16 @@ let
7177 sha256 = "1v337d810d88jzfriw07pr16d34nibm6q2zkw787lc3sfn07glp5";
7278 }) {};
7379
74− beam-core = dontCheck (self.callCabal2nix "beam-core" (beam-src + /beam-core) {});
75− beam-postgres = dontCheck (self.callCabal2nix "beam-postgres" (beam-src + /beam-postgres) {});
76− beam-migrate = self.callCabal2nix "beam-migrate" (beam-src + /beam-migrate) {};
80+ beam-core = dontCheck (self.callCabal2nix "beam-core" (src.beam + /beam-core) {});
81+ beam-postgres = dontCheck (self.callCabal2nix "beam-postgres" (src.beam + /beam-postgres) {});
82+ beam-migrate = self.callCabal2nix "beam-migrate" (src.beam + /beam-migrate) {};
7783
78− postgresql-simple = dontCheck (self.callCabal2nix "postgresql-simple" postgresql-simple-src {});
84+ postgresql-simple = dontCheck (self.callCabal2nix "postgresql-simple" src.postgresql-simple {});
85+ constraints-extras = self.callCabal2nix "constraints-extras" src.constraints-extras {};
86+ dependent-map = self.callCabal2nix "dependent-map" src.dependent-map {};
87+ dependent-monoidal-map = self.callCabal2nix "dependent-monoidal-map" src.dependent-monoidal-map {};
88+ dependent-sum-aeson-orphans = self.callCabal2nix "dependent-sum-aeson-orphans" src.dependent-sum-aeson-orphans {};
89+ reflex-aeson-orphans = self.callCabal2nix "reflex-aeson-orphans" src.reflex-aeson-orphans {};
7990
8091 websockets = self.callCabal2nix "websockets" (pkgs.fetchFromGitHub {
8192 owner = "obsidiansystems";
@@ -84,11 +95,13 @@ let
8495 sha256 = "1wd0gkx8a4x6n5cdkra96zvpv8l49i0z90amvrng70ybnzbxpfi5";
8596 }) {};
8697
87− gargoyle = doJailbreak (self.callCabal2nix "gargoyle" (gargoyle-src + /gargoyle) {});
98+ vessel = self.callCabal2nix "vessel" src.vessel {};
99+
100+ gargoyle = doJailbreak (self.callCabal2nix "gargoyle" (src.gargoyle + /gargoyle) {});
88101 gargoyle-postgresql-nix = pkgs.haskell.lib.addBuildTool
89− (self.callCabal2nix "gargoyle-postgresql-nix" (gargoyle-src + /gargoyle-postgresql-nix) {})
102+ (self.callCabal2nix "gargoyle-postgresql-nix" (src.gargoyle + /gargoyle-postgresql-nix) {})
90103 pkgs.postgresql100; # `staticWhich` requires `psql` on PATH during build time
91− gargoyle-postgresql = addBuildDepend (doJailbreak (self.callCabal2nix "gargoyle-postgresql" (gargoyle-src + /gargoyle-postgresql) {})) pkgs.postgresql100;
104+ gargoyle-postgresql = addBuildDepend (doJailbreak (self.callCabal2nix "gargoyle-postgresql" (src.gargoyle + /gargoyle-postgresql) {})) pkgs.postgresql100;
92105 postgresql-libpq = enableCabalFlag (overrideCabal super.postgresql-libpq (drv: {
93106 pkgconfigDepends = (drv.pkgconfigDepends or []) ++ [pkgs.postgresql100]; #TODO: Obelisk should probably provide this version of postgresql
94107 })) "use-pkg-config";
dep/constraints-extras/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
dep/constraints-extras/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "obsidiansystems",
3+ "repo": "constraints-extras",
4+ "branch": "master",
5+ "rev": "2f1674dbb53b611bbdc4bfd7a617dc2a674034f3",
6+ "sha256": "1x73c11fwms5hvwry9zma164m04wq3w6ac65hry8yf7fm6ks2smi"
7+}
dep/dependent-map/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
dep/dependent-map/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "obsidiansystems",
3+ "repo": "dependent-map",
4+ "branch": "cg-expose-internals",
5+ "rev": "e274e57bc263a309d8d53112f17f7d06d81d9f7e",
6+ "sha256": "1wb25f1r6rl4i1ylgqrqz387p2jvfv21g9hlc9904mc76k8q949v"
7+}
dep/dependent-monoidal-map/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
dep/dependent-monoidal-map/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "obsidiansystems",
3+ "repo": "dependent-monoidal-map",
4+ "branch": "develop",
5+ "rev": "c2c9edb02b89ef67850e8ac3c3a314963b875324",
6+ "sha256": "15qqmc9j984pwrsiviz4wrkxspzzw2x9b86zily4fqlb3yqlnlny"
7+}
dep/dependent-sum-aeson-orphans/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
dep/dependent-sum-aeson-orphans/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "obsidiansystems",
3+ "repo": "dependent-sum-aeson-orphans",
4+ "branch": "master",
5+ "rev": "a4b2f1113dbfe09a0abf417e2a7fbd6215b0e213",
6+ "sha256": "1nwij0nxa3pbm4dky78iicgf1paph58whni2b7ga27f9qakbhmpr"
7+}
dep/reflex-aeson-orphans/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
dep/reflex-aeson-orphans/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "reflex-frp",
3+ "repo": "reflex-aeson-orphans",
4+ "branch": "master",
5+ "rev": "cf7e30cd0908660231956ef415103af1913feb69",
6+ "sha256": "1hv7kxxk6s7b8xhbwx8i3fx6rk2ggvvsv1grwzydxl54lyhy60c6"
7+}
dep/vessel/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
dep/vessel/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "obsidiansystems",
3+ "repo": "vessel",
4+ "branch": "develop",
5+ "rev": "b56cf521cb7c6ad051b882e0c44afaf890864af3",
6+ "sha256": "1qrq44snhws7wvkck88md3czghfhcqd53v85jxjfaa9ks94dabsz"
7+}
lib/backend/obelisk-backend.cabal+4
@@ -14,10 +14,12 @@ library
1414 categories,
1515 clientsession,
1616 constraints,
17+ constraints-extras,
1718 containers,
1819 data-default,
1920 dependent-sum,
2021 dependent-sum-template,
22+ dependent-map,
2123 directory,
2224 --TODO: Do we really want enclosed-exceptions?
2325 enclosed-exceptions,
@@ -49,9 +51,11 @@ library
4951 time,
5052 universe,
5153 unordered-containers,
54+ vessel,
5255 websockets,
5356 websockets-snap
5457 exposed-modules:
58+ Data.Aeson.GADT
5559 Data.MonoidMap
5660 Obelisk.Api
5761 Obelisk.Api.Pipeline
lib/backend/src/Data/Aeson/GADT.hsnew file+184
@@ -0,0 +1,184 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE ExistentialQuantification #-}
4+{-# LANGUAGE GADTs #-}
5+{-# LANGUAGE LambdaCase #-}
6+{-# LANGUAGE RankNTypes #-}
7+{-# LANGUAGE FlexibleContexts #-}
8+{-# LANGUAGE FlexibleInstances #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE StandaloneDeriving #-}
11+{-# LANGUAGE TypeApplications #-}
12+{-# LANGUAGE TypeFamilies #-}
13+{-# LANGUAGE TypeOperators #-}
14+{-# LANGUAGE UndecidableInstances #-}
15+{-# LANGUAGE QuasiQuotes #-}
16+{-# LANGUAGE TemplateHaskell #-}
17+{-# LANGUAGE PatternGuards #-}
18+
19+{-# OPTIONS_GHC -fno-warn-orphans #-}
20+
21+module Data.Aeson.GADT where
22+
23+import Control.Monad
24+import Data.Aeson
25+import Data.Some as Some
26+import Data.Constraint.Forall
27+import Data.GADT.Compare
28+import Language.Haskell.TH
29+import Data.Functor.Classes
30+
31+import Data.Constraint.Extras
32+import Data.Dependent.Sum
33+import Data.Dependent.Map (DMap)
34+import qualified Data.Dependent.Map as DMap
35+import Data.Functor.Identity
36+
37+import Data.Some (Some(..))
38+
39+decCons :: Dec -> [Con]
40+decCons = \case
41+ DataD _ _ _ _ cs _ -> cs
42+ NewtypeD _ _ _ _ c _ -> [c]
43+ _ -> error "undefined"
44+
45+conName :: Con -> Name
46+conName c = case c of
47+ NormalC n _ -> n
48+ RecC n _ -> n
49+ InfixC _ n _ -> n
50+ ForallC _ _ c' -> conName c'
51+ GadtC [n] _ _ -> n
52+ RecGadtC [n] _ _ -> n
53+ _ -> error "conName: GADT constructors with multiple names not yet supported"
54+
55+conArity :: Con -> Int
56+conArity c = case c of
57+ NormalC _ ts -> length ts
58+ RecC _ ts -> length ts
59+ InfixC _ _ _ -> 2
60+ ForallC _ _ c' -> conArity c'
61+ GadtC _ ts _ -> length ts
62+ RecGadtC _ ts _ -> length ts
63+
64+deriveJSONGADT :: Name -> DecsQ
65+deriveJSONGADT n = do
66+ tj <- deriveToJSONGADT n
67+ fj <- deriveFromJSONGADT n
68+ return (tj ++ fj)
69+
70+deriveToJSONGADT :: Name -> DecsQ
71+deriveToJSONGADT n = do
72+ x <- reify n
73+ let cons = case x of
74+ TyConI d -> decCons d
75+ _ -> error "undefined"
76+ [d|
77+ instance ToJSON ($(conT n) a) where
78+ toJSON r = $(caseE [|r|] $ map conMatchesToJSON cons)
79+ |]
80+
81+instance (ForallF ToJSON c) => ToJSON (Some c) where
82+ toJSON (Some.This (x :: c a)) = whichever @ToJSON @c @a (toJSON x)
83+
84+deriveFromJSONGADT :: Name -> DecsQ
85+deriveFromJSONGADT n = do
86+ x <- reify n
87+ let cons = case x of
88+ TyConI d -> decCons d
89+ _ -> error "undefined"
90+ let wild = match wildP (normalB [|fail "deriveFromJSONGADT: Supposedly-complete GADT pattern match fell through in generated code. This shouldn't happen."|]) []
91+ [d|
92+ instance FromJSON (Some $(conT n)) where
93+ parseJSON v = do
94+ (tag', v') <- parseJSON v
95+ $(caseE [|tag' :: String|] $ map (conMatchesParseJSON [|v'|]) cons ++ [wild])
96+ |]
97+
98+deriveEqTag :: Name -> DecsQ
99+deriveEqTag n = do
100+ x <- reify n
101+ let cons = case x of
102+ TyConI d -> decCons d
103+ _ -> error "undefined"
104+ [d|
105+ instance Eq1 f => EqTag $(conT n) f where
106+ eqTagged a b = $(caseE [|(a, b)|] $ concatMap conMatchesEqTagged cons)
107+ |]
108+
109+-- | Generate all required matches (and some redundant ones...) for `eqTagged`
110+-- for some constructor
111+conMatchesEqTagged :: Con -> [MatchQ]
112+conMatchesEqTagged c = case c of
113+ ForallC _ _ c' -> conMatchesEqTagged c'
114+ GadtC _ tys _ -> forTypes (map snd tys)
115+ _ -> error "conMatchesEqTagged: Unmatched constructor type"
116+ where
117+ name = conName c
118+ forTypes ts =
119+ [ do
120+ as <- mapM (\_ -> newName "a") ts
121+ bs <- mapM (\_ -> newName "b") ts
122+ x <- newName "x"
123+ y <- newName "y"
124+ let compareTagFields = foldr (\(a, b) e -> [| $(varE a) == $(varE b) && $(e) |]) [| True |] (zip as bs)
125+ match
126+ (tupP [conP name (map varP as), conP name (map varP bs)])
127+ (normalB (lamE [varP x, varP y] [| $(compareTagFields) && eq1 $(varE x) $(varE y) |] ))
128+ []
129+ , match
130+ (tupP [conP name (map (const wildP) ts), wildP])
131+ (normalB [| \ _ _ -> False |])
132+ []
133+ ]
134+
135+-- | Implementation of 'toJSON'
136+conMatchesToJSON :: Con -> MatchQ
137+conMatchesToJSON c = do
138+ let name = conName c
139+ base = nameBase name
140+ toJSONExp e = [| toJSON $(e) |]
141+ vars <- replicateM (conArity c) (newName "x")
142+ let body = toJSONExp $ tupE [ [| base :: String |] , tupE $ map (toJSONExp . varE) vars ]
143+ match (conP name (map varP vars)) (normalB body) []
144+
145+
146+-- | Implementation of 'parseJSON'
147+conMatchesParseJSON :: ExpQ -> Con -> MatchQ
148+conMatchesParseJSON e c = do
149+ let name = conName c
150+ match' = match (litP (StringL (nameBase name)))
151+ vars <- replicateM (conArity c) (newName "x")
152+ let forTypes _ = do
153+ let pat = tupP (map varP vars)
154+ conApp = foldl appE (conE name) (map varE vars)
155+ body = doE [ bindS pat [| parseJSON $e |]
156+ , noBindS [| return (This $conApp) |]
157+ ]
158+ match' (normalB body) []
159+ case c of
160+ ForallC _ _ c' -> conMatchesParseJSON e c'
161+ GadtC _ tys _ -> forTypes (map snd tys)
162+ NormalC _ tys -> forTypes (map snd tys)
163+ _ -> error "conMatchesParseJSON: Unmatched constructor type"
164+
165+type ToJSONFactor f g = (Has' ToJSON f g, ForallF ToJSON f)
166+type FromJSONFactor f g = (FromJSON (Some f), GCompare f, Has' FromJSON f g)
167+
168+type JSONFactor f = (ToJSONFactor f Identity, FromJSONFactor f Identity)
169+
170+instance ToJSONFactor f g => ToJSON (DSum f g) where
171+ toJSON ((f :: f a) :=> x) = toJSON (whichever @ToJSON @f @a (toJSON f), has' @ToJSON @g f (toJSON x))
172+
173+instance FromJSONFactor f g => FromJSON (DSum f g) where
174+ parseJSON x = do
175+ (tag, val) <- parseJSON x
176+ Some.This (parsedTag :: f a) <- parseJSON tag
177+ val' <- has' @FromJSON @g parsedTag (parseJSON val)
178+ return $ parsedTag :=> val'
179+
180+instance ToJSONFactor f g => ToJSON (DMap f g) where
181+ toJSON = toJSON . DMap.toList
182+
183+instance FromJSONFactor f g => FromJSON (DMap f g) where
184+ parseJSON = fmap DMap.fromList . parseJSON
lib/backend/src/Obelisk/Api.hs+26−95
@@ -1,3 +1,5 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE StandaloneDeriving #-}
13{-# LANGUAGE GADTs #-}
24{-# LANGUAGE GeneralizedNewtypeDeriving #-}
35{-# LANGUAGE LambdaCase #-}
@@ -24,69 +26,20 @@ import Control.Monad (forever)
2426import Control.Monad.Reader
2527import Control.Monad.Trans (lift)
2628import Data.Aeson (FromJSON, ToJSON)
29+import Data.Align
2730import Data.Attoparsec.ByteString
2831import Data.ByteString (ByteString)
2932import Data.Default
30−import Data.Map.Monoidal (MonoidalMap)
31−import Data.MonoidMap (MonoidMap (..))
33+import Data.Functor.Compose
34+import Data.Map.Monoidal (MonoidalMap (..))
3235import Data.Pool (withResource)
36+import Data.Vessel
3337import qualified Database.PostgreSQL.Simple as PG
3438import qualified Database.PostgreSQL.Simple.Transaction as PG
35−import Reflex (FunctorMaybe (..))
3639import Reflex.Patch (Group)
3740import Reflex.Query.Class (QueryResult, SelectedCount (..))
3841import Snap.Core (Snap)
3942
40−{-
41−-- | A way for a pipeline to retrieve data
42−newtype QueryHandler q m = QueryHandler
43− { runQueryHandler :: q -> m (QueryResult q) }
44−
45−-- | A way to push data into a pipeline
46−newtype Recipient q m = Recipient
47− { tellRecipient :: QueryResult q -> m () }
48−
49−-- | A way of attaching to (and later detaching from) a pipeline
50−newtype Registrar q = Registrar { unRegistrar :: Recipient q IO -> IO (QueryHandler q IO, IO ()) }
51−
52−-- | A way of connecting a source of data to a consumer of data. The consumer
53−-- can pull data from the datasource and the datasource can push data to the
54−-- consumer.
55−--
56−-- q is the consumer side
57−-- q' is the datasource side
58−newtype Pipeline m q q' = Pipeline { unPipeline :: QueryHandler q' m -> Recipient q m -> IO (QueryHandler q m, Recipient q' m) }
59−
60−instance Category (Pipeline m) where
61− id = Pipeline $ \qh r -> return (qh, r)
62− Pipeline f . Pipeline g = Pipeline $ \qh r -> do
63− rec (qh'', r') <- g qh' r
64− (qh', r'') <- f qh r'
65− return (qh'', r'')
66−
67−-- | A key used to track particular consumers
68−newtype ClientKey = ClientKey { unClientKey :: Integer }
69− deriving (Eq, Ord, Show)
70−
71−servePostgres
72− :: ( Monoid q'
73− , Semigroup q'
74− )
75− => Pool Postgresql -- ^ The DB to connect to
76− -> (forall a. request a -> write a)
77− -> (ByteString -> q' -> read (QueryResult q'))
78− -> (q' -> read (QueryResult q))
79− -> Pipeline IO q q'
80− -> (Snap () -> IO a)
81− -> IO a
82−servePostgres db handleApi handleNotify handleQuery pipe = do
83− (getNextNotification, finalizeListener) <- startNotificationListener db
84− rec (qh, finalizeFeed) <- feedPipeline (handleNotify <$> getNextNotification) handleQuery r
85− (qh', r) <- unPipeline pipe qh r'
86− (r', handleListen) <- connectPipelineToWebsockets "" (RequestHandler handleApi) qh'
87− return (handleListen, finalizeFeed >> finalizeListener)
88−-}
89−
9043newtype ReadDb a = ReadDb { unReadDb :: ReaderT PG.Connection IO a }
9144 deriving (Functor, Applicative, Monad)
9245
@@ -114,43 +67,39 @@ unsafeWriteDb :: (PG.Connection -> IO a) -> WriteDb a
11467unsafeWriteDb f = WriteDb $ lift . f =<< ask
11568
11669withApiHandler
117− :: forall query queryResult request a.
118− ( Monoid (query SelectedCount)
119− , QueryResult (query (MonoidMap ClientKey SelectedCount)) ~ queryResult (MonoidMap ClientKey SelectedCount)
120− , QueryResult (query SelectedCount) ~ queryResult SelectedCount
121− , QueryResult (query ()) ~ queryResult ()
122− , Foldable queryResult
123− , Functor query
124− , Functor queryResult
125− , FunctorMaybe queryResult
126− , Monoid (query (MonoidMap ClientKey SelectedCount))
127− , Monoid (queryResult SelectedCount)
128− , Eq (queryResult SelectedCount)
129− , Eq (query (MonoidMap ClientKey SelectedCount))
130− , Eq (query SelectedCount)
131− , Group (query SelectedCount)
132− , ToJSON (queryResult ())
133− , FromJSON (query ())
70+ :: forall v query q0 request a.
71+ ( View v
72+ , Group (v (Const SelectedCount))
73+ , query ~ v (Compose (MonoidalMap ClientKey) (Const SelectedCount))
74+ , q0 ~ v Proxy
75+ , QueryResult query ~ v (Compose (MonoidalMap ClientKey) Identity)
76+ , QueryResult (v (Const SelectedCount)) ~ v Identity
77+ , Monoid (v (Compose (MonoidalMap ClientKey) (Const SelectedCount)))
78+ , Monoid (v Identity)
79+ , ToJSON (v Identity)
80+ , FromJSON (v Proxy)
13481 , Request request
13582 )
13683 => ByteString -- ^ The URI of the DB to connect to
13784 -> (forall x. request x -> WriteDb x) -- ^ How to handle an API request
138− -> (forall x. query x -> Transaction -> ReadDb (QueryResult (query x))) -- ^ How to handle a change
139− -> (forall x. query x -> ReadDb (QueryResult (query x))) -- ^ How to handle a query
85+ -> (v Proxy -> Transaction -> ReadDb (v Identity)) -- ^ How to handle a change
86+ -> (v Proxy -> ReadDb (v Identity)) -- ^ How to handle a query
14087 -> (Snap () -> IO a)
14188 -> IO a
14289withApiHandler dbUri handleApi handleChange handleQuery go = do
14390 withNonEmptyTransactions dbUri $ \transactions -> do
14491 withConnectionPool dbUri $ \db -> do
145− let onChange :: Transaction -> query x -> IO (QueryResult (query x))
146− onChange m q = withResource db $ \conn -> readTransaction conn $ handleChange q m
147− let pipe :: Pipeline IO (MonoidalMap ClientKey (query SelectedCount)) (query (MonoidMap ClientKey SelectedCount))
148− pipe = queryMorphismPipeline $ transposeMonoidMap . monoidMapQueryMorphism
149− rec (qh, finalizeFeed) <- feedPipeline (onChange <$> atomically (readTChan transactions)) (QueryHandler $ \q -> withResource db $ \conn -> readTransaction conn $ handleQuery q) r
92+ let onChange :: Transaction -> v (Compose (MonoidalMap ClientKey) (Const SelectedCount)) -> IO (v (Compose (MonoidalMap ClientKey) Identity))
93+ onChange txn q = withResource db $ \conn -> readTransaction conn $ mapDecomposedV (flip handleChange txn) q
94+ let pipe :: Pipeline IO (MonoidalMap ClientKey (v (Const SelectedCount))) (v (Compose (MonoidalMap ClientKey) (Const SelectedCount)))
95+ pipe = queryMorphismPipeline transposeView
96+ rec (qh, finalizeFeed) <- feedPipeline (onChange <$> atomically (readTChan transactions)) (QueryHandler $ \q -> withResource db $ \conn -> readTransaction conn $ mapDecomposedV handleQuery q) r
15097 (qh', r) <- unPipeline pipe qh r'
15198 (r', handleListen) <- connectPipelineToWebsockets "" (\r'' -> withResource db $ \conn -> writeTransaction conn $ handleApi r'') qh'
15299 go handleListen
153100
101+deriving instance Ord k => Align (MonoidalMap k)
102+
154103withNonEmptyTransactions :: ByteString -> (TChan Transaction -> IO a) -> IO a
155104withNonEmptyTransactions dbUri go = do
156105 let opts = def
@@ -167,21 +116,3 @@ withNonEmptyTransactions dbUri go = do
167116 atomically $ writeTChan transactions transaction
168117 withAsync processLines $ \a -> link a >> do
169118 go transactions
170−{-
171−
172−standardChangeChannel :: PG.Identifier
173−standardChangeChannel = "obelisk_api_updates"
174−
175−standardChangeSubscriberTable :: PG.Identifier
176−standardChangeSubscriberTable = "obelisk_api_updates_subscriber"
177−
178−standardChangeSpillTable :: PG.Identifier
179−standardChangeSpillTable = "obelisk_api_updates_spill"
180−
181−-- | Listen on a Postgres notification channel. Uses a connection from the Pool for as long as it is r-- | Listen for Postgresql changes on a particular channel
182−withChangeListener :: FromJSON change => Pool PG.Connection -> (change -> IO ()) -> IO a -> IO a
183−withChangeListener db onChange = withBigNotificationListener db standardChangeChannel standardChangeSpillTable $ \message ->
184− case Aeson.eitherDecode' $ LBS.fromStrict message of
185− Left e -> fail $ "withChangeListener: " <> e
186− Right change -> onChange change
187−-}
lib/backend/src/Obelisk/Api/Pipeline.hs+44−43
@@ -1,4 +1,6 @@
11{-# LANGUAGE RecursiveDo #-}
2+{-# LANGUAGE TypeApplications #-}
3+{-# LANGUAGE KindSignatures #-}
24{-# LANGUAGE GADTs #-}
35{-# LANGUAGE FlexibleContexts #-}
46{-# LANGUAGE UndecidableInstances #-}
@@ -9,8 +11,12 @@ module Obelisk.Api.Pipeline where
911
1012import Prelude hiding (id, (.))
1113
12−import Obelisk.Request (Request, SomeRequest (..))
14+import Obelisk.Request (Request)
1315
16+import Data.Vessel
17+import Data.Constraint.Extras
18+import Data.Some (Some)
19+import qualified Data.Some as Some
1420import Control.Concurrent
1521import Control.Monad
1622import Control.Monad.State.Strict
@@ -152,8 +158,8 @@ data WebSocketRequest q r
152158 | WebSocketRequest_Api (TaggedRequest r)
153159 deriving (Typeable, Generic)
154160
155−instance (Request r, FromJSON q) => FromJSON (WebSocketRequest q r)
156−instance (Request r, ToJSON q) => ToJSON (WebSocketRequest q r)
161+instance (FromJSON q, FromJSON (Some r)) => FromJSON (WebSocketRequest q r)
162+instance (ToJSON q, ToJSON (Some r)) => ToJSON (WebSocketRequest q r)
157163
158164-- | Represents a WebSocket response from one of three channels: incoming 'View's, API responses, or version info
159165data WebSocketResponse q
@@ -166,11 +172,11 @@ instance FromJSON (QueryResult q) => FromJSON (WebSocketResponse q)
166172instance ToJSON (QueryResult q) => ToJSON (WebSocketResponse q)
167173
168174-- | A request tagged with an identifier
169−data TaggedRequest r = TaggedRequest Value (SomeRequest r)
175+data TaggedRequest r = TaggedRequest Value (Some r)
170176 deriving (Typeable, Generic)
171177
172−instance Request r => FromJSON (TaggedRequest r)
173−instance Request r => ToJSON (TaggedRequest r)
178+instance FromJSON (Some r) => FromJSON (TaggedRequest r)
179+instance ToJSON (Some r) => ToJSON (TaggedRequest r)
174180
175181-- | A response tagged with an identifier matching the one in the 'TaggedRequest'. The identifier is the first argument.
176182data TaggedResponse = TaggedResponse Value Value
@@ -181,42 +187,39 @@ instance ToJSON TaggedResponse
181187
182188-- | Handles a websocket connection
183189handleWebsocket
184− :: forall q qr r.
185− ( Eq (q SelectedCount)
186− , Eq (qr SelectedCount)
187− , QueryResult (q SelectedCount) ~ qr SelectedCount
188− , QueryResult (q ()) ~ qr ()
189− , ToJSON (qr ())
190− , Functor qr
191− , Group (q SelectedCount)
192− , Monoid (qr SelectedCount)
193− , FromJSON (q ())
194− , Request r
195− , Functor q
190+ :: forall (v :: (* -> *) -> *) q q0 req.
191+ ( View v
192+ , Group (v (Const SelectedCount))
193+ , q ~ v (Const SelectedCount)
194+ , q0 ~ v Proxy
195+ , QueryResult q ~ v Identity
196+ , ToJSON (v Identity)
197+ , FromJSON (v Proxy)
198+ , Request req
196199 )
197200 => Text -- ^ Version
198− -> (forall a. r a -> IO a)
199− -> Registrar (q SelectedCount)
201+ -> (forall a. req a -> IO a)
202+ -> Registrar q
200203 -> Snap ()
201204handleWebsocket v rh register = withWebsocketsConnection $ \conn -> do
202− let sender = Recipient $ sendEncodedDataMessage conn . (\a -> WebSocketResponse_View (void a) :: WebSocketResponse (q ()))
203− sendEncodedDataMessage conn (WebSocketResponse_Version v :: WebSocketResponse (q ()))
205+ let sender = Recipient $ sendEncodedDataMessage conn . (\a -> WebSocketResponse_View a :: WebSocketResponse q)
206+ sendEncodedDataMessage conn (WebSocketResponse_Version v :: WebSocketResponse q)
204207 bracket (unRegistrar register sender) snd $ \(vsHandler, _) -> flip evalStateT mempty $ forever $ do
205− (wsr :: WebSocketRequest (q ()) r) <- liftIO $ getDataMessage conn
208+ (wsr :: WebSocketRequest q0 r) <- liftIO $ getDataMessage conn
206209 case wsr of
207− WebSocketRequest_Api (TaggedRequest reqId (SomeRequest req)) -> lift $ do
210+ WebSocketRequest_Api (TaggedRequest reqId (Some.This req)) -> lift $ do
208211 a <- rh req
209212 sendEncodedDataMessage conn
210− (WebSocketResponse_Api $ TaggedResponse reqId (toJSON a) :: WebSocketResponse (q ()))
213+ (WebSocketResponse_Api $ TaggedResponse reqId (has @ToJSON req (toJSON a)) :: WebSocketResponse q)
211214 WebSocketRequest_ViewSelector new -> do
212215 old <- get
213− let new' = SelectedCount 1 <$ new
216+ let new' = mapV (const (Const (SelectedCount 1))) new
214217 vsDiff = new' ~~ old
215− when (vsDiff /= mempty) $ do
218+ when (not (nullV vsDiff)) $ do
216219 qr <- lift $ runQueryHandler vsHandler vsDiff
217220 put new'
218− when (qr /= mempty) $ lift $
219− sendEncodedDataMessage conn (WebSocketResponse_View (void qr) :: WebSocketResponse (q ()))
221+ when (not (nullV qr)) $ lift $
222+ sendEncodedDataMessage conn (WebSocketResponse_View qr :: WebSocketResponse q)
220223
221224-------------------------------------------------------------------------------
222225
@@ -249,24 +252,22 @@ feedPipeline getNextNotification qh r = do
249252
250253-- | Connects the pipeline to websockets consumers
251254connectPipelineToWebsockets
252− :: ( Eq (q SelectedCount)
253− , QueryResult (q SelectedCount) ~ qr SelectedCount
254− , QueryResult (q ()) ~ qr ()
255− , Group (q SelectedCount)
256− , Monoid (qr SelectedCount)
257− , Eq (qr SelectedCount)
258− , ToJSON (qr ())
259− , FromJSON (q ())
260− , Request r
261− , Functor qr
262− , Functor q
255+ :: forall v req q.
256+ ( View v
257+ , q ~ v (Const SelectedCount)
258+ , QueryResult q ~ v Identity
259+ , Group q
260+ , Monoid (v Identity)
261+ , ToJSON (v Identity)
262+ , FromJSON (v Proxy)
263+ , Request req
263264 )
264265 => Text
265− -> (forall a. r a -> IO a)
266+ -> (forall a. req a -> IO a)
266267 -- ^ API handler
267− -> QueryHandler (MonoidalMap ClientKey (q SelectedCount)) IO
268+ -> QueryHandler (MonoidalMap ClientKey q) IO
268269 -- ^ A way to retrieve more data for each consumer
269− -> IO (Recipient (MonoidalMap ClientKey (q SelectedCount)) IO, Snap ())
270+ -> IO (Recipient (MonoidalMap ClientKey q) IO, Snap ())
270271 -- ^ A way to send data to many consumers and a handler for websockets connections
271272connectPipelineToWebsockets ver rh qh = do
272273 (allRecipients, registerRecipient) <- connectPipelineToWebsockets' qh
lib/request/obelisk-request.cabal+2−1
@@ -8,11 +8,12 @@ library
88 build-depends: base,
99 aeson,
1010 constraints,
11+ constraints-extras,
12+ dependent-sum,
1113 template-haskell,
1214 vector
1315 exposed-modules:
1416 Obelisk.Request
15− Obelisk.Request.TH
1617 other-modules:
1718 Obelisk.Request.HList
1819 ghc-options: -Wall
lib/request/src/Obelisk/Request.hs+7−19
@@ -1,25 +1,13 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
13{-# LANGUAGE GADTs #-}
24
35module Obelisk.Request
4− ( Request (..)
5− , SomeRequest (..)
6+ ( Request
67 ) where
78
8−import Data.Aeson (FromJSON, ToJSON, Value, parseJSON, toJSON)
9−import Data.Aeson.Types (Parser)
10−import Data.Constraint (Dict)
9+import Data.Aeson (FromJSON, ToJSON)
10+import Data.Constraint.Extras
11+import Data.Some (Some)
1112
12−class Request r where
13− requestToJSON :: r a -> Value
14− requestParseJSON :: Value -> Parser (SomeRequest r)
15− requestResponseToJSON :: r a -> Dict (ToJSON a)
16− requestResponseFromJSON :: r a -> Dict (FromJSON a)
17−
18−data SomeRequest t where
19− SomeRequest :: (FromJSON x, ToJSON x) => t x -> SomeRequest t
20−
21−instance Request r => FromJSON (SomeRequest r) where
22− parseJSON = requestParseJSON
23−
24−instance Request r => ToJSON (SomeRequest r) where
25− toJSON (SomeRequest r) = requestToJSON r
13+type Request r = (FromJSON (Some r), ToJSON (Some r), Has FromJSON r, Has ToJSON r) --TODO: shouldn't Has FromJSON r imply FromJSON (Some r) ?
skeleton/backend/src/Backend.hs+98−7
@@ -1,4 +1,6 @@
11{-# LANGUAGE EmptyCase #-}
2+{-# LANGUAGE PolyKinds #-}
3+{-# LANGUAGE DataKinds #-}
24{-# LANGUAGE StandaloneDeriving #-}
35{-# LANGUAGE TypeOperators #-}
46{-# LANGUAGE UndecidableInstances #-}
@@ -23,6 +25,14 @@
2325{-# OPTIONS_GHC -fno-warn-orphans #-} --TODO: Eliminate this
2426module Backend where
2527
28+import Prelude hiding (id, (.))
29+
30+import Control.Category
31+import Control.Category.Monoidal (Monoidal)
32+import Control.Category.Braided
33+import Control.Categorical.Bifunctor
34+import Control.Category.Associative
35+import Control.Category.Cartesian
2636import Control.Exception
2737import Control.Applicative
2838import Control.Concurrent
@@ -31,10 +41,12 @@ import Common.Schema
3141import Backend.Schema
3242import Common.Route
3343import Data.Foldable
44+import Data.Functor.Misc
3445import qualified Data.Map.Monoidal as Map
3546import Data.Maybe
3647import Data.Pool
3748import Data.Semigroup
49+import Data.Vessel
3850import Obelisk.Backend
3951import Obelisk.Route
4052import Obelisk.Api
@@ -65,7 +77,7 @@ import Reflex.Patch.MapWithMove2
6577
6678import Data.Proxy
6779
68−import Control.Lens
80+import Control.Lens hiding (Bifunctor, bimap)
6981import qualified GHC.Generics as Generic
7082import GHC.Generics hiding (R, C)
7183import GHC.Types
@@ -83,19 +95,19 @@ backend = Backend
8395 print e
8496 forever $ threadDelay maxBound --TODO: Probably not necessary once we have `ob psql`
8597 Right _ -> pure ()
86− let onNotify :: forall a. VS (Id Task) Task a -> Transaction -> ReadDb (V (Id Task) Task a)
87− onNotify (VS requested) txn = do
98+ let onNotify :: VS (Id Task) (First Task) Proxy -> Transaction -> ReadDb (V (Id Task) (First Task) Identity)
99+ onNotify (MapV requested) txn = do
88100 decoded <- ReadDb $ do
89101 conn <- ask
90102 lift $ decodeTransaction conn txn
91103 traceM $ show $ _db_tasks decoded
92104 traceM $ show $ _db_users decoded
93105 tasks <- runSelectReturningList' $ select $ all_ $ _db_tasks db
94− pure $ V $ Map.intersectionWith (,) requested $ Map.fromList $ fmap (\x -> (TaskId $ _task_id x, First $ Just x)) tasks
95− onSubscribe :: forall a. VS (Id Task) Task a -> ReadDb (V (Id Task) Task a)
96− onSubscribe (VS requested) = do
106+ pure $ MapV $ Map.fromList $ fmap (\x -> (TaskId $ _task_id x, Identity $ First x)) tasks
107+ onSubscribe :: VS (Id Task) (First Task) Proxy -> ReadDb (V (Id Task) (First Task) Identity)
108+ onSubscribe (MapV requested) = do
97109 tasks <- runSelectReturningList' $ select $ all_ $ _db_tasks db
98− pure $ V $ Map.intersectionWith (,) requested $ Map.fromList $ fmap (\x -> (TaskId $ _task_id x, First $ Just x)) tasks
110+ pure $ MapV $ Map.fromList $ fmap (\x -> (TaskId $ _task_id x, Identity $ First x)) tasks
99111 onRequest :: forall a. MyRequest a -> WriteDb a
100112 onRequest = \case
101113 MyRequest_Add a -> do
@@ -113,6 +125,80 @@ backend = Backend
113125 , _backend_routeEncoder = backendRouteEncoder
114126 }
115127
128+class CoCartesian' (k :: o -> o -> *) where
129+ type Sum' k :: (o -> *) -> o
130+ inSum :: tag x -> x `k` Sum' k tag
131+ outSum :: (forall x. tag x -> x `k` a) -> Sum' k tag `k` a
132+
133+class Cartesian' (k :: o -> o -> *) where
134+ type Product' k :: (o -> *) -> o
135+ outProd :: field x -> Product' k field `k` x
136+ inProd :: (forall x. field x -> a `k` x) -> a `k` Product' k field
137+ inProd' :: forall (field :: o -> *) (a :: o). (Product' (->) (WrapArg field (k a))) -> a `k` Product' k field
138+
139+newtype Product'Hask field = Product'Hask (forall x. field x -> x)
140+
141+instance Cartesian' (->) where
142+ type Product' (->) = Product'Hask
143+
144+{-
145+type MyAppView = CrudEverything MyAppDb
146+-}
147+
148+{-
149+crudEverything :: IV (PostgresDb a) (a CrudIF, someOtherIF)
150+crudEverything = (distributeIV $ MyDB
151+ { _myDb_thing = crudIV :: IV (TableEntity ThingT) (CrudIF Thing)
152+ }) &&& someOtherIV
153+
154+watch $ (mempty { _myDb_thing = crudPage 10 0 }, mempty)
155+-}
156+
157+{-
158+newtype PatchTarget' p = PatchTarget' (PatchTarget p)
159+
160+data Server a b = forall state. Server
161+ { _server_subscribe :: b Proxy -> ReadDb (b (Product state PatchTarget')) -- Output 'b' must have same shape as input
162+ , _server_notify :: Transaction -> b state -> ReadDb (b (Product state Identity)) -- Output state must have same shape as input state; output `b Identity` must be no larger than input `b State`, and should have size approximately proportional to the Transaction size. Output `b State` should generally not be fully traversed.
163+ , _server_request :: forall x. request x -> WriteDb x
164+ }
165+-}
166+
167+{-
168+data Delta x
169+
170+data IF query view request response
171+
172+data DBIn query request = (Event (Delta query), Event request)
173+
174+data DBOut view response = (Event (Partial view), Event (Delta view), Event response)
175+
176+--data Database query view request response
177+-- Such that: every Delta query triggers a Partial view; every request (by anyone) triggers a Delta view; every request (by us) triggers a response
178+database :: DBIn -> m DBOut
179+
180+times :: DBIn -> DBIn -> m (DBOut, DBOut)
181+
182+item :: DBOut dbView dbResponse -> DBIn appQuery appResponse -> m (DBIn dbQuery dbRequest, DBOut appView appResponse)
183+
184+data IV dbquery db dbreq request query result = forall state. IV
185+ { _iv_notify
186+ :: query
187+ -> Delta db --TODO: Should this be NonEmptyDelta, where NonEmptyDelta (a, b) = These (NonEmptyDelta a) (NonEmptyDelta b)
188+ -> state
189+ -> (state, Delta result)
190+ , _iv_subscribe :: Delta query -> db -> (state, result)
191+ , _iv_request :: request -> db -> (dbreq, a)
192+ }
193+
194+timesIV :: IV db request query result -> IV db2 request2 query2 result2 -> IV (db, db2) (Either request request2) (query, query2) (result, result2)
195+timesIV (IV n s {- r -}) (IV n2 s2 {- r2 -}) = IV
196+ (\(q, q2) (ddb, ddb2) (s, s2) -> _ (n q ddb s, n2 q2 ddb2 s2))
197+ _
198+
199+composeIV :: IV result (Delta db) query2 result2 -> IV db request query result
200+-}
201+
116202class MonadBeamRead m where
117203 runSelectReturningList' :: FromBackendRow Postgres a => SqlSelect Postgres a -> m [a]
118204 runSelectReturningOne' :: FromBackendRow Postgres a => SqlSelect Postgres a -> m (Maybe a)
@@ -296,6 +382,11 @@ instance Beamable tbl => Patch (PartialUpdate tbl) where
296382 type PatchTarget (PartialUpdate tbl) = tbl Identity
297383 apply (PartialUpdate new) old = zipBeamFieldsM (\(Columnar' newField) (Columnar' oldField) -> pure $ Columnar' $ fromMaybe oldField newField) new old
298384
385+availableValues :: PatchMapWithMove2 k p -> OldMap.Map k (PatchTarget p)
386+availableValues (PatchMapWithMove2 m) = flip OldMap.mapMaybe m $ \case
387+ NodeInfo { _nodeInfo_from = From_Insert v } -> Just v
388+ _ -> Nothing
389+
299390decodeChange
300391 :: forall tbl
301392 . Table tbl
skeleton/common/src/Common/Api.hs+27−22
@@ -1,50 +1,55 @@
1−{-# LANGUAGE EmptyCase #-}
2−{-# LANGUAGE TemplateHaskell #-}
3−{-# LANGUAGE GADTs #-}
4−{-# LANGUAGE TupleSections #-}
5−{-# LANGUAGE DeriveFunctor #-}
61{-# LANGUAGE DeriveFoldable #-}
2+{-# LANGUAGE DeriveFunctor #-}
73{-# LANGUAGE DeriveTraversable #-}
8−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9−{-# LANGUAGE TypeApplications #-}
10−{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE EmptyCase #-}
115{-# LANGUAGE FlexibleContexts #-}
126{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE GADTs #-}
8+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
139{-# LANGUAGE LambdaCase #-}
1410{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE PolyKinds #-}
1512{-# LANGUAGE RankNTypes #-}
13+{-# LANGUAGE ScopedTypeVariables #-}
14+{-# LANGUAGE TemplateHaskell #-}
15+{-# LANGUAGE TupleSections #-}
16+{-# LANGUAGE TypeApplications #-}
1617{-# LANGUAGE TypeFamilies #-}
1718module Common.Api where
1819
1920import Common.Schema
2021import Data.Aeson
22+import Data.Aeson.GADT
2123import Data.AppendMap ()
24+import Data.Constraint.Extras.TH
2225import Data.Functor.Identity
2326import Data.Map.Monoidal (MonoidalMap (..))
2427import qualified Data.Map.Monoidal as Map
28+import Data.Vessel
2529import Data.Semigroup
2630import Data.Text (Text)
27−import Obelisk.Request.TH
31+import Data.Functor.Compose
2832import Reflex.Class
2933import Reflex.Query.Class
3034
31−newtype VS k v a = VS (MonoidalMap k a)
32− deriving (Semigroup, Monoid, Functor, Foldable, Traversable, Group, ToJSON, FromJSON, Eq)
33−
34−newtype V k v a = V (MonoidalMap k (a, First (Maybe v)))
35− deriving (Semigroup, Monoid, Functor, Foldable, Traversable, ToJSON, FromJSON, Eq)
36−
37−instance FunctorMaybe (V k v) where
38− fmapMaybe f (V m) = V $ Map.mapMaybe (\(a, v) -> (,v) <$> f a) m
35+type VS = MapV
36+-- ()
3937
40−instance (Ord k, Semigroup a) => Query (VS k v a) where
41− type QueryResult (VS k v a) = V k v a
42− crop _ = id
38+type V = MapV
39+-- PatchMapWithMove2 (Id Task) (TaskT Maybe)
4340
4441type family Id t where
4542 Id (f Identity) = PrimaryKey f Identity
4643
4744data MyRequest a where
48− MyRequest_Add :: Text -> MyRequest (Id Task)
45+ MyRequest_Add :: Text -> MyRequest (PrimaryKey TaskT Identity)
46+
47+--TODO: deriveArgDict REQUIRES PolyKinds; see if we can check for that in TH
48+deriveArgDict ''MyRequest
49+deriveJSONGADT ''MyRequest
50+
51+instance (Ord k, Ord k', Semigroup v) => Query (MapV (k :: *) (v :: *) (Compose (MonoidalMap k') (Const SelectedCount))) where
52+ type QueryResult (MapV k v (Compose (MonoidalMap k') (Const SelectedCount))) = MapV k v (Compose (MonoidalMap k') Identity)
4953
50−makeRequestForData ''MyRequest
54+instance (Ord k, Semigroup v) => Query (MapV (k :: *) (v :: *) (Const SelectedCount)) where
55+ type QueryResult (MapV k v (Const SelectedCount)) = MapV k v Identity