refactor(8.0): delete DataAPI — superseded by Db persist/restore + import API + stats

The legacy backup/import/export/stats facade (src/api/DataAPI.ts) drifted
from the modern entity shape and every job it did now has a first-class
surface. Delete it and brain.data(), and rewire the CLI:

- data-stats → brain.stats() (full BrainyStats report: per-type breakdowns,
  indexed fields, index health, storage backend, writer lock, version)
- clean → brain.clear()
- export → alias of snapshot; a db.persist() snapshot is the full-fidelity
  export format (open with Brainy.load, load wholesale with brainy restore);
  external data ingestion remains brainy import (UniversalImportAPI)

Rewiring clean onto brain.clear() exposed two real bugs, both fixed:

- clear() left this.graphIndex undefined forever — any graph-touching call
  afterwards (relate, getNeighbors, stats) crashed. clear() now re-resolves
  the graph index exactly as init() does and re-wires the shared UUID↔int
  resolver, and re-resolves the metadata index with the same provider
  fallback as init().
- storage.clear() reset the legacy totals but not the per-type/subtype
  count rollups or id→type caches, so stats() reported phantom counts for
  deleted entities. Both adapters now delegate derived-state reset to
  reloadDerivedState(), the same path restore-from-snapshot uses.

One-shot CLI commands (data-stats, clean, snapshot/export, restore,
history, generation) now close the brain and exit explicitly — global
cache timers otherwise keep the process alive holding the writer lock.

Verified: build clean, 1383/1383 unit tests, 24/24 db-mvcc integration,
plus an end-to-end CLI smoke (add → data-stats → export → clean →
data-stats).
This commit is contained in:
David Snelling 2026-06-11 09:05:12 -07:00
parent cc8037db10
commit 478fa176f2
12 changed files with 262 additions and 639 deletions

View file

@ -4298,68 +4298,95 @@ export class Brainy<T = any> implements BrainyInterface<T> {
}
/**
* Clear all data from the database
* @description Clear ALL data from the database entities, relationships,
* blobs, and every derived index. The instance remains fully usable
* afterwards: the vector, metadata, and graph indexes are re-resolved
* exactly as on `init()`, and when the VFS was active a fresh VFS root
* entity is re-created (so a thresholdless vector search against a cleared
* store can still surface that one root entity).
* @example
* await brain.clear()
*/
async clear(): Promise<void> {
await this.ensureInitialized()
// Clear storage
await this.storage.clear()
await this.storage.clear()
// Invalidate GraphAdjacencyIndex to prevent stale in-memory data
// The index has LSMTree data and verbIdSet pointing to deleted entities.
// Without this, relate()'s duplicate check uses stale data, potentially
// allowing duplicate relationships or missing valid duplicates.
if (typeof (this.storage as any).invalidateGraphIndex === 'function') {
;(this.storage as any).invalidateGraphIndex()
}
this.graphIndex = undefined as any
// Reset index
if ('clear' in this.index && typeof this.index.clear === 'function') {
await this.index.clear()
} else {
// Recreate index using plugin factory when available
this.index = this.createIndex()
}
// Recreate metadata index to clear cached data
const clearMetadataFactory = this.pluginRegistry.getProvider<(storage: StorageAdapter) => any>('metadataIndex')
this.metadataIndex = clearMetadataFactory
? clearMetadataFactory(this.storage)
: new MetadataIndexManager(this.storage)
await this.metadataIndex.init()
// Reset dimensions
this.dimensions = undefined
// Clear any cached sub-APIs
this._neural = undefined
this._nlp = undefined
this._tripleIntelligence = undefined
// Re-initialize the content-addressed blob store after storage.clear()
// (clear() drops the BlobStorage instance). The VFS stores all file
// content through it, so this must happen BEFORE VFS reinitialization.
if (typeof (this.storage as any).initializeBlobStorage === 'function') {
await (this.storage as any).initializeBlobStorage()
}
// Reset VFS state - root entity was deleted by storage.clear()
// Bug: VFS instance remained in memory pointing to deleted root entity
if (this._vfs) {
// Clear PathResolver caches (including UnifiedCache VFS entries)
if ((this._vfs as any).pathResolver?.invalidateAllCaches) {
(this._vfs as any).pathResolver.invalidateAllCaches()
}
// Recreate and reinitialize VFS so it's ready for use
this._vfs = new VirtualFileSystem(this)
await this._vfs.init()
// _vfsInitialized remains true since we just initialized
} else {
// VFS was never used, reset flag for clean state
this._vfsInitialized = false
// Invalidate GraphAdjacencyIndex to prevent stale in-memory data
// The index has LSMTree data and verbIdSet pointing to deleted entities.
// Without this, relate()'s duplicate check uses stale data, potentially
// allowing duplicate relationships or missing valid duplicates.
if (typeof (this.storage as any).invalidateGraphIndex === 'function') {
;(this.storage as any).invalidateGraphIndex()
}
// Reset index
if ('clear' in this.index && typeof this.index.clear === 'function') {
await this.index.clear()
} else {
// Recreate index using plugin factory when available
this.index = this.createIndex()
}
// Recreate metadata index to clear cached data — mirror init()'s
// resolution: provider factory first, then the JS implementation (with a
// plugin-provided native entityIdMapper when registered).
const clearMetadataFactory = this.pluginRegistry.getProvider<(storage: StorageAdapter) => any>('metadataIndex')
if (clearMetadataFactory) {
this.metadataIndex = clearMetadataFactory(this.storage)
} else {
const entityIdMapperFactory = this.pluginRegistry.getProvider<(storage: StorageAdapter) => any>('entityIdMapper')
this.metadataIndex = new MetadataIndexManager(this.storage, {}, {
entityIdMapper: entityIdMapperFactory ? entityIdMapperFactory(this.storage) : undefined,
})
}
await this.metadataIndex.init()
// Re-resolve the graph index the same way init() does (provider factory,
// else the storage layer's lazy singleton) and re-wire the shared
// UUID ↔ int resolver. Without this, every graph-touching call after
// clear() — relate(), getNeighbors(), stats() — would hit an undefined
// index.
const clearGraphFactory = this.pluginRegistry.getProvider<(storage: StorageAdapter) => any>('graphIndex')
if (clearGraphFactory) {
this.graphIndex = clearGraphFactory(this.storage)
this.storage.setGraphIndex(this.graphIndex)
} else {
this.graphIndex = await (this.storage as any).getGraphIndex()
}
this.wireGraphIdResolver()
// Reset dimensions
this.dimensions = undefined
// Clear any cached sub-APIs
this._neural = undefined
this._nlp = undefined
this._tripleIntelligence = undefined
// Re-initialize the content-addressed blob store after storage.clear()
// (clear() drops the BlobStorage instance). The VFS stores all file
// content through it, so this must happen BEFORE VFS reinitialization.
if (typeof (this.storage as any).initializeBlobStorage === 'function') {
await (this.storage as any).initializeBlobStorage()
}
// Reset VFS state - root entity was deleted by storage.clear()
// Bug: VFS instance remained in memory pointing to deleted root entity
if (this._vfs) {
// Clear PathResolver caches (including UnifiedCache VFS entries)
if ((this._vfs as any).pathResolver?.invalidateAllCaches) {
(this._vfs as any).pathResolver.invalidateAllCaches()
}
// Recreate and reinitialize VFS so it's ready for use
this._vfs = new VirtualFileSystem(this)
await this._vfs.init()
// _vfsInitialized remains true since we just initialized
} else {
// VFS was never used, reset flag for clean state
this._vfsInitialized = false
}
}
// ─── Migration API ───────────────────────────────────────────────
@ -6231,19 +6258,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
return this._hub
}
/**
* Data Management API - backup, restore, import, export
*/
async data() {
const { DataAPI } = await import('./api/DataAPI.js')
return new DataAPI(
this.storage,
(id: string) => this.get(id),
undefined, // No getRelation method yet
this
)
}
/**
* Get Triple Intelligence System
* Advanced pattern recognition and relationship analysis