brainy/docs/guides/export-and-import.md
David Snelling a408d3799d feat: portable graph export()/import() (BackupData v1) on brain.data()
brain.data() now serializes part or all of a brain to a versioned, portable
JSON document (BackupData) and restores it — the path for partial backups,
cross-environment moves, and 7.x→8.0 migration.

- export(selector?, options?): select by ids, collection (+transitive Contains),
  connected neighbourhood, vfsPath subtree, predicate, or whole brain; structural
  and predicate selectors compose. Options: includeVectors, includeContent (VFS
  blobs), includeSystem, edges ('induced'|'incident'|'none').
- import(backup, options?): onConflict 'merge' (dedup-by-id) | 'replace' | 'skip';
  reembed 'auto' (re-embed from data when no vector carried) | 'never'; remapIds
  to clone a subgraph under fresh ids.
- BackupData v1: format/formatVersion/brainyVersion/createdAt/embedding/entities/
  relations/blobs?/danglingIds?/stats. Reserved fields (subtype, data, confidence,
  weight, service) top-level; metadata custom-only. Current-state, no generations.
- Replaces the prior flat-entity export() (dropped relations) with a graph-complete
  document. Distinct from brain.import(file) ingestion, which is unchanged.
- Export BackupData/BackupEntity/BackupRelation/ExportSelector/ExportOptions/
  ImportOptions/ImportResult/DataAPI from the package root. CLI `brainy export`
  now writes a BackupData document.

Guide: docs/guides/backup-and-export.md. Tests: tests/unit/api/data-backup.test.ts.
2026-06-16 15:57:57 -07:00

7.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. Covers vectors, edge policy, conflict handling, and id remapping.
guides/subtypes-and-facets
api/README

Export & Import (portable graph)

brain.data() exposes a portable graph export/import API. One method serializes a graph — an item, a collection, a connected neighbourhood, a VFS subtree, a predicate match, or the whole brain — into a single versioned JSON document (BackupData); the inverse restores it.

const data = await brain.data()

const backup = await data.export()          // whole brain → BackupData
await data.import(backup)                    // restore (merge by id, re-embed if no vectors)

It is portable (human-readable JSON), versioned (formatVersion, so a 7.x export 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.

When to use which

You want… Use
A portable, partial-or-whole, cross-version graph document brain.data().export() / import() (this guide)
To ingest a CSV / PDF / Excel / JSON file as new entities brain.import(file) — see Import Anything

The two are different operations that happen to share a verb: import(file) parses a foreign document into new entities; data().import(backup) restores a graph this API exported.

Exporting

export(selector?, options?): Promise<BackupData>

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 }
The whole brain (omit)

The selector reuses find()'s grammar — "export what find() would match, minus ranking and limit." Structural and predicate selectors compose — a structural selector picks the nodes, and predicate keys then filter them:

// Members of a collection whose status is "open"
await data.export({ collection: collectionId, where: { status: 'open' } })

If you already have results from find(), export exactly those with the ids selector:

const hits = await brain.find({ type: NounType.Document })
await data.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 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).
// A self-contained subgraph with vectors, ready to restore elsewhere
const subset = await data.export({ ids }, { includeVectors: true })

// A VFS subtree including file bytes
const tree = await data.export({ vfsPath: '/docs' }, { includeContent: true })

Importing

import(backup, options?): Promise<ImportResult>
const result = await brain.data().then(d => d.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.

The default onConflict: 'merge' is what lets you assemble one working graph from many exported documents that share entity ids — re-importing an id merges rather than duplicates.

// Clone a subgraph under fresh ids (a copy, not a move)
const remap = new Map(backup.entities.map(e => [e.id, crypto.randomUUID()]))
await data.import(backup, { remapIds: id => remap.get(id) ?? id })

The BackupData format

{
  "format": "brainy-backup",
  "formatVersion": 1,                 // import gates on this (cross-version migration)
  "brainyVersion": "7.32.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",
      "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, 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.

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). The format is current-state — if you need a whole-brain snapshot with generation history, that is a separate native facility (db.persist() / Brainy.load() on 8.0).

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 data.export({ vfsPath: '/' },        { includeContent: true }) // all VFS + bytes
await data.export({ vfsPath: '/docs' },    { includeContent: true }) // one directory
await data.export({ vfsPath: '/a/b.txt' }, { includeContent: true }) // one file