EventCell

BEAM-native coordinate event store. Append-only segment files, ETS indexes, BLAKE3 hash chains.

What it does

EventCell stores immutable events addressed by four dimensions:

Events are written to append-only segment files with CRC32 integrity checks, hash-chained per entity stream for tamper detection, and indexed in ETS for fast in-memory queries. Point lookups and per-entity streams use index-driven scans; cross-dimension queries narrow via dimension tables when possible. No external databases. No network dependencies. Just files and ETS.

Quick start

# Open a store
{:ok, store} = EventCell.Store.open(:my_store, data_dir: "/tmp/events")

# Append events
coord = EventCell.Coordinate.new!("user:u_1", "created", "org:acme", <<clock_bytes::binary>>)
{:ok, entry} = EventCell.Store.append(store, coord, "payload bytes")

# Query
{:ok, latest} = EventCell.Store.latest(store, "user:u_1")
history = EventCell.Store.stream(store, "user:u_1") |> Enum.to_list()
{:ok, results} = EventCell.Store.query(store, fact: "created", scope: "org:acme/*")

# Close
EventCell.Store.close(store)

Installation

def deps do
  [
    {:event_cell, "~> 0.6.0"}
  ]
end

Design

Storage layout

data_dir/
  segments/
    000001.ecel    # sealed, immutable
    000002.ecel    # sealed, immutable
    000003.ecel    # active segment (writes go here)
  snapshots/
    snap_1710000000000000000/
      meta.bin     # {segment_id, offset, entry_count, timestamp}
      idx_*.ets    # ETS table dumps
      symbols/     # string interning state

Configuration

EventCell.Store.open(:my_store,
  data_dir: "/var/data/events",        # required
  segment_max_bytes: 268_435_456,      # 256MB default
  fsync_interval_ms: 100,             # datasync timer, default 100ms
  fsync_event_count: 1000             # datasync after N events, default 1000
)

License

Apache-2.0

EventCell