# persistent-stm - STM transactions involving persistent storage [![CI](https://github.com/cdsmith/persistent-stm/actions/workflows/ci.yml/badge.svg)](https://github.com/cdsmith/persistent-stm/actions/workflows/ci.yml) [![Hackage](https://img.shields.io/hackage/v/persistent-stm)](https://hackage.haskell.org/package/persistent-stm) Haskell's `STM` monad implements composable transactions on in-memory state, offering atomicity, isolation, and consistency. However, they lack persistence, so changes are not durable at all. This package adds a limited form of persistence to the existing STM monad, allowing for transactions that include writing values durably to external storage. The persistence is limited in the following senses: * First, it is only suitable for use by a single process at a time. It is not possible to access the storage from multiple processes at the same time, *even* *if* some of those processes are merely readers. * While the view of data in memory is always consistent, the consistency of data on disk depends on the `Persistence` implementation, which you choose. The included implementation, `filePersistence`, does *not* guarantee that data will be in a consistent or readable state if the process is suddenly terminated with a power outage, system crash, etc. This can be fixed by using a `Persistence` implementation built on a transactional storage layer such as a database. The persistence essentially works as a key-value store. The key is a `String`, and the value can be of any type that implements `DBStorable`. The `DBStorable` class works like `Binary` or other serialization classes, except that it's designed to have access to the `DB` so that it can contain other `DBRef`s. This way, at runtime, you can maintain complex data structures that point directly to each other, but are persisted via their keys. # Quick Start A simple example of using `persistent-stm` follows: ```haskell import PersistentSTM.DB main :: IO () main = do persistence <- filePersistence "./my-data" withDB persistence $ \db -> do n <- atomically $ do ref <- getDBRef db "my-key" readDBRef ref >>= \case Nothing -> do writeDBRef ref 1 return 1 Just n -> do writeDBRef ref (n + 1) return (n + 1) putStrLn $ "Number of times program was run: " ++ show n ``` Here, `filePersistence` creates a `Persistence` implementation that stores data in a directory called `./my-data` on disk. The `withDB` function brackets the portion of code that uses the directory for storage. During the execution of `withDB`, one can use `db` to read and write persistent values inside of STM transactions. That is done using `getDBRef`, `readDBRef`, `writeDBRef`, and `deleteDBRef`. # FAQ ## Does this work reliably? I'm publishing this now to get more feedback, but I am confident that within the limitations described above, this is a correct implementation. Until there is a broader community consensus, I'll still label this experimental. ## How does this compare with the TCache package? The implementation here was inspired by TCache, and involves some similar ideas. I was motivated to implement this package because of several details in which use of TCache was hard to justify. These include: * Playing too fast and loose with unsafe operations for my taste. * Far too much use of global state and overlapping instances. * Many more possible states making it hard to reason about the correctness of the implementation. * Failure to build with newer GHC versions. All things put together, I reached the conclusion that I could trust a new implementation more than TCache. Note that TCache has more features that this package. I don't intend to implement features like triggers, indexes, and so on, all of which can be implemented on top of the basic functionality in this package if desired.