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 }
}
}
```

View file

@ -1,14 +1,17 @@
/**
* @module DataAPI
* @description Portable graph backup/restore for Brainy the `BackupData v1`
* export/import format plus `clear()`/`getStats()` data-management helpers.
* @description Portable graph export/import for Brainy the `PortableGraph v1`
* format plus `clear()`/`getStats()` data-management helpers.
*
* Accessed via `brain.data()`. The two headline methods are:
* A `PortableGraph` is the portable, versioned interchange representation of a graph
* (entities + relations + optional vectors), NOT a backup exported for transport
* between instances, versions, and products. Accessed via `brain.data()`. The two
* headline methods are:
*
* - **`export(selector?, options?) → BackupData`** serialize a graph (an item, a
* - **`export(selector?, options?) → PortableGraph`** serialize a graph (an item, a
* collection + children, a connected neighbourhood, a VFS subtree, a predicate
* match, or the whole brain) into ONE versioned, portable JSON document.
* - **`import(backup, options?) → ImportResult`** restore a `BackupData` into the
* - **`import(graph, options?) → ImportResult`** restore a `PortableGraph` into the
* brain (dedup-by-id merge by default), re-embedding from `data` when vectors are
* absent.
*
@ -20,7 +23,7 @@
*
* **Design decision (reserved-field split):** entities carry Brainy's standard fields
* (`subtype`, `data`, `confidence`, `weight`, `service`, `createdBy`, `createdAt`) at
* the TOP LEVEL of each `BackupEntity`, and `metadata` holds ONLY custom user fields
* the TOP LEVEL of each `PortableGraphEntity`, and `metadata` holds ONLY custom user fields
* mirroring the in-memory `Entity` shape, so `import()` maps each field to its dedicated
* `add()`/`relate()` parameter rather than dumping everything into the metadata bag.
*/
@ -32,10 +35,10 @@ import { getBrainyVersion } from '../utils/version.js'
/** The fixed entity id of the VFS root collection (excluded from exports unless `includeSystem`). */
const VFS_ROOT_ID = '00000000-0000-0000-0000-000000000000'
/** Magic string identifying a Brainy portable backup document. */
const BACKUP_FORMAT = 'brainy-backup'
/** Magic string identifying a Brainy `PortableGraph` document (the `format` tag). */
const PORTABLE_GRAPH_FORMAT = 'brainy-portable-graph'
/** Current portable-format version. Import gates on this for cross-version migration. */
const BACKUP_FORMAT_VERSION = 1
const PORTABLE_GRAPH_FORMAT_VERSION = 1
/** Default embedding model label (informational; `dimensions` is the real compat gate). */
const DEFAULT_EMBED_MODEL = 'all-MiniLM-L6-v2'
/** Storage-layer fetch ceiling for enumerations (well above any single-brain entity count). */
@ -109,12 +112,12 @@ export interface ExportOptions {
}
/**
* @description Controls how a `BackupData` is applied to the brain on `import()`.
* @description Controls how a `PortableGraph` is applied to the brain on `import()`.
*/
export interface ImportOptions {
/**
* Conflict policy when an entity id already exists (default: `'merge'`):
* - `'merge'` update in place (dedup-by-id; the default that lets you assemble many backups).
* - `'merge'` update in place (dedup-by-id; the default that lets you assemble many graphs).
* - `'replace'` delete then re-create.
* - `'skip'` leave the existing entity untouched.
*/
@ -129,8 +132,8 @@ export interface ImportOptions {
remapIds?: (id: string) => string
}
/** One entity in a `BackupData`. Standard fields top-level; `metadata` is custom-only. */
export interface BackupEntity {
/** One entity in a `PortableGraph`. Standard fields top-level; `metadata` is custom-only. */
export interface PortableGraphEntity {
id: string
/** NounType value. */
type: string
@ -156,8 +159,8 @@ export interface BackupEntity {
metadata?: any
}
/** One relation (edge) in a `BackupData`. */
export interface BackupRelation {
/** One relation (edge) in a `PortableGraph`. */
export interface PortableGraphRelation {
id: string
from: string
to: string
@ -179,9 +182,9 @@ export interface BackupRelation {
* @description A self-describing, versioned, portable graph document. The same shape
* is produced/consumed on 7.x and 8.0; `formatVersion` gates cross-version migration.
*/
export interface BackupData {
/** Always `'brainy-backup'` — identifies the document type. */
format: typeof BACKUP_FORMAT
export interface PortableGraph {
/** Always `'brainy-portable-graph'` — identifies the document type. */
format: typeof PORTABLE_GRAPH_FORMAT
/** Integer format version (import gates on this). */
formatVersion: number
/** The Brainy version that produced the document (informational). */
@ -193,9 +196,9 @@ export interface BackupData {
/** Echo of the selector that produced this document (provenance). */
selector?: ExportSelector
/** Exported entities. */
entities: BackupEntity[]
entities: PortableGraphEntity[]
/** Exported relations. */
relations: BackupRelation[]
relations: PortableGraphRelation[]
/** VFS file bytes keyed by sha256 — present only with `includeContent`. */
blobs?: Record<string, string>
/** Endpoints referenced by `edges:'incident'` that fell outside the node set. */
@ -244,10 +247,10 @@ export class DataAPI {
// ============================================================================
/**
* @description Serialize part or all of the graph into a portable `BackupData`.
* @description Serialize part or all of the graph into a portable `PortableGraph`.
* @param selector - WHAT to export (omit for the whole brain). See {@link ExportSelector}.
* @param options - HOW to export (vectors, file bytes, edge policy). See {@link ExportOptions}.
* @returns A versioned, portable `BackupData` document.
* @returns A versioned, portable `PortableGraph` document.
* @example
* // A single workbench's members (exact id set), with vectors:
* const backup = await brain.data().export({ ids }, { includeVectors: true })
@ -261,7 +264,7 @@ export class DataAPI {
* // The whole brain:
* const backup = await brain.data().export()
*/
async export(selector: ExportSelector = {}, options: ExportOptions = {}): Promise<BackupData> {
async export(selector: ExportSelector = {}, options: ExportOptions = {}): Promise<PortableGraph> {
if (!this.brain) {
throw new Error('DataAPI.export() requires a Brainy instance (use brain.data().export()).')
}
@ -291,10 +294,10 @@ export class DataAPI {
}
// 4. Build entity records.
const entities: BackupEntity[] = []
const entities: PortableGraphEntity[] = []
for (const id of idSet) {
const e = entityMap.get(id)
if (e) entities.push(this.toBackupEntity(e, includeVectors))
if (e) entities.push(this.toPortableGraphEntity(e, includeVectors))
}
// 5. Collect edges per policy.
@ -315,8 +318,8 @@ export class DataAPI {
const blobCount = blobs ? Object.keys(blobs).length : 0
return {
format: BACKUP_FORMAT,
formatVersion: BACKUP_FORMAT_VERSION,
format: PORTABLE_GRAPH_FORMAT,
formatVersion: PORTABLE_GRAPH_FORMAT_VERSION,
brainyVersion: getBrainyVersion(),
createdAt: new Date().toISOString(),
embedding: { model: this.detectModel(), dimensions },
@ -339,30 +342,30 @@ export class DataAPI {
// ============================================================================
/**
* @description Restore a `BackupData` into the brain. Dedup-by-id merge by default, so
* @description Restore a `PortableGraph` into the brain. Dedup-by-id merge by default, so
* assembling many backups that share entity ids merges rather than duplicates. Vectors
* are re-embedded from `data` when absent (`reembed:'auto'`).
* @param data - A `BackupData` document (must have `format:'brainy-backup'`).
* @param data - A `PortableGraph` document (must have `format:'brainy-portable-graph'`).
* @param options - Conflict/vector/id-remap policy. See {@link ImportOptions}.
* @returns Counts of imported/merged/skipped/re-embedded entities + any per-record errors.
* @throws If `data` is not a `BackupData`, or its `formatVersion` is newer than supported.
* @throws If `data` is not a `PortableGraph`, or its `formatVersion` is newer than supported.
* @example
* const result = await brain.data().import(backup, { onConflict: 'merge' })
*/
async import(data: BackupData, options: ImportOptions = {}): Promise<ImportResult> {
async import(data: PortableGraph, options: ImportOptions = {}): Promise<ImportResult> {
if (!this.brain) {
throw new Error('DataAPI.import() requires a Brainy instance (use brain.data().import()).')
}
if (!data || (data as any).format !== BACKUP_FORMAT) {
if (!data || (data as any).format !== PORTABLE_GRAPH_FORMAT) {
throw new Error(
`DataAPI.import() expects a BackupData document (format:'${BACKUP_FORMAT}'). ` +
`DataAPI.import() expects a PortableGraph document (format:'${PORTABLE_GRAPH_FORMAT}'). ` +
`For file ingestion (CSV/PDF/Excel/JSON), use brain.import() instead.`
)
}
if (typeof data.formatVersion === 'number' && data.formatVersion > BACKUP_FORMAT_VERSION) {
if (typeof data.formatVersion === 'number' && data.formatVersion > PORTABLE_GRAPH_FORMAT_VERSION) {
throw new Error(
`Backup formatVersion ${data.formatVersion} is newer than this Brainy supports ` +
`(max ${BACKUP_FORMAT_VERSION}). Upgrade Brainy to import this document.`
`PortableGraph formatVersion ${data.formatVersion} is newer than this Brainy supports ` +
`(max ${PORTABLE_GRAPH_FORMAT_VERSION}). Upgrade Brainy to import this document.`
)
}
@ -698,9 +701,9 @@ export class DataAPI {
// SERIALIZATION HELPERS (private)
// ============================================================================
/** Map a canonical `Entity` to a `BackupEntity` (reserved fields top-level). */
private toBackupEntity(e: Entity, includeVectors: boolean): BackupEntity {
const be: BackupEntity = { id: e.id, type: e.type as string }
/** Map a canonical `Entity` to a `PortableGraphEntity` (reserved fields top-level). */
private toPortableGraphEntity(e: Entity, includeVectors: boolean): PortableGraphEntity {
const be: PortableGraphEntity = { id: e.id, type: e.type as string }
if (e.subtype !== undefined) be.subtype = e.subtype
const vis = (e as any).visibility
if (vis !== undefined && vis !== 'public') be.visibility = vis
@ -715,9 +718,9 @@ export class DataAPI {
return be
}
/** Map a canonical `Relation` to a `BackupRelation`. */
private toBackupRelation(r: Relation): BackupRelation {
const br: BackupRelation = { id: r.id, from: r.from, to: r.to, type: r.type as string }
/** Map a canonical `Relation` to a `PortableGraphRelation`. */
private toPortableGraphRelation(r: Relation): PortableGraphRelation {
const br: PortableGraphRelation = { id: r.id, from: r.from, to: r.to, type: r.type as string }
if (r.subtype !== undefined) br.subtype = r.subtype
const vis = (r as any).visibility
if (vis !== undefined && vis !== 'public') br.visibility = vis
@ -731,10 +734,10 @@ export class DataAPI {
private async collectEdges(
idSet: Set<string>,
edges: 'induced' | 'incident' | 'none'
): Promise<{ relations: BackupRelation[]; danglingIds?: string[] }> {
): Promise<{ relations: PortableGraphRelation[]; danglingIds?: string[] }> {
if (edges === 'none') return { relations: [] }
const relations: BackupRelation[] = []
const relations: PortableGraphRelation[] = []
const dangling = new Set<string>()
const seen = new Set<string>()
@ -747,7 +750,7 @@ export class DataAPI {
if (edges === 'induced' && !toIn) continue
if (!toIn) dangling.add(r.to)
seen.add(r.id)
relations.push(this.toBackupRelation(r))
relations.push(this.toPortableGraphRelation(r))
}
}
@ -760,7 +763,7 @@ export class DataAPI {
if (!idSet.has(r.from)) {
dangling.add(r.from)
seen.add(r.id)
relations.push(this.toBackupRelation(r))
relations.push(this.toPortableGraphRelation(r))
}
}
}
@ -771,7 +774,7 @@ export class DataAPI {
/** Read VFS file bytes (base64) for file entities, keyed by content hash. */
private async collectBlobs(
entities: BackupEntity[],
entities: PortableGraphEntity[],
entityMap: Map<string, Entity>
): Promise<Record<string, string>> {
const blobs: Record<string, string> = {}
@ -820,8 +823,8 @@ export class DataAPI {
}
}
/** `add()` params from a `BackupEntity` (excludes brain-managed fields like `createdAt`). */
private entityAddFields(be: BackupEntity): Record<string, any> {
/** `add()` params from a `PortableGraphEntity` (excludes brain-managed fields like `createdAt`). */
private entityAddFields(be: PortableGraphEntity): Record<string, any> {
const fields: Record<string, any> = {
data: be.data,
type: be.type as NounType
@ -834,8 +837,8 @@ export class DataAPI {
return fields
}
/** `update()` params from a `BackupEntity` (for `onConflict:'merge'`). */
private entityUpdateFields(be: BackupEntity): Record<string, any> {
/** `update()` params from a `PortableGraphEntity` (for `onConflict:'merge'`). */
private entityUpdateFields(be: PortableGraphEntity): Record<string, any> {
const fields: Record<string, any> = {}
if (be.data !== undefined) fields.data = be.data
if (be.type !== undefined) fields.type = be.type as NounType

View file

@ -876,7 +876,7 @@ export const coreCommands = {
const brain = getBrainy()
const format = options.format || 'json'
// Export the whole brain as a portable BackupData document (vectors + VFS file bytes).
// Export the whole brain as a portable PortableGraph document (vectors + VFS file bytes).
const dataApi = await brain.data()
const backup = await dataApi.export(undefined, {
includeVectors: true,

View file

@ -116,12 +116,12 @@ export {
// Export version utilities
export { getBrainyVersion } from './utils/version.js'
// Export portable graph backup/restore API (brain.data())
// Export portable graph export/import API (brain.data())
export { DataAPI } from './api/DataAPI.js'
export type {
BackupData,
BackupEntity,
BackupRelation,
PortableGraph,
PortableGraphEntity,
PortableGraphRelation,
ExportSelector,
ExportOptions,
ImportOptions,

View file

@ -1,7 +1,7 @@
/**
* Unit tests for the portable graph backup/restore API (brain.data()).
*
* Exercises BackupData v1 export()/import(): real round-trips against in-memory
* Exercises PortableGraph v1 export()/import(): real round-trips against in-memory
* storage (no mocks of the API under test) selectors, edge policy, vectors,
* conflict handling, id remapping, subtype fidelity, and cross-version guards.
*/
@ -11,11 +11,11 @@ import { randomUUID } from 'node:crypto'
import { Brainy } from '../../../src/brainy'
import { createTestConfig } from '../../helpers/test-factory'
import { NounType, VerbType } from '../../../src/types/graphTypes'
import type { BackupData } from '../../../src/api/DataAPI'
import type { PortableGraph } from '../../../src/api/DataAPI'
const VFS_ROOT_ID = '00000000-0000-0000-0000-000000000000'
describe('DataAPI — portable graph backup/restore (BackupData v1)', () => {
describe('DataAPI — portable graph backup/restore (PortableGraph v1)', () => {
let brain: Brainy
beforeEach(async () => {
@ -28,14 +28,14 @@ describe('DataAPI — portable graph backup/restore (BackupData v1)', () => {
})
describe('format + whole-brain round-trip', () => {
it('exports a self-describing, versioned BackupData document', async () => {
it('exports a self-describing, versioned PortableGraph document', async () => {
const a = await brain.add({ data: 'Alice', type: NounType.Person, subtype: 'employee' })
const b = await brain.add({ data: 'Acme', type: NounType.Organization })
await brain.relate({ from: a, to: b, type: VerbType.WorksWith, subtype: 'full-time' })
const backup = await brain.data().then((d) => d.export())
expect(backup.format).toBe('brainy-backup')
expect(backup.format).toBe('brainy-portable-graph')
expect(backup.formatVersion).toBe(1)
expect(typeof backup.brainyVersion).toBe('string')
expect(backup.brainyVersion.length).toBeGreaterThan(0)
@ -288,15 +288,15 @@ describe('DataAPI — portable graph backup/restore (BackupData v1)', () => {
})
describe('import validation', () => {
it('rejects a non-BackupData payload', async () => {
it('rejects a non-PortableGraph payload', async () => {
const d = await brain.data()
await expect(d.import({ entities: [] } as any)).rejects.toThrow(/BackupData/)
await expect(d.import({ entities: [] } as any)).rejects.toThrow(/PortableGraph/)
})
it('rejects a newer formatVersion', async () => {
const d = await brain.data()
const future: BackupData = {
format: 'brainy-backup',
const future: PortableGraph = {
format: 'brainy-portable-graph',
formatVersion: 999,
brainyVersion: 'x',
createdAt: new Date().toISOString(),