Export & Import (portable graph)
Brainy serializes part or all of a brain — an item, a collection, a connected neighbourhood, a VFS subtree, a predicate match, or the whole brain — into a single versioned JSON document (PortableGraph), and restores it.
const graph = await brain.export() // whole brain → PortableGraph
await brain.import(graph) // restore (merge by id, re-embed if no vectors)It is portable (human-readable JSON), versioned (formatVersion, so a document written by 7.x imports cleanly into 8.0), and current-state (the entities and edges as they are now — no generation history). Use it for portable artifacts, partial exports, cross-environment moves, and version upgrades.
export() is a method on the immutable Db value, so it composes with every way of obtaining one:
brain.export(sel) // = brain.now().export(sel)
;(await brain.asOf(gen)).export(sel) // time-travel export (a past generation)
brain.now().with(ops).export(sel) // what-if export (a speculative state)When to use which
You want… | Use |
|---|---|
A portable, partial-or-whole, cross-version graph document |
|
A whole-brain snapshot with generation history |
|
To ingest a CSV / PDF / Excel / JSON file as new entities |
|
import() is polymorphic: hand it a PortableGraph and it does the graph round-trip; hand it a file/buffer and it does foreign-file ingestion (dispatched on the document's format: 'brainy-portable-graph' tag).
Exporting
brain.export(selector?, options?): Promise<PortableGraph>
// (also on any Db: brain.now().export(...), (await brain.asOf(g)).export(...))Selectors — what to export
Omit the selector to export the whole brain. Otherwise pick a node set:
Scenario | Selector |
|---|---|
Just an item (or items) |
|
A collection + its children |
|
A connected neighbourhood |
|
A VFS directory / file (+ subtree) |
|
Everything matching a predicate |
|
The whole brain | (omit) |
The selector reuses find()'s grammar — "export what find() would match, minus ranking and limit." Structural and predicate selectors compose:
// Members of a collection whose status is "open"
await brain.export({ collection: collectionId, where: { status: 'open' } })
// Already have find() results? Export exactly those with the ids selector
const hits = await brain.find({ type: NounType.Document })
await brain.export({ ids: hits.map(r => r.id) })Options — how to serialize
Option | Default | Effect |
|---|---|---|
|
| Carry embedding vectors verbatim. Off ⇒ |
|
| Include VFS file bytes in |
|
| Include |
|
|
|
Importing
brain.import(graph, options?): Promise<ImportResult>The whole graph is applied as one atomic transaction — it advances the brain exactly one generation, or none on failure.
const result = await brain.import(graph, { onConflict: 'merge' })
// → { imported, merged, skipped, reembedded, blobsWritten, errors }Option | Default | Effect |
|---|---|---|
|
|
|
|
|
|
| — | Rewrite every id on the way in, e.g. to clone a template subgraph under fresh ids. |
| — | Transaction metadata recorded in the tx-log alongside the new generation. |
The default onConflict: 'merge' lets you assemble one working graph from many exported documents that share entity ids — re-importing an id merges rather than duplicates.
The PortableGraph format
{
"format": "brainy-portable-graph", // identifies the document type
"formatVersion": 1, // import gates on this (cross-version migration)
"brainyVersion": "8.0.0",
"createdAt": "2026-06-16T…Z",
"embedding": { "model": "all-MiniLM-L6-v2", "dimensions": 384 },
"selector": { … }, // echoes what was exported (provenance)
"entities": [
{
"id": "…", "type": "Document", "subtype": "invoice", "visibility": "public",
"data": "…", // the embedding source
"confidence": 1, "weight": 1, "service": "…",
"vector": [ … ], // only with includeVectors
"metadata": { … } // custom fields only (reserved fields are top-level)
}
],
"relations": [
{ "id":"…", "from":"…", "to":"…", "type":"Contains", "subtype":"…",
"weight":1, "confidence":1, "metadata": { … } }
],
"blobs": { "<sha256>": "<base64>" }, // only with includeContent
"danglingIds": [ "…" ], // only with edges:'incident'
"stats": { "entityCount": 0, "relationCount": 0, "blobCount": 0, "vectorDimensions": 384 }
}Standard fields (subtype, visibility, data, confidence, weight, service) sit at the top level of each entity; metadata holds only custom user fields — mirroring the in-memory Entity shape, so import() maps each field to its dedicated parameter. The TypeScript types (PortableGraph, PortableGraphEntity, PortableGraphRelation, ExportSelector, ExportOptions, ImportOptions, ImportResult) are exported from the package root.
Generations & time-travel
The portable document is current-state — it never embeds generation history (that keeps it cross-version-portable). History lives where it's queryable:
During a session:
brain.asOf(g)/brain.now().with(ops)on the live brain. Becauseexport()is on theDb,(await brain.asOf(g)).export()serializes a past generation andbrain.now().with(ops).export()serializes a speculative one.A whole-brain snapshot with history:
brain.now().persist(path)/Brainy.load(path)(native, generation-preserving) — a separate facility from this portable format.
Note: only transact() (and the write shortcuts that commit through it) advances a generation, so time-travel export differs across transaction boundaries.
Cross-version (7.x → 8.0)
Because the document is shared and versioned, a PortableGraph written by 7.x imports into 8.0: formatVersion is read forward, subtype is carried so 8.0 re-types correctly, and the same 384-dimension model on both lines means includeVectors:false re-embeds identically (or true carries vectors verbatim).
VFS
VFS directories are Collection entities and files are entities linked by Contains, so the whole filesystem (or any subtree) exports through the vfsPath selector:
await brain.export({ vfsPath: '/' }, { includeContent: true }) // all VFS + bytes
await brain.export({ vfsPath: '/docs' }, { includeContent: true }) // one directory
await brain.export({ vfsPath: '/a/b.txt' }, { includeContent: true }) // one file