- New docs/guides/export-and-import.md (public) for the 8.0 surface: brain.export()/import(), Db composition (asOf/with time-travel + what-if export), selectors, options, BackupData v1 format, cross-version (7.x→8.0), VFS, and the generations/persist distinction. Documents only the implemented surface. - api/README "Export & Import (portable) + Snapshots (native)": adds the portable brain.export()/import() round-trip alongside the native persist()/asOf() snapshot.
8.2 KiB
| title | slug | public | category | template | order | description | next | ||
|---|---|---|---|---|---|---|---|---|---|
| Export & Import (portable graph) | guides/export-and-import | true | guides | guide | 9 | Export part or all of a brain to a portable, versioned BackupData document and import it back — by id, collection, connected neighbourhood, VFS subtree, predicate, or the whole brain. export() lives on the immutable Db, so asOf()/with() give time-travel and what-if exports. |
|
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 (BackupData), and restores it.
const backup = await brain.export() // whole brain → BackupData
await brain.import(backup) // 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 backups,
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 | brain.export() / brain.import() (this guide) |
| A whole-brain snapshot with generation history | brain.now().persist(path) / Brainy.load(path) (native) |
| To ingest a CSV / PDF / Excel / JSON file as new entities | brain.import(file) — see Import Anything |
import() is polymorphic: hand it a BackupData 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-backup' tag).
Exporting
brain.export(selector?, options?): Promise<BackupData>
// (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) | { ids: ['a', 'b'] } |
| A collection + its children | { collection: collectionId } (alias memberOf) |
| A connected neighbourhood | { connected: { from: id, depth: 2, verbs?, direction? } } |
| A VFS directory / file (+ subtree) | { vfsPath: '/docs', recursive?: true } |
| Everything matching a predicate | { type, subtype, where, service, visibility } |
| 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 |
|---|---|---|
includeVectors |
false |
Carry embedding vectors verbatim. Off ⇒ import() re-embeds from data. |
includeContent |
false |
Include VFS file bytes in blobs so files round-trip byte-identically. |
includeSystem |
false |
Include visibility:'system' entities such as the VFS root. |
edges |
'induced' |
'induced' (both endpoints in the set), 'incident' (also dangling edges, recorded in danglingIds), or 'none' (nodes only). |
Importing
brain.import(backup, options?): Promise<ImportResult>
The whole backup is applied as one atomic transaction — it advances the brain exactly one generation, or none on failure.
const result = await brain.import(backup, { onConflict: 'merge' })
// → { imported, merged, skipped, reembedded, blobsWritten, errors }
| Option | Default | Effect |
|---|---|---|
onConflict |
'merge' |
'merge' (update existing id in place — assemble many backups), 'replace' (delete + recreate), or 'skip'. |
reembed |
'auto' |
'auto' (use the carried vector, else re-embed from data) or 'never' (require a carried vector; record an error if absent). |
remapIds |
— | Rewrite every id on the way in, e.g. to clone a template subgraph under fresh ids. |
meta |
— | 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 BackupData format
{
"format": "brainy-backup",
"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 (BackupData, BackupEntity, BackupRelation, 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 backup 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