docs: add docs

This commit is contained in:
orion 2024-04-01 15:20:59 -05:00
parent 1e0f06d176
commit c0ec6dec53
Signed by: orion
GPG Key ID: 6D4165AE4C928719
6 changed files with 75 additions and 49 deletions

View File

@ -18,15 +18,43 @@ import Effect.Class (liftEffect)
import Effect.Exception (Error)
import Prim.Row (class Union)
type PoolT :: forall k. (k -> Type) -> k -> Type
type PoolT = ReaderT Pool
-- | Monad handling pool resource acquisition & release
-- |
-- | ```
-- | runPostgres
-- | {connectionString: "postgresql://postgres:postgres@localhost:5432"}
-- | $ session do
-- | exec_ "create table foo (bar int);"
-- | exec_ "insert into foo values (1);"
-- | res <- query "select * from foo"
-- | pure $ res == 1
-- | ```
-- |
-- | Is equivalent to:
-- | ```
-- | do
-- | pool <- liftEffect $ Pool.make {connectionString: "postgresql://postgres:postgres@localhost:5432"}
-- | finally (Pool.end pool) do
-- | client <- Pool.connect pool
-- | finally (liftEffect $ Pool.release pool client) do
-- | Client.exec_ "create table foo (bar int);" client
-- | Client.exec_ "insert into foo values (1);" client
-- | res <- Client.query "select * from foo" client
-- | pure $ res == 1
-- | ```
type PostgresT :: forall k. (k -> Type) -> k -> Type
type PostgresT = ReaderT Pool
type SessionT :: forall k. (k -> Type) -> k -> Type
type SessionT = ReaderT Client
-- | A monad representing a connected session to a database
class (MonadBracket Error Fiber m, MonadAff m, MonadReader Client m) <= MonadSession m where
-- | Executes a query and unmarshals the result into `r`
query :: forall q r. AsQuery q => FromRows r => q -> m r
-- | Executes a query and returns the number of rows affected
exec :: forall q. AsQuery q => q -> m Int
-- | Executes a query and discards the result
exec_ :: forall q. AsQuery q => q -> m Unit
instance (MonadBracket Error Fiber m, MonadAff m, MonadReader Client m) => MonadSession m where
@ -38,7 +66,9 @@ instance (MonadBracket Error Fiber m, MonadAff m, MonadReader Client m) => Monad
liftAff $ Client.exec q client
exec_ = void <<< exec
session :: forall m a. MonadBracket Error Fiber m => MonadAff m => MonadSession (SessionT m) => SessionT m a -> PoolT m a
-- | Lifts a session to `PostgresT`, releasing the client to the pool
-- | after execution.
session :: forall m a. MonadBracket Error Fiber m => MonadAff m => MonadSession (SessionT m) => SessionT m a -> PostgresT m a
session m = do
pool <- ask
let
@ -46,7 +76,12 @@ session m = do
rel _ c = liftEffect $ Pool.release pool c
lift $ bracket acq rel (runReaderT m)
transaction :: forall m a. MonadBracket Error Fiber m => MonadAff m => MonadSession (SessionT m) => SessionT m a -> PoolT m a
-- | Lifts a session to `PostgresT`, running the session
-- | in a transaction.
-- |
-- | If the session throws an error, the transaction will be
-- | rolled back and the error rethrown.
transaction :: forall m a. MonadBracket Error Fiber m => MonadAff m => MonadSession (SessionT m) => SessionT m a -> PostgresT m a
transaction m =
let
begin = void $ exec "begin;"
@ -55,10 +90,11 @@ transaction m =
in
session $ begin *> catchError commit rollback
runPool :: forall m a missing trash r e f. MonadBracket e f m => MonadAff m => Union r missing (Pool.Config trash) => Record r -> PoolT m a -> m a
runPool cfg m =
-- | Runs a `PostgresT` with a pool created with the provided config, invoking `Pool.end` afterwards.
runPostgres :: forall m a missing trash r e f. MonadBracket e f m => MonadAff m => Union r missing (Pool.Config trash) => Record r -> PostgresT m a -> m a
runPostgres cfg m =
let
acq = liftEffect $ Pool.make @r @missing @trash cfg
rel _ p = liftAff $ Pool.end p
in
bracket acq rel (runReaderT m)
bracket acq rel $ runReaderT m

View File

@ -6,14 +6,17 @@ import Data.Either (Either(..))
import Data.String.Regex (Regex)
import Data.String.Regex as Regex
import Data.String.Regex.Flags (RegexFlags(..))
import Data.Tuple.Nested (type (/\), (/\))
import Effect (Effect)
import Effect.Aff (Aff, bracket)
import Effect.Aff (Aff, bracket, makeAff)
import Effect.Aff.Postgres.Client (Client)
import Effect.Aff.Postgres.Client as Client
import Effect.Aff.Postgres.Pool (Pool)
import Effect.Aff.Postgres.Pool as Pool
import Effect.Class (liftEffect)
import Effect.Unsafe (unsafePerformEffect)
import Node.EventEmitter (EventHandle(..))
import Node.EventEmitter as EE
import Node.Path as Path
import Node.Process (cwd)
import Partial.Unsafe (unsafePartial)
@ -54,3 +57,21 @@ unsafeFromRight e = unsafePartial $ case e of Right b -> b
re :: String -> RegexFlags -> Regex
re s f = unsafeFromRight $ Regex.regex s f
class Curried a b | a -> b where
curried :: (b -> Effect Unit) -> a
instance Curried (Effect Unit) Unit where
curried f = f unit
instance Curried (a -> Effect Unit) a where
curried = identity
instance Curried (a -> b -> Effect Unit) (a /\ b) where
curried f = \a b -> f $ a /\ b
instance Curried (a -> b -> c -> Effect Unit) (a /\ b /\ c) where
curried f = \a b c -> f $ a /\ b /\ c
onceAff :: forall psCb jsCb emitter r. Curried psCb r => EventHandle emitter psCb jsCb -> emitter -> Aff r
onceAff ev em = makeAff \res -> mempty <* EE.once ev (curried (res <<< Right)) em

View File

@ -4,7 +4,7 @@ import Prelude
import Control.Monad.Error.Class (throwError)
import Control.Monad.Fork.Class (class MonadBracket, bracket)
import Control.Monad.Postgres (PoolT, exec_, query, runPool, session, transaction)
import Control.Monad.Postgres (PostgresT, exec_, query, runPostgres, session, transaction)
import Data.Array as Array
import Data.Array.NonEmpty as Array.NonEmpty
import Data.Maybe (fromJust, maybe)
@ -18,7 +18,7 @@ import Test.Common (re, withConfig)
import Test.Spec (Spec, around, describe, it)
import Test.Spec.Assertions (expectError, shouldEqual)
withTable :: forall a m. MonadBracket Error Fiber m => MonadAff m => String -> PoolT m a -> PoolT m a
withTable :: forall a m. MonadBracket Error Fiber m => MonadAff m => String -> PostgresT m a -> PostgresT m a
withTable s m =
let
tabname = unsafePartial fromJust $ join $ Array.index (maybe [] Array.NonEmpty.toArray $ Regex.match (re "create table (\\w+)" Regex.Flag.ignoreCase) s) 1
@ -28,21 +28,21 @@ withTable s m =
spec :: Spec Unit
spec =
around withConfig $ describe "Control.Monad.Postgres" do
it "empty works" \cfg -> runPool cfg $ pure unit
it "connects" \cfg -> runPool cfg do
it "empty works" \cfg -> runPostgres cfg $ pure unit
it "connects" \cfg -> runPostgres cfg do
act <- session $ query "select 1"
act `shouldEqual` 1
it "connects multiple" \cfg -> runPool cfg do
it "connects multiple" \cfg -> runPostgres cfg do
a <- session $ query "select 1"
b <- session $ query "select 2"
a `shouldEqual` 1
b `shouldEqual` 2
it "transaction commits" \cfg -> runPool cfg do
it "transaction commits" \cfg -> runPostgres cfg do
withTable "create table test_txn_commits (id int);" do
transaction $ exec_ "insert into test_txn_commits values (1);"
act <- session $ query "select * from test_txn_commits"
act `shouldEqual` [ 1 ]
it "transaction rolls back" \cfg -> runPool cfg do
it "transaction rolls back" \cfg -> runPostgres cfg do
withTable "create table test_txn_rolls_back (id int);" do
expectError $ transaction do
exec_ "insert into test_txn_rolls_back values (1);"

View File

@ -12,8 +12,7 @@ import Effect.Aff (forkAff, joinFiber)
import Effect.Aff.Postgres.Client (query)
import Effect.Aff.Postgres.Client as Client
import Effect.Exception as Error
import Test.Common (withClient)
import Test.Event (onceAff)
import Test.Common (onceAff, withClient)
import Test.Spec (Spec, around, describe, it)
import Test.Spec.Assertions (shouldEqual)
@ -60,4 +59,4 @@ spec =
describe "json" do
it "unmarshals" \c -> shouldEqual (JSON { foo: "bar" }) =<< query "select '{\"foo\": \"bar\"}' :: json" c
it "is string" \c -> shouldEqual "{\"foo\": \"bar\"}" =<< query "select '{\"foo\": \"bar\"}' :: json" c
it "array is string" \c -> shouldEqual [ [ "{\"foo\": \"bar\"}" ] ] =<< query "select array['{\"foo\": \"bar\"}' :: json]" c
it "array is string" \c -> shouldEqual [["{\"foo\": \"bar\"}"]] =<< query "select array['{\"foo\": \"bar\"}' :: json]" c

View File

@ -7,8 +7,7 @@ import Effect.Aff (finally, forkAff, joinFiber)
import Effect.Aff.Postgres.Client as Client
import Effect.Aff.Postgres.Pool as Pool
import Effect.Class (liftEffect)
import Test.Common (config, withPool)
import Test.Event (onceAff)
import Test.Common (config, onceAff, withPool)
import Test.Spec (Spec, around, describe, it)
import Test.Spec.Assertions (expectError, shouldEqual)

View File

@ -1,29 +0,0 @@
module Test.Event where
import Prelude
import Data.Either (Either(..))
import Data.Profunctor (lcmap)
import Data.Tuple.Nested (type (/\), uncurry2, (/\))
import Effect (Effect)
import Effect.Aff (Aff, makeAff)
import Node.EventEmitter (EventHandle(..))
import Node.EventEmitter as EE
class Curried a b | a -> b where
curried :: (b -> Effect Unit) -> a
instance Curried (Effect Unit) Unit where
curried f = f unit
instance Curried (a -> Effect Unit) a where
curried = identity
instance Curried (a -> b -> Effect Unit) (a /\ b) where
curried f = \a b -> f $ a /\ b
instance Curried (a -> b -> c -> Effect Unit) (a /\ b /\ c) where
curried f = \a b c -> f $ a /\ b /\ c
onceAff :: forall psCb jsCb emitter r. Curried psCb r => EventHandle emitter psCb jsCb -> emitter -> Aff r
onceAff ev em = makeAff \res -> mempty <* EE.once ev (curried (res <<< Right)) em