Fabriq

Introduction

Fabriq is a standalone data fabric for Go — the single module through which application code talks to every datastore, enforcing tenancy, eventing, and projection invariants across all of them.

Fabriq is the single module through which application code talks to every datastore. Application code never opens a connection, writes SQL, or calls Redis directly — it holds one facade and reaches every store through typed capability ports. In exchange, fabriq guarantees a small set of invariants that are otherwise impossible to hold across five different engines.

The three invariants

Architecture

Postgres is the source of truth. Redis is the event fan-out. FalkorDB and Elasticsearch are derived projections. The kernel in core/ is domain-agnostic and engine-agnostic; engine dialects live exclusively under adapters/.

        commands                queries (capability ports)        deltas
           │                            │                           ▲
           ▼                            ▼                           │
┌──────────────────────────────────────────────────────────────────┴─────┐
│ fabriq (facade)                                                         │
│  core/registry  core/command  core/event  core/projection  core/subscribe
│  ─────────────────────────── ports ──────────────────────────────────  │
│  adapters/postgres (grove)  adapters/redis  adapters/falkordb  adapters/elastic
└─────────────────────────────────────────────────────────────────────────┘
     Postgres+Timescale+pgvector   Redis Streams   FalkorDB    Elasticsearch
        (source of truth)           (fan-out)      (projection) (projection)

Capability ports

There is deliberately no unified query language. Each storage capability has an explicit, engine-typed port. Relational work speaks SQL through grove, graph work speaks openCypher, search speaks queries over declared fields. No engine types appear in any signature, so adapters stay swappable.

PortEnginePurpose
RelationalPostgres (grove)The source of truth: aggregate rows, raw SQL escape hatch.
GraphFalkorDBKnowledge-graph projection, openCypher traversal + batched hydration.
SearchElasticsearchFull-text projection over declared fields.
TimeseriesTimescaleDBBulk telemetry ingest and windowed reads (event-bypass).
VectorpgvectorEmbedding upsert and nearest-neighbour search.
DocumentPostgres + grove CRDTCollaborative CRDT documents that materialize into ordinary events.

A first taste

reg := registry.New()
_ = domain.RegisterAll(reg) // or your own entity pack

f, stores, err := fabriq.Open(ctx, reg, fabriq.Config{
    Postgres: fabriq.PostgresConfig{DSN: dsn},
    Redis:    fabriq.RedisConfig{Addr: redisAddr},
})

// Writes: the only path, one versioned event per command.
res, err := f.Exec(tenantCtx, command.Command{
    Entity: "asset", Op: command.OpCreate,
    Payload: &domain.Asset{Name: "Pump 7", SiteID: siteID},
})

// Reads: capability ports.
var a domain.Asset
err = f.Relational().Get(tenantCtx, "asset", res.AggID, &a)

// Live deltas: server-resolved channel, conflated, resumable.
deltas, err := f.Subscribe(tenantCtx, query.SubscribeScope{
    Entity: "asset", Scope: "site", ID: siteID,
})

// Live query: a maintained, ordered, filtered window with
// enter/leave/move/update deltas and exact top-N.
snap, live, cancel, err := f.LiveQuery(tenantCtx, livequery.LiveQuery{
    Entity: "asset",
    Where:  query.Where{query.Eq("kind", "pump")},
    Sort:   []livequery.SortKey{{Column: "name"}},
    Limit:  50,
})

Every call requires a tenant-stamped context (tenant.WithTenant), set only by auth middleware from validated claims. An unstamped context is rejected before it reaches a store.

Where to go next

Fabriq is built on the Forge ecosystem: storage on grove, binaries on forge (apps) and forge/cli.

On this page