tln-db is the Go-native embedded fact store and query engine for Tln. It sits behind the FactStore interface (see Plugins), so the language core stays storage-agnostic while tln-db provides a fast, durable, self-contained backend.

Facts are the entity–attribute records Tln reasons over — loaded from your systems, never written in .tln. tln-db stores, indexes, and queries them.

Two ways to run it

  • Embedded — a Go library, in-process (bboltstore.Open()).
  • Sidecar — a standalone tlndb-server over gRPC (Unix socket or TCP) with an HTTP/JSON debug endpoint, so several processes can share one store (Postgres-style local socket).

Select it as your store

With the bundle system, tln-db is a store plugin you declare in mod.tln and point at the sidecar in config/store.tln — Active-Record style, no Go host:

# mod.tln
plugin "db" "v0.1.0" store
# config/store.tln
store db { target env "TLNDB_ADDR" }   # e.g. unix:///tmp/tlndb.sock

tln bundle wires it in through the store factory; the bundled tlnstore client is thin — it just dials tlndb-server (no bbolt / HNSW / roaring pulled in), so a bundle stays light while the heavy engine lives in the sidecar.

Choosing a store is opt-in and code-free. With no config/store.tln, Tln uses the built-in in-memory store — delete those two lines and you’re back to memory, same rules.tln. A project has at most one store, resolved by precedence: a host’s WithFactStore wins → else the manifest store plus config/store.tln → else in-memory. Config values resolve from the environment at run time (env "TLNDB_ADDR"), so no address or secret lives in the source.

What’s inside

Built on proven Go building blocks, tuned for rule evaluation:

  • Document store — snappy-compressed JSON in per-tenant buckets (strict isolation), ACID, SIGKILL-durable, on bbolt (B+ tree).
  • Inverted index — roaring-bitmap-backed lookups: equality, numeric ranges, temporal windows, group-by, closure tables, running stats (Welford), and absence queries.
  • Vector search — per-(entity, scope) HNSW index with cosine / Euclidean distance, for the language’s find similar / retrieval needs.
  • Composite queries — Query (pattern / predicate / or / not / full-text + aggregates + group-by), SequenceJoin, ClusterQuery, and a streamed Subscribe for reactive consumers.

Queries run in two phases: narrow (intersect docID bitmaps from the inverted index) then evaluate (decode candidates and check the remaining clauses) — index-fast where it can be, exact where it must be.

Swappable and tested

tln-db ships a conformance suite that any FactStore backend runs against, so alternatives drop in without language-level changes. Core itself ships only the built-in in-memory store (tests / REPL); every other backend is a store plugin. The other one today is tln-datalevin — the HTTP client for the JVM datalevin-server (Clojure Datalog), a sidecar like tln-db but a different transport. It’s selected the same Active-Record way:

# mod.tln
plugin "datalevin" "v0.1.0" store
# config/store.tln
store datalevin { url env "DATALEVIN_URL" }   # e.g. http://localhost:8898

Timestamps are clock-injectable for deterministic tests, and a mutation event stream (assert / change / retract) makes changes auditable — the same determinism-and-explainability story as the language itself.

Source: github.com/opentalon/tln-db.