Backends#

All backends implement the same append, snapshot, query, and optimistic-concurrency contract. Choose one backend for storage and create one or more stores over it.

Memory#

backend := memory.Open()
store, err := backend.NewStore()

Use memory for tests and single-process ephemeral state. It supports indexing and archiving.

PostgreSQL#

backend, err := postgres.Open(postgres.Config{
    URL:      "postgres://localhost:5432/app?sslmode=disable",
    Prefix:   "timebox",
    MaxConns: 32,
})
if err != nil {
    return err
}
defer backend.Close()

store, err := backend.NewStore()

The backend creates and owns its schema. Prefix is the PostgreSQL schema name. PostgreSQL supports events, snapshots, status, tags, and multi-aggregate transactions; it does not support archiving.

Redis and Valkey#

backend, err := redis.Open(redis.Config{
    Addr:     "127.0.0.1:6379",
    Password: os.Getenv("REDIS_PASSWORD"),
    Prefix:   "timebox",
    DB:       0,
})
if err != nil {
    return err
}
defer backend.Close()

store, err := backend.NewStore()

Atomic operations run through Lua scripts. Shard adds a Redis hash tag so all Timebox keys occupy one cluster slot, which is required for atomic multi-key operations on Redis Cluster.

Raft#

cfg := raft.Config{
    LocalID: "node-1",
    Address: "127.0.0.1:9001",
    DataDir: "/var/lib/myapp/timebox",
    Servers: []raft.Server{
        {ID: "node-1", Address: "127.0.0.1:9001"},
    },
}

backend, err := raft.Open(cfg)
if err != nil {
    return err
}
defer backend.Close()

store, err := backend.NewStore()
if err != nil {
    return err
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := store.WaitReady(ctx); err != nil {
    return err
}

The Raft backend replicates writes, persists a segmented write-ahead log, and serves reads from local materialized state. Configure the same voter set on every node with a unique LocalID, Address, and DataDir per node.

Publisher receives committed events after they are durably applied:

cfg.Publisher = func(events ...*timebox.Event) {
    committed.Publish(events...)
}