Open-source project
project-minigraf/minigraf avatar
project-minigraf/minigraf

Minigraf, a bi-temporal Datalog graph database in one file

Embedded graph memory for AI agents, mobile apps, and the browser.

213 stars14 forksRustApache-2.0

At a glance

What is it?
Minigraf embeds a bi-temporal graph database in a single .graph file and queries it with Datalog from Rust or Python. The catch is a v2.x bug that collapses multi-valued attributes written in one transact call.
Who is it for?
Minigraf earns a place in a Rust or Python service that needs to remember what it asserted, when it asserted it, and to keep every version of that record in one file. It fits badly where a JVM or Clojure stack is not negotiable, or where an importer writes several values per attribute in one call and cannot change before v3.0.0 ships.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • project-minigraf/minigraf213 ★ · +183
Star history
Repository2026-08-232026-10-23
project-minigraf/minigraf30213

183 Stars in the last 61 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Facts are entity, attribute and value triples inside one .graph file

Minigraf stores data as entity, attribute and value triples, and every query is a pattern over those triples. The pattern `[:alice :friend ?friend]` reads as: find the value of `:friend` for `:alice`, and bind it to `?friend`. Attributes are namespaced keywords such as `:person/name`, so a store can keep one flat relation without declaring columns anywhere. The database is a single file, and `Minigraf::open("data.graph")` is the entire setup step: no server process, no configuration file. Because the crate is built as both `cdylib` and `rlib`, that one core compiles into a Rust binary and into the shared library sitting behind the JVM, Android, Swift and C bindings. On docs.rs the crate is published with all features enabled for two targets, x86_64-unknown-linux-gnu and wasm32-unknown-unknown, so the reference docs cover the native build and the browser build from the same source.

Transaction time and valid time are separate clocks on every fact

Bi-temporality here means two timestamps per fact. Transaction time records when the database learned the fact. Valid time records the interval during which it is true about the world. Correcting a record does not overwrite it: the correction arrives as a new fact with a later transaction time, and a query can be positioned on either clock. That is the argument the project makes for Datalog over SQL-style languages, where time becomes one more dimension in a relation instead of a special case bolted on top. The visualizer at project-minigraf.github.io/minigraf-visualizer/ exists to make the two clocks visible. It runs Minigraf in the browser, steps through transactions, moves the valid-time cursor and draws each version of a fact on a map. The worked example it ships with is a salary recorded wrongly and then corrected, which is the case the design is built for. That visualizer also opens `.graph` files, so you can inspect a file you already have.

One execute method takes transacts and queries alike

Writes and reads go through the same call. `db.execute` takes a string of Datalog: a `transact` form adds facts, a `query` form with `:find` and `:where` reads them back. Opening a file-backed database takes a path, and `OpenOptions` is how you pass one.

rust
use minigraf::{Minigraf, OpenOptions};

// Open or create a file-backed database
let db = OpenOptions::new().path("myapp.graph").open()?;

// Add facts
db.execute(r#"(transact [[:alice :person/name "Alice"]
                         [:alice :person/age 30]
                         [:alice :friend :bob]
                         [:bob :person/name "Bob"]])"#)?;

Four facts land in one call: `:alice` gets a name, an age and a friend, and `:bob` gets a name.

rust
let results = db.execute(r#"
    (query [:find ?friend-name
            :where [:alice :friend ?friend]
                   [?friend :person/name ?friend-name]])
"#)?;

Two patterns chained inside `:where` join without join syntax: the first binds `?friend`, the second reads that binding and produces `?friend-name`. The comment left at the end of the quick start marks the next step, an explicit transaction that is all or nothing. Inside a repository checkout, `cargo test` runs 1212 tests, and `cargo run < demos/demo_recursive.txt` feeds the recursive rules demo into the same Datalog REPL a bare `cargo run` opens.

cargo add and pip install are the two Tier 1 paths

The minimum supported Rust version is 1.89, set as `rust-version` in Cargo.toml, and the crate sits on edition 2024. Pinning the current release in a manifest looks like this:

toml
[dependencies]
minigraf = "2.0.2"

Cargo can add it for you:

sh
cargo add minigraf

The Python package on PyPI is the other Tier 1 path, and both Tier 1 bindings ship alongside every core release. The npm packages belong to the experimental tier:

bash
pip install minigraf
npm install @minigraf/browser
npm install @minigraf/wasi
npm install minigraf

`@minigraf/browser` and `@minigraf/wasi` are separate builds for the browser and for WASI, while the bare `minigraf` npm package targets Node.js. Tier 2 means built and smoke-tested, then released on a best-effort schedule, which is a weaker promise than Tier 1 carries. The JVM and Android artifacts are Maven coordinates, `io.github.project-minigraf:minigraf-jvm` and `io.github.project-minigraf:minigraf-android`, the latter packaged as an `.aar`. Swift bindings arrive as an `.xcframework` through Swift Package Manager, and the C interface as a header plus tarball on the minigraf-c releases page. For those four the platform table points at the Use-Cases wiki page rather than spelling out a command, so read that before wiring them up.

Two values of one attribute in a single transact read back as one

This is the failure to know about before you write an importer. On v2.x, putting two values of the same attribute for one entity into a single `transact` call can leave you reading back one value. Retracting two values in a single `retract` behaves the same way. The case is tracked as issue 371, and the workaround is mechanical: write or retract each value of a multi-valued attribute in its own call. That costs transaction count, and it is a cheap thing to accept during an evaluation rather than an expensive thing to discover inside production data.

The fix changes the file format, so it cannot ship as a patch release. It is scheduled for v3.0.0, which turns the upgrade into a file compatibility question rather than a dependency bump, and the README does not document a migration path for existing `.graph` files. v2.x keeps receiving data-integrity and security fixes for 12 months after v3.0.0 ships, and pinned issue 421 is where the current list of bugs, affected versions, workarounds and fix versions lives. Releases so far have come thick: v2.0.0 on 2026-08-26, v2.0.1 on 2026-09-25, v2.0.2 on 2026-09-27.

Window functions and $slot bindings live in the same query string

Aggregation belongs to the query language here. A `:find` clause accepts window functions written as `sum/count/min/max/avg/rank/row-number :over (partition-by … :order-by …)`, so grouped totals and per-group ranks are expressed in the same pattern language as a lookup. For repeated execution there are prepared statements: the statement is parsed and planned once, then run with `$slot` bind tokens supplying the values, which is the shape a hot loop wants.

The grammar is small by design. Patterns look like `[?e :attr ?v]`, with no JOIN variants and no subquery nesting, and the project's argument is that a language this narrow fits in a system prompt and can be generated correctly by a coding assistant from a few examples. That matters when what you are storing is what an agent learned and the same agent has to read it back. Complete syntax sits in the Datalog Reference wiki page; the README stops at the quick start and the list of reasons to prefer Datalog, which cites four decades of research and production use in Datomic and XTDB.

Against XTDB, Cozo, Neo4j and SQLite the single file is the dividing line

The README runs its own comparison across four alternatives, and the rows worth weighing are the ones where the answers split. Bi-temporal support divides three ways: Minigraf and XTDB both claim it, Cozo gets a partial mark for time travel, Neo4j and SQLite get nothing. Single file narrows the field to Minigraf and SQLite, since XTDB, Cozo and Neo4j are things you install and connect to. Graph native then separates those two single-file options, because SQLite is not built around connected data while Minigraf's facts are the edges themselves.

Language matters as well. XTDB shares the Datalog choice but is written in Clojure, Cozo is also Rust, and Neo4j uses Cypher. On WebAssembly the table marks Minigraf ready for browser and WASI, SQLite ready, Cozo limited, and the other two not at all. Read those marks as the claims the table makes rather than as measured results, since no figures back them. The practical consequence is narrower than the grid suggests: XTDB and Cozo are the only alternatives that would not force a move off Datalog, and only one of those two is Rust with a single-file format.

The browser build reaches for IndexedDB, and wasm-opt stays off

Build configuration shows how far the browser target goes. Under `cfg(all(target_arch = "wasm32", not(target_os = "wasi")))` the manifest pulls in `wasm-bindgen`, `wasm-bindgen-futures`, `js-sys` and `web-sys`, with `web-sys` requesting `IdbDatabase`, `IdbObjectStore`, `IdbTransaction` and neighbouring features. That list points at IndexedDB as the browser storage path, and the `not(target_os = "wasi")` guard keeps the dependency set off the WASI build. The wasm-pack release profile sets `wasm-opt = false`, so published WebAssembly skips the binaryen optimization pass.

Repository layout follows that spread. `fuzz/` is excluded from the workspace while `benches/`, `demos/`, `docs/` and `tests/` sit inside it, and `examples/` holds one directory per binding (browser, c, java, node, python) alongside files named memory_profile.rs, tutorial_udfs.rs and generate_compat_fixture.rs. Licensing is dual: Cargo.toml sets `license = "MIT OR Apache-2.0"`, and both LICENSE-MIT and LICENSE-APACHE sit at the root. Two policy documents are readable in the tree, PHILOSOPHY.md for the support policy and ROADMAP.md for planned work and direction.

Editorial conclusion

Minigraf earns a place in a Rust or Python service that needs to remember what it asserted, when it asserted it, and to keep every version of that record in one file. It fits badly where a JVM or Clojure stack is not negotiable, or where an importer writes several values per attribute in one call and cannot change before v3.0.0 ships. Check pinned issue 421 first, then confirm you can afford one transact call per attribute value.

Frequently asked questions

What does Minigraf require before it will build?

Rust 1.89 or newer, which is the minimum supported version set as rust-version in Cargo.toml, and edition 2024. The published crate is at version 2.0.2.

Which Minigraf bindings ship with every core release?

The two Tier 1 bindings: the Rust crate on crates.io and the Python package on PyPI. Browser WASM, WASI, Node.js, JVM, Android, iOS and macOS, and C/FFI are Tier 2, built and smoke-tested and released on a best-effort schedule.

What goes wrong in Minigraf when one transact call writes two values of the same attribute?

On v2.x the two values can read back as a single value, tracked as issue 371. Write or retract each value of a multi-valued attribute in its own call. The fix changes the file format and ships in v3.0.0.

How long will Minigraf v2.x keep receiving fixes after v3.0.0?

v2.x gets data-integrity and security fixes for 12 months after v3.0.0 ships. The support policy is written up in PHILOSOPHY.md, and the current list of known bugs sits in pinned issue 421.

Can I try Minigraf without installing anything?

Yes. A browser playground at minigraf-playground.vercel.app needs no install, and the time travel visualizer at project-minigraf.github.io/minigraf-visualizer/ runs Minigraf in the browser and opens .graph files.

Official sources

  1. Official README
  2. Project repository
  3. Release notes
Community

Where developers are discussing it

Posts on Hacker News, dev.to and Lobsters that link to this repository, found by our scan of those communities. 1 in the last 30 days; newest first.

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/project-minigraf-minigraf.svg)](https://hysenlabs.com/projects/project-minigraf-minigraf)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/project-minigraf-minigraf.svg)](https://hysenlabs.com/projects/project-minigraf-minigraf)

Was this useful?