hindsight-filesystem-store
Copyright(c) 2024
LicenseBSD3
Maintainermaintainer@example.com
Stabilityexperimental
Safe HaskellNone
LanguageGHC2021

Hindsight.Store.Filesystem

Description

Overview

File-based event store persisting events as JSON on disk. Provides durability without requiring a database. Suitable for single-node deployments with moderate event volumes.

Events stored in append-only events.log file. Multi-process support via file locking and fsnotify change detection.

Quick Start

import Hindsight.Store.Filesystem

main :: IO ()
main = do
  -- Create store with default config
  config <- mkDefaultConfig "./events"
  store <- newFilesystemStore config

  -- Insert events (see Hindsight.Store for details)
  streamId <- StreamId <$> UUID.nextRandom
  let event = mkEvent MyEvent myData
  result <- insertEvents store Nothing $ singleEvent streamId NoStream event

  -- Subscribe to events
  handle <- subscribe store matcher (EventSelector AllStreams FromBeginning)
  -- ... process events ...

  -- Cleanup when done
  cleanupFilesystemStore store

Configuration

FilesystemStoreConfig has three parameters:

  • storePath - Directory for event log and lock file
  • syncInterval - Disk sync frequency (microseconds, 0 = sync every write)
  • lockTimeout - Max time to wait for file lock (microseconds)

Use mkDefaultConfig for sensible defaults or construct manually for custom settings.

Use Cases

When to use Filesystem store:

  • Single-node applications requiring durability
  • Development/staging environments
  • Embedded systems or edge deployments
  • Apps that can't run PostgreSQL (resource constraints, deployment complexity)
  • Multi-process applications on same host

When NOT to use Filesystem store:

  • Distributed multi-node systems (use PostgreSQL)
  • Very high event throughput (PostgreSQL performs better)
  • Large event volumes (startup replay becomes slow)

Trade-offs

Advantages:

  • Events survive process restarts (durable)
  • No database dependency
  • Multi-process support on same host
  • Simple deployment (just a directory)
  • ACID guarantees via file locking

Limitations:

  • Startup time grows with event count (linear log replay)
  • All indices must fit in memory
  • Single-node only (no distributed support)
  • Performance limited by disk I/O
  • Not suitable for very large datasets

Implementation

Persistence layer over Memory store infrastructure. Events written to disk then loaded into in-memory STM structures. File locking serializes writes. fsnotify detects changes from other processes for incremental reloading.

Storage format: Append-only JSON log (events.log), one transaction per line. Stream indices rebuilt on startup by replaying log.

Synopsis

Store Types

data FilesystemStoreHandle Source #

Handle for filesystem store operations.

newtype FilesystemCursor Source #

Cursor for filesystem store.

Constructors

FilesystemCursor 

Fields

Instances

Instances details
FromJSON FilesystemCursor Source # 
Instance details

Defined in Hindsight.Store.Filesystem

ToJSON FilesystemCursor Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Generic FilesystemCursor Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Associated Types

type Rep FilesystemCursor 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep FilesystemCursor = D1 ('MetaData "FilesystemCursor" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'True) (C1 ('MetaCons "FilesystemCursor" 'PrefixI 'True) (S1 ('MetaSel ('Just "getSequenceNo") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Integer)))
Show FilesystemCursor Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Eq FilesystemCursor Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Ord FilesystemCursor Source # 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep FilesystemCursor Source # 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep FilesystemCursor = D1 ('MetaData "FilesystemCursor" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'True) (C1 ('MetaCons "FilesystemCursor" 'PrefixI 'True) (S1 ('MetaSel ('Just "getSequenceNo") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Integer)))

Configuration

data FilesystemStoreConfig Source #

Configuration for filesystem store

Constructors

FilesystemStoreConfig 

Fields

Instances

Instances details
FromJSON FilesystemStoreConfig Source # 
Instance details

Defined in Hindsight.Store.Filesystem

ToJSON FilesystemStoreConfig Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Generic FilesystemStoreConfig Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Associated Types

type Rep FilesystemStoreConfig 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep FilesystemStoreConfig = D1 ('MetaData "FilesystemStoreConfig" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "FilesystemStoreConfig" 'PrefixI 'True) (S1 ('MetaSel ('Just "storePath") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 FilePath) :*: (S1 ('MetaSel ('Just "syncInterval") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 Int) :*: S1 ('MetaSel ('Just "lockTimeout") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 Int))))
Show FilesystemStoreConfig Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Eq FilesystemStoreConfig Source # 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep FilesystemStoreConfig Source # 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep FilesystemStoreConfig = D1 ('MetaData "FilesystemStoreConfig" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "FilesystemStoreConfig" 'PrefixI 'True) (S1 ('MetaSel ('Just "storePath") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 FilePath) :*: (S1 ('MetaSel ('Just "syncInterval") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 Int) :*: S1 ('MetaSel ('Just "lockTimeout") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 Int))))

mkDefaultConfig Source #

Arguments

:: FilePath

Base directory for store files

-> FilesystemStoreConfig

Configuration with defaults

Create a default configuration for a filesystem store.

Defaults: sync every write, 5-second lock timeout.

getStoreConfig Source #

Arguments

:: FilesystemStoreHandle

Store handle

-> FilesystemStoreConfig

Store configuration

Get the configuration from a store handle.

Useful for accessing the store path during cleanup or creating additional instances.

Store Operations

newFilesystemStore Source #

Arguments

:: FilesystemStoreConfig

Store configuration

-> IO FilesystemStoreHandle

Initialized store handle

Initialize or open a filesystem store.

Creates the store directory and event log file if they don't exist. If the event log exists and contains events, automatically reloads them into memory. Starts file watchers for cross-process event notifications.

This function handles both fresh stores and reopening existing stores, making it suitable for process restarts and multi-instance deployments.

cleanupFilesystemStore Source #

Arguments

:: FilesystemStoreHandle

Store handle to clean up

-> IO () 

Cleanup store resources and shut down background threads.

Stops file watchers and removes the lock file. Call this before application shutdown to ensure clean termination.

Exceptions

Testing Support

data EventLogEntry Source #

An entry in our event log represents a transaction state change.

Constructors

EventLogEntry 

Fields

Instances

Instances details
FromJSON EventLogEntry Source # 
Instance details

Defined in Hindsight.Store.Filesystem

ToJSON EventLogEntry Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Generic EventLogEntry Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Associated Types

type Rep EventLogEntry 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep EventLogEntry = D1 ('MetaData "EventLogEntry" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "EventLogEntry" 'PrefixI 'True) (S1 ('MetaSel ('Just "transactionId") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 UUID) :*: (S1 ('MetaSel ('Just "events") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 [StoredEvent]) :*: S1 ('MetaSel ('Just "timestamp") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 UTCTime))))
Show EventLogEntry Source # 
Instance details

Defined in Hindsight.Store.Filesystem

Eq EventLogEntry Source # 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep EventLogEntry Source # 
Instance details

Defined in Hindsight.Store.Filesystem

type Rep EventLogEntry = D1 ('MetaData "EventLogEntry" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "EventLogEntry" 'PrefixI 'True) (S1 ('MetaSel ('Just "transactionId") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 UUID) :*: (S1 ('MetaSel ('Just "events") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 [StoredEvent]) :*: S1 ('MetaSel ('Just "timestamp") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 UTCTime))))

data StorePaths Source #

File paths used by the store.

Constructors

StorePaths 

Fields

getPaths Source #

Arguments

:: FilePath

Base directory for the store

-> StorePaths

Computed file paths

Compute file paths for store files within a base directory.