feat(8.0): API simplification — remove neural()/Db.search, one storage path key, integration→0

8.0 RC cleanup toward "one place per thing, zero-config, no deprecation":

- Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead
  legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})`
  / `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate
  entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor,
  NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept.
- Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams).
  Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` →
  now `find({ query, limit })`.
- Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases
  (`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the
  exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver
  feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native
  storage provider resolves the identical root (no split-brain).
- Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now
  applied as a post-filter on `result.score` (the documented way to bound semantic results).
- Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()`
  and failed dimension validation; they are metadata-only updates now.
- Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination
  shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an
  in-place path change that preserves the blob and the entity id, for files and directories.
- Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability.
  Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk
  flushes and emits `progress.queryable`, so imported data is queryable during the import.
- Sweep all docs, comments, and JSDoc for the removed/changed APIs.

Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
This commit is contained in:
David Snelling 2026-06-20 13:31:11 -07:00
parent 0c4a51c24e
commit 606445cd61
74 changed files with 712 additions and 7470 deletions

View file

@ -9,7 +9,7 @@ import { v4 as uuidv4 } from './universal/uuid.js'
import { JsHnswVectorIndex } from './hnsw/hnswIndex.js'
// TypeAwareHNSWIndex removed from default path — single unified JsHnswVectorIndex is faster
// for the 99% of queries that don't filter by type (avoids searching 42 separate graphs)
import { createStorage } from './storage/storageFactory.js'
import { createStorage, resolveFilesystemRoot } from './storage/storageFactory.js'
import type { StorageOptions } from './storage/storageFactory.js'
import { rebuildCounts } from './utils/rebuildCounts.js'
import type { MetadataWriteBuffer } from './utils/metadataWriteBuffer.js'
@ -23,7 +23,6 @@ import {
} from './utils/index.js'
import { embeddingManager } from './embeddings/EmbeddingManager.js'
import { matchesMetadataFilter } from './utils/metadataFilter.js'
import { ImprovedNeuralAPI } from './neural/improvedNeuralAPI.js'
import { NaturalLanguageProcessor } from './neural/naturalLanguageProcessor.js'
import { NeuralEntityExtractor, ExtractedEntity } from './neural/entityExtractor.js'
import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js'
@ -348,7 +347,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
private pluginRegistry = new PluginRegistry()
// Sub-APIs (lazy-loaded)
private _neural?: ImprovedNeuralAPI
private _nlp?: NaturalLanguageProcessor
private _extractor?: NeuralEntityExtractor
private _tripleIntelligence?: TripleIntelligenceSystem
@ -482,7 +480,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* @example
* ```typescript
* const reader = await Brainy.openReadOnly({
* storage: { type: 'filesystem', rootDirectory: '/data/brainy-data/tenant' }
* storage: { type: 'filesystem', path: '/data/brainy-data/tenant' }
* })
* const bookings = await reader.find({ where: { entityType: 'booking' } })
* await reader.close()
@ -4287,7 +4285,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
}
// Use find with vector
return this.find({
const results = await this.find({
vector: targetVector,
limit: params.limit,
type: params.type,
@ -4295,6 +4293,14 @@ export class Brainy<T = any> implements BrainyInterface<T> {
service: params.service,
excludeVFS: params.excludeVFS // Pass through VFS filtering
})
// A min-similarity `threshold` is applied as a post-filter on result.score
// — the canonical way to impose a minimum score on plain semantic results
// (top-level vector search does not honor it; see the find({ near }) guidance).
// Previously `params.threshold` was silently dropped.
return params.threshold != null
? results.filter((r) => r.score >= params.threshold!)
: results
}
// ============= BATCH OPERATIONS =============
@ -4856,7 +4862,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
this.dimensions = undefined
// Clear any cached sub-APIs
this._neural = undefined
this._nlp = undefined
this._tripleIntelligence = undefined
@ -5692,7 +5697,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* @param config - Standard `BrainyConfig`.
* @returns The initialized instance.
* @example
* const brain = await Brainy.open({ storage: { type: 'filesystem', rootDirectory: './data' } })
* const brain = await Brainy.open({ storage: { type: 'filesystem', path: './data' } })
*/
static async open<T = any>(config?: BrainyConfig): Promise<Brainy<T>> {
const brain = new Brainy<T>(config)
@ -5711,7 +5716,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* @returns A `Db` over the snapshot (release it to free resources).
* @example
* const db = await Brainy.load('/backups/2026-06-01')
* const hits = await db.search('quarterly invoices')
* const hits = await db.find({ query: 'quarterly invoices' })
* await db.release()
*/
static async load<T = any>(path: string): Promise<Db<T>> {
@ -5724,7 +5729,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
}
const brain = new Brainy<T>({
storage: { type: 'filesystem', rootDirectory: path },
storage: { type: 'filesystem', path },
mode: 'reader'
})
await brain.init()
@ -5957,7 +5962,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
/**
* Build the at-`generation` index materialization the brain-side half of
* the historical full-query surface (`Db.find`/`Db.search`/`Db.related` at
* the historical full-query surface (`Db.find`/`Db.related` at
* past pinned generations; see `src/db/db.ts` and
* `docs/ADR-001-generational-mvcc.md`):
*
@ -6812,16 +6817,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
// ============= SUB-APIS =============
/**
* Neural API - Advanced AI operations
*/
neural(): ImprovedNeuralAPI {
if (!this._neural) {
this._neural = new ImprovedNeuralAPI(this)
}
return this._neural
}
/**
* Natural Language Processing API
*/
@ -7403,7 +7398,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
*
* @example
* ```typescript
* const reader = await Brainy.openReadOnly({ storage: { type: 'filesystem', rootDirectory: '/data/brain' } })
* const reader = await Brainy.openReadOnly({ storage: { type: 'filesystem', path: '/data/brain' } })
* const fresh = await reader.requestFlush({ timeoutMs: 3000 })
* if (!fresh) {
* console.warn('Writer did not respond; results reflect last natural flush.')
@ -10786,10 +10781,12 @@ export class Brainy<T = any> implements BrainyInterface<T> {
// skip the probe-storage construction entirely on the init hot path. (The
// migration is node-filesystem-only; `fs.existsSync` is absent/throwing
// elsewhere, which the catch treats as "nothing to migrate".)
const rootDir =
(storageConfig.rootDirectory as string) ||
(storageConfig.path as string) ||
'./brainy-data' // createStorage's filesystem default
//
// Resolve the root through the SHARED resolver so the migration probe checks
// the SAME directory createStorage (and any plugin factory) will open, and a
// removed pre-8.0 alias (`rootDirectory`, `options.*`, `fileSystemStorage.*`)
// throws here too — consistent with createStorage, never a silent skip.
const rootDir = resolveFilesystemRoot(storageConfig)
try {
if (!fs.existsSync(`${rootDir}/branches`)) return
} catch {
@ -10935,10 +10932,23 @@ export class Brainy<T = any> implements BrainyInterface<T> {
Record<string, unknown>
const storageType = storageConfig.type || 'auto'
// Check plugin-provided storage factories (e.g., 'filesystem' override from cortex)
// Check plugin-provided storage factories (e.g., a native filesystem/mmap
// override registered by an acceleration plugin).
const pluginFactory = this.pluginRegistry.getStorageFactory(storageType)
if (pluginFactory) {
const adapter = await pluginFactory.create(storageConfig)
// Hand the plugin factory a NORMALIZED config whose canonical `path` is
// the SAME root our built-in resolver computed. The plugin's native side
// (e.g. getBinaryBlobPath / mmap files) re-resolves the directory itself;
// without this, an alias-only config (`{ rootDirectory }`, `{ options:
// { path } }`, …) would resolve to the alias here but to ./brainy-data on
// the plugin side — a brainy-data/cor split-brain where the two halves
// write to different directories. Stamping the resolved `path` keeps both
// resolvers on the identical root.
const normalizedConfig = {
...storageConfig,
path: resolveFilesystemRoot(storageConfig)
}
const adapter = await pluginFactory.create(normalizedConfig)
return adapter as BaseStorage
}