feat(8.0): portable graph export()/import() (BackupData v1) — Db.export + polymorphic import

Re-adds the portable graph round-trip on 8.0 (deleted with DataAPI), now on the
immutable Db so it composes with the generational model.

- src/db/backup.ts: BackupData v1 engine (identical wire format to the 7.32.0 line).
  exportGraph() reads through a Db at its pinned generation; importGraph() applies the
  whole backup as ONE atomic transact() (a single generation).
- Db.export(selector?, options?) — read at this view's generation, so
  brain.asOf(g).export() is a time-travel export and brain.now().with(ops).export() a
  what-if export.
- brain.export(selector?, options?) — sugar for now().export().
- brain.import() is polymorphic: a BackupData document -> graph round-trip; a
  file/buffer -> existing foreign-file ingestion (dispatched on the 'brainy-backup'
  tag; zero migration for ingestion callers).
- Selectors (ids/collection/connected/vfsPath/predicate/whole) + edge policy
  (induced/incident/none) + includeVectors/includeContent/includeSystem; system
  entities excluded by visibility. onConflict merge/replace/skip, reembed auto/never,
  remapIds. DbHost gains storage (VFS blob bytes).
- Types exported from the package root: BackupData/BackupEntity/BackupRelation/
  ExportSelector/ExportOptions/ImportOptions/ImportResult + isBackupData.

Tests: tests/unit/db/db-backup.test.ts (10, green) — round-trip, selectors, edges,
vectors+re-embed, merge, asOf/with composition, validation. Full unit suite green.

Follow-ups: docs (8.0 guide + RELEASES + api/README), completeness adds
(since(prior).export() delta, exportStream/importStream, validate()), includeContent
filesystem test, ids-at-asOf get() refinement, @soulcraft/formats Zod schema mirror.
This commit is contained in:
David Snelling 2026-06-16 16:38:18 -07:00
parent f4dea80176
commit 010ccf816d
5 changed files with 948 additions and 4 deletions

View file

@ -56,6 +56,9 @@ import type {
Relation,
Result
} from '../types/brainy.types.js'
import type { StorageAdapter } from '../coreTypes.js'
import { exportGraph } from './backup.js'
import type { ExportSelector, ExportOptions, BackupData } from './backup.js'
import {
splitNounMetadataRecord,
splitVerbMetadataRecord
@ -105,6 +108,8 @@ export interface SpeculativeOverlay<T = any> {
export interface DbHost<T = any> {
/** The generational record layer of the host brain. */
readonly store: GenerationStore
/** The host brain's storage adapter (portable-export VFS blob bytes). */
readonly storage: StorageAdapter
/** Live `brain.get()` (current-generation fast path). */
get(id: string, options?: GetOptions): Promise<Entity<T> | null>
/** Live `brain.find()` (current-generation fast path). */
@ -409,6 +414,28 @@ export class Db<T = any> {
return materialized.find({ ...(options ?? {}), query })
}
/**
* @description Serialize part or all of this database value into a portable
* `BackupData` document, read **as of this view's generation**. Because `export`
* lives on the immutable `Db`, it composes with every way of obtaining one:
* `brain.now().export(sel)` (current), `(await brain.asOf(g)).export(sel)`
* (time-travel export), and `brain.now().with(ops).export(sel)` (what-if export).
*
* The document is portable JSON, versioned (`formatVersion`), and current-state
* (no generation history) distinct from `persist()` (native whole-brain snapshot
* that preserves history). Restore with `brain.import(backup)`.
*
* @param selector - WHAT to export (omit for the whole brain). See {@link ExportSelector}.
* @param options - HOW to export (vectors / VFS bytes / edge policy). See {@link ExportOptions}.
* @returns A versioned, portable `BackupData` document.
* @example
* const backup = await brain.now().export({ collection: id }, { includeVectors: true })
*/
async export(selector: ExportSelector = {}, options: ExportOptions = {}): Promise<BackupData> {
this.assertUsable('export')
return exportGraph(this, this.host.storage, selector, options)
}
/**
* @description Relationships **as of this view's generation** the `Db`
* counterpart of `brain.related()` (same string-id shorthand and