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:
parent
5e7379dc41
commit
89036deb20
9 changed files with 98 additions and 95 deletions
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
Loading…
Add table
Add a link
Reference in a new issue