NEWCRDT document plane — collaborative state, ordinary events

One write path.
Every engine, in step.

Fabriq is a standalone data fabric for Go. Commands commit once through a transactional outbox, then fan out to relational, time-series, vector, graph, and search engines — versioned, tenant-scoped, always rebuildable.

write pathEXEC
// one write — exactly one versioned event
res, err := f.Exec(ctx, command.Command{
Entity: "order",
Op: command.OpCreate,
Payload: &Order{Ref: "ord_1042"},
})
└─ fans out — in step
Postgres
source of truth
Redis
live deltas
FalkorDB
graph
Elastic
search

Three invariants.
Zero drift.

Not conventions. Not review checklists. Structural properties of the fabric.

Transactional outbox

One write, one event

Every command commits inside a Postgres transaction that appends exactly one versioned event. A leader-elected relay publishes it to Redis Streams — nothing written twice, nothing lost.

Structural tenancy

Tenant-scoped, structurally

Tenant rides on context and is stamped into every engine — row-level security in Postgres, graph-per-tenant, index routing, key prefixes. Cross-tenant reads don’t fail politely. They can’t happen.

Derived projections

Always rebuildable

Graph and search are projections, never written directly. Blue-green rebuilds swap in atomically, and a reconciler detects drift and heals it through the same outbox.

The write path

Follow a write through the loom.

  1. 01

    f.Exec(cmd)

    A command enters the facade — validated against the registry, tenant and traceparent stamped on the envelope.

  2. 02

    postgres commit

    State and exactly one versioned event commit atomically — the outbox lives in the same transaction.

  3. 03

    leader relay

    A leader-elected relay wakes on LISTEN/NOTIFY and publishes the event to Redis Streams, in order.

  4. 04

    woven outward

    Consumer groups project the event into graph and search, and push live deltas to subscribers — one truth, every engine.

Capability ports

Every shape of data. One facade.

Reads go through typed ports. Writes go through Exec — and nowhere else. The fabric decides which engine serves which shape; your code imports one package.

f.Exec()

The only write path. Commands in a Postgres transaction, exactly one versioned event each.

Transactional outbox
f.Relational()

Typed gets, filtered lists, and pagination against the source of truth.

Postgres · RLS
f.Timeseries()

Bulk telemetry ingest and windowed reads on hypertables.

TimescaleDB
f.Vector()

Similarity search over embeddings with HNSW indexes.

pgvector
f.Graph()

openCypher traversals with one-shot, batched hydration.

FalkorDB
f.Search()

Full-text multi-match over declared fields, alias-swap rebuilds.

Elasticsearch
f.Document()

CRDT documents that materialize into ordinary versioned entities.

Merge engine
f.Subscribe()

Conflated live deltas with Last-Event-ID resume over SSE.

Redis Streams
Why a fabric

Invariants,
not promises.

1
Write path into the stores
4
Engines behind one facade
0
Direct writes to projections
100%
Access tenant-scoped

Most stacks bolt a search index and a graph onto a primary database and hope the glue holds. Drift creeps in quietly, tenancy leaks through a forgotten filter, and rebuilding a projection becomes a weekend with a runbook.

Fabriq makes the guarantees structural. Writes pass through one door and leave a versioned trail. Projections are derived, disposable, and rebuilt blue-green while reads keep flowing. And when an engine disagrees with the source of truth, the reconciler notices — and heals it through the same front door.