packages feed

kiroku-store-0.12.0.0: src/Kiroku/Store/Effect/Resource.hs

{-# LANGUAGE TypeFamilies #-}

module Kiroku.Store.Effect.Resource
  ( -- * The KirokuStoreResource effect
    KirokuStoreResource,

    -- * Operations
    getKirokuStore,

    -- * Bracket-style runner
    withKirokuStore,

    -- * Runner over an existing handle
    runKirokuStoreWith,
  )
where

import Effectful (Dispatch (..), DispatchOf, Eff, Effect, IOE, UnliftStrategy (..), withEffToIO, (:>))
import Effectful.Dispatch.Static (SideEffects (..), StaticRep, evalStaticRep, getStaticRep)
import Kiroku.Store.Connection (ConnectionSettings, KirokuStore, withStore)

-- ---------------------------------------------------------------------------
-- KirokuStoreResource static effect
-- ---------------------------------------------------------------------------

-- | Static effect carrying a 'KirokuStore' handle.
--
-- Static rather than dynamic because the store handle is acquired exactly
-- once per program and is not meant to be mocked — the dynamic mocking
-- surface lives on the 'Kiroku.Store.Effect.Store' effect that operates
-- against the handle. Splitting the resource (static) from the operations
-- (dynamic) keeps mocking ergonomic without requiring callers to swap the
-- handle itself.
--
-- @Static WithSideEffects@ rather than @Static NoSideEffects@ because the
-- underlying connection pool performs IO during operation lookup.
data KirokuStoreResource :: Effect

type instance DispatchOf KirokuStoreResource = Static WithSideEffects

newtype instance StaticRep KirokuStoreResource = KirokuStoreResource KirokuStore

-- | Retrieve the 'KirokuStore' handle from the effect stack.
getKirokuStore :: (KirokuStoreResource :> es) => Eff es KirokuStore
getKirokuStore = do
  KirokuStoreResource store <- getStaticRep
  pure store

-- | Bracket-style runner: acquire the store, install the effect, run the action, release.
withKirokuStore ::
  (IOE :> es) =>
  ConnectionSettings ->
  Eff (KirokuStoreResource : es) a ->
  Eff es a
withKirokuStore settings action = withEffToIO SeqUnlift $ \unlift ->
  withStore settings $ \store ->
    unlift (evalStaticRep (KirokuStoreResource store) action)

-- | Install an /already acquired/ 'KirokuStore' into the effect stack.
--
-- Unlike 'withKirokuStore', this neither acquires nor releases: the caller owns
-- the handle's lifetime. Use it when a process has opened exactly one store (the
-- documented arrangement) and needs to run several independent effectful actions
-- against it. Acquiring a store per action instead costs a connection pool, a
-- dedicated @LISTEN@ connection, and a publisher thread each time.
--
-- There is no bracket and no unlifting here, but @IOE@ is still required:
-- 'KirokuStoreResource' is @Static WithSideEffects@, and 'evalStaticRep' demands
-- @IOE@ for any effect declared that way.
runKirokuStoreWith ::
  (IOE :> es) =>
  KirokuStore ->
  Eff (KirokuStoreResource : es) a ->
  Eff es a
runKirokuStoreWith = evalStaticRep . KirokuStoreResource