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

The export()/import() document type was named BackupData, but it is not a backup:
it is the portable, versioned, partial-or-whole interchange representation of a
graph (entities + relations + optional vectors/blobs) — the unit Brainy exports
for transport between instances, versions, and products, and the payload other
artifacts embed. The actual backup is persist()/load() (the native whole-brain,
generation-preserving snapshot), so "Backup*" actively collided with that concept.

Rename every developer-visible symbol, file, doc, JSDoc and comment:
- BackupData→PortableGraph, BackupEntity→PortableGraphEntity,
  BackupRelation→PortableGraphRelation, BackupReader/Writer→PortableGraphReader/Writer,
  BackupValidation→PortableGraphValidation, isBackupData→isPortableGraph,
  validateBackup→validatePortableGraph, BACKUP_FORMAT[_VERSION]→PORTABLE_GRAPH_FORMAT[_VERSION].
- src/db/backup.ts → src/db/portableGraph.ts; test → db-portable-graph.test.ts.
- Guide, api/README, RELEASES updated.

The on-the-wire `format` tag is renamed 'brainy-backup' → 'brainy-portable-graph':
the 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. No deprecated aliases (nothing to alias). 7.x shipped the
same rename as 7.32.2.

1471 unit green; build clean; db-portable-graph.test.ts 17/17.
This commit is contained in:
David Snelling 2026-06-19 12:09:19 -07:00
parent 3a3aa43b3a
commit 373a48122d
8 changed files with 156 additions and 145 deletions

View file

@ -1415,16 +1415,16 @@ await brain.import('https://api.example.com/data.json')
### Export & Import (portable) + Snapshots (native)
**Portable graph backup** — `brain.export()` / `brain.import()` (`BackupData` v1, versioned
**Portable graph export/import** — `brain.export()` / `brain.import()` (`PortableGraph` v1, versioned
JSON, partial-or-whole, cross-version). `export()` lives on the immutable `Db`, so it composes
with `now()`/`asOf()`/`with()`:
```typescript
// Export part or all of the brain to a portable, versioned document
const backup = await brain.export({ ids }, { includeVectors: true })
const graph = await brain.export({ ids }, { includeVectors: true })
// Restore it — import() routes a BackupData to the graph round-trip (merge by id)
await otherBrain.import(backup, { onConflict: 'merge' })
// Restore it — import() routes a PortableGraph to the graph round-trip (merge by id)
await otherBrain.import(graph, { onConflict: 'merge' })
// Time-travel export (serialize a past generation) / what-if export (a speculative state)
const past = await brain.asOf(gen)
@ -1436,7 +1436,7 @@ await brain.now().with(ops).export({ ids })
Selectors: `{ ids }`, `{ collection }` (alias `memberOf`), `{ connected: { from, depth } }`,
`{ vfsPath }`, predicate (`{ type, subtype, where, service }`), or whole brain (omit). See the
**[Export & Import guide](../guides/export-and-import.md)**. Distinct from `brain.import(file)`
(CSV/PDF/Excel/JSON ingestion — `import()` dispatches on whether you pass a `BackupData` or a file).
(CSV/PDF/Excel/JSON ingestion — `import()` dispatches on whether you pass a `PortableGraph` or a file).
**Native whole-brain snapshot** (generation-preserving, not portable JSON):

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. export() lives on the immutable Db, so asOf()/with() give time-travel and what-if exports.
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. export() lives on the immutable Db, so asOf()/with() give time-travel and what-if exports.
next:
- guides/subtypes-and-facets
- api/README
@ -15,16 +15,16 @@ next:
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.
versioned JSON document (`PortableGraph`), and restores it.
```typescript
const backup = await brain.export() // whole brain → BackupData
await brain.import(backup) // restore (merge by id, re-embed if no vectors)
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 backups,
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
@ -44,14 +44,14 @@ brain.now().with(ops).export(sel) // what-if export (a specula
| 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-anything.md) |
`import()` is **polymorphic**: hand it a `BackupData` and it does the graph round-trip;
`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-backup'` tag).
`format: 'brainy-portable-graph'` tag).
## Exporting
```typescript
brain.export(selector?, options?): Promise<BackupData>
brain.export(selector?, options?): Promise<PortableGraph>
// (also on any Db: brain.now().export(...), (await brain.asOf(g)).export(...))
```
@ -92,20 +92,20 @@ await brain.export({ ids: hits.map(r => r.id) })
## Importing
```typescript
brain.import(backup, options?): Promise<ImportResult>
brain.import(graph, options?): Promise<ImportResult>
```
The whole backup is applied as **one atomic transaction** — it advances the brain exactly
The whole graph is applied as **one atomic transaction** — it advances the brain exactly
one generation, or none on failure.
```typescript
const result = await brain.import(backup, { onConflict: 'merge' })
const result = await brain.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 exported 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. |
| `meta` | — | Transaction metadata recorded in the tx-log alongside the new generation. |
@ -113,11 +113,11 @@ const result = await brain.import(backup, { onConflict: 'merge' })
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
## The `PortableGraph` format
```jsonc
{
"format": "brainy-backup",
"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",
@ -145,7 +145,7 @@ documents that share entity ids — re-importing an id merges rather than duplic
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`,
TypeScript types (`PortableGraph`, `PortableGraphEntity`, `PortableGraphRelation`, `ExportSelector`,
`ExportOptions`, `ImportOptions`, `ImportResult`) are exported from the package root.
## Generations & time-travel
@ -164,7 +164,7 @@ 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:
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).