refactor: rename BackupData → PortableGraph (the type is interchange, not a backup)

Parity with the 8.0 rename, backported to the 7.x line. The brain.data()
export()/import() document type was BackupData, but it is the portable, versioned
interchange representation of a graph (entities + relations + optional vectors),
NOT a backup — exported for transport between instances, versions, and products.
The actual backup is the native snapshot, so "Backup*" mis-signalled.

Rename every developer-visible symbol, JSDoc, comment and doc:
- BackupData→PortableGraph, BackupEntity→PortableGraphEntity,
  BackupRelation→PortableGraphRelation, BACKUP_FORMAT[_VERSION]→
  PORTABLE_GRAPH_FORMAT[_VERSION], internal toBackup* helpers→toPortableGraph*.
- src/api/DataAPI.ts, src/index.ts, src/cli/commands/core.ts, docs, and the test
  (renamed data-backup.test.ts → data-portable-graph.test.ts).

The on-the-wire `format` tag is also renamed 'brainy-backup' → 'brainy-portable-graph':
the export/import format was introduced in 7.32.0 and has not been adopted by any
consumer, so there are no stored documents to stay compatible with — a clean rename
beats carrying a legacy tag forward. No deprecated aliases (nothing to alias).

1505 unit green; build clean; data-portable-graph.test.ts 20/20.
This commit is contained in:
David Snelling 2026-06-19 12:15:07 -07:00
parent 5e7379dc41
commit 89036deb20
9 changed files with 98 additions and 95 deletions

View file

@ -1765,18 +1765,18 @@ await brain.import('https://api.example.com/data.json')
---
### Export & Import (portable backup)
### Export & Import (portable graph)
`brain.data()` exposes a portable graph backup/restore API. `export(selector?, options?)`
serializes part or all of the graph to a versioned, portable `BackupData` document;
`import(backup, options?)` restores it (dedup-by-id merge by default, re-embedding when
`brain.data()` exposes a portable graph graph/restore API. `export(selector?, options?)`
serializes part or all of the graph to a versioned, portable `PortableGraph` document;
`import(graph, options?)` restores it (dedup-by-id merge by default, re-embedding when
vectors are absent).
```typescript
const data = await brain.data()
// Whole brain → a portable BackupData document
const backup = await data.export()
// Whole brain → a portable PortableGraph document
const graph = await data.export()
// Just one workbench's members, with vectors, then restore elsewhere (merge by id)
const subset = await data.export({ ids }, { includeVectors: true })

View file

@ -457,14 +457,14 @@ await brain.storage.withLock('resource-id', async () => {
### Export Data
```typescript
// Export the whole brain to a portable BackupData document
const backup = await brain.data().then(d => d.export(undefined, { includeVectors: true }))
// Export the whole brain to a portable PortableGraph document
const graph = await brain.data().then(d => d.export(undefined, { includeVectors: true }))
```
### Import Data
```typescript
// Restore a BackupData document (dedup-by-id merge by default)
await brain.data().then(d => d.import(backup, { onConflict: 'merge' }))
// Restore a PortableGraph document (dedup-by-id merge by default)
await brain.data().then(d => d.import(graph, { onConflict: 'merge' }))
```
See the [Export & Import guide](../guides/export-and-import.md) for partial exports
@ -479,7 +479,7 @@ const newBrain = new Brainy({ storage: { type: 's3' } })
await oldBrain.init()
await newBrain.init()
// Transfer all data via a portable backup
// Transfer all data via a portable graph
const data = await oldBrain.data().then(d => d.export(undefined, { includeVectors: true }))
await newBrain.data().then(d => d.import(data))
```

View file

@ -546,7 +546,7 @@ const appComponent = await brain.findOne({ type: 'ReactComponent', name: 'App' }
const imports = await brain.getRelated(appComponent.id, 'Imports')
console.log(`App component imports:`, imports.map(c => c.name))
// Export the analyzed component graph as a portable BackupData document
// Export the analyzed component graph as a portable PortableGraph document
const graph = await brain.data().then(d => d.export({ type: 'ReactComponent' }))
console.log(`Exported ${graph.stats.entityCount} components, ${graph.stats.relationCount} edges`)
```

View file

@ -5,7 +5,7 @@ public: true
category: guides
template: guide
order: 9
description: 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.
description: Export part or all of a brain to a portable, versioned PortableGraph 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.
next:
- guides/subtypes-and-facets
- api/README
@ -15,19 +15,19 @@ next:
`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
match, or the whole brain — into a single versioned JSON document (`PortableGraph`); the
inverse restores it.
```typescript
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)
const graph = await data.export() // whole brain → PortableGraph
await data.import(graph) // 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
no generation history). Use it for portable artifacts, partial graphs, cross-environment
moves, and version upgrades.
## When to use which
@ -38,13 +38,13 @@ moves, and version upgrades.
| To ingest a CSV / PDF / Excel / JSON **file** as new entities | `brain.import(file)` — see [Import Anything](./import-anything.md) |
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
foreign document into new entities; `data().import(graph)` restores a graph this API
exported.
## Exporting
```typescript
export(selector?, options?): Promise<BackupData>
export(selector?, options?): Promise<PortableGraph>
```
### Selectors — *what* to export
@ -96,17 +96,17 @@ const tree = await data.export({ vfsPath: '/docs' }, { includeContent: true })
## Importing
```typescript
import(backup, options?): Promise<ImportResult>
import(graph, options?): Promise<ImportResult>
```
```typescript
const result = await brain.data().then(d => d.import(backup, { onConflict: 'merge' }))
const result = await brain.data().then(d => d.import(graph, { 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'`. |
| `onConflict` | `'merge'` | `'merge'` (update existing id in place — assemble many graphs), `'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. |
@ -115,15 +115,15 @@ exported documents that share entity ids — re-importing an id merges rather th
```typescript
// 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 })
const remap = new Map(graph.entities.map(e => [e.id, crypto.randomUUID()]))
await data.import(graph, { remapIds: id => remap.get(id) ?? id })
```
## The `BackupData` format
## The `PortableGraph` format
```jsonc
{
"format": "brainy-backup",
"format": "brainy-portable-graph",
"formatVersion": 1, // import gates on this (cross-version migration)
"brainyVersion": "7.32.0",
"createdAt": "2026-06-16T…Z",
@ -154,7 +154,7 @@ of each entity; `metadata` holds **only** custom user fields — mirroring the i
## Cross-version (7.x → 8.0)
Because the document is shared and versioned, a backup written by 7.x imports into 8.0:
Because the document is shared and versioned, a graph 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

View file

@ -540,11 +540,11 @@ export async function generateStaticProps() {
})
await brain.init()
// Build a search index — export the whole brain as a portable BackupData document
const backup = await brain.data().then(d => d.export())
// Build a search index — export the whole brain as a portable PortableGraph document
const graph = await brain.data().then(d => d.export())
return {
props: { searchIndex: backup.entities }
props: { searchIndex: graph.entities }
}
}
```