2025-08-26 12:32:21 -07:00
/ * *
* UnifiedCache - Single cache for both HNSW and MetadataIndex
* Prevents resource competition with cost - aware eviction
2025-10-10 14:09:30 -07:00
*
2026-01-27 15:38:21 -08:00
* Features :
2025-10-10 14:09:30 -07:00
* - Adaptive sizing : Automatically scales from 2 GB to 128 GB + based on available memory
* - Container - aware : Detects Docker / K8s limits ( cgroups v1 / v2 )
* - Environment detection : Production vs development allocation strategies
* - Memory pressure monitoring : Warns when approaching limits
2025-08-26 12:32:21 -07:00
* /
import { prodLog } from './logger.js'
2025-10-10 14:09:30 -07:00
import {
getRecommendedCacheConfig ,
formatBytes ,
checkMemoryPressure ,
type MemoryInfo ,
type CacheAllocationStrategy
} from './memoryDetection.js'
feat: export provider contracts for the plugin surface brainy consumes
Native accelerators (cortex) register providers for metadataIndex, graphIndex,
hnsw, entityIdMapper, cache, columnStore, and aggregation. Until now the exact
method/property surface brainy calls on each was implicit — a provider could
drop a member brainy depends on and only fail at runtime when that path ran.
This defines and exports the provider contracts from the stable
@soulcraft/brainy/plugin entrypoint:
- MetadataIndexProvider, GraphIndexProvider, HnswProvider,
EntityIdMapperProvider, CacheProvider — each typed as exactly the surface
brainy calls (optional/feature-detected members like HNSW setPersistMode and
enableCOW are intentionally excluded).
- Re-exports ColumnStoreProvider and AggregationProvider (and the aggregate
types) from the same entrypoint so a plugin author can import the whole
provider surface from one place.
Brainy's own baseline classes now `implements` these contracts
(MetadataIndexManager, GraphAdjacencyIndex, HNSWIndex, EntityIdMapper,
UnifiedCache), so the interfaces can never silently diverge from what brainy
ships — and any provider that declares `implements` gets a compile error the
moment brainy starts requiring a new member.
The embeddings/embedBatch providers stay typed by the existing EmbeddingFunction
(no duplicate interface added). Type-only changes; no runtime behavior change.
2026-05-27 14:45:40 -07:00
import type { CacheProvider } from '../plugin.js'
2025-08-26 12:32:21 -07:00
export interface CacheItem {
key : string
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
type : 'vectors' | 'metadata' | 'embedding' | 'other'
2025-08-26 12:32:21 -07:00
data : any
size : number
rebuildCost : number // milliseconds to rebuild
lastAccess : number
accessCount : number
}
export interface UnifiedCacheConfig {
2025-10-10 14:09:30 -07:00
/** Maximum cache size in bytes (auto-detected if not specified) */
maxSize? : number
/** Minimum cache size in bytes (default 256MB) */
minSize? : number
/** Force development mode allocation (25% instead of 40-50%) */
developmentMode? : boolean
/** Enable request coalescing to prevent duplicate loads */
2025-08-26 12:32:21 -07:00
enableRequestCoalescing? : boolean
2025-10-10 14:09:30 -07:00
/** Enable fairness monitoring to prevent cache starvation */
2025-08-26 12:32:21 -07:00
enableFairnessCheck? : boolean
2025-10-10 14:09:30 -07:00
/** Fairness check interval in milliseconds */
fairnessCheckInterval? : number
/** Enable access pattern persistence for warm starts */
2025-08-26 12:32:21 -07:00
persistPatterns? : boolean
2025-10-10 14:09:30 -07:00
/** Enable memory pressure monitoring (default true) */
enableMemoryMonitoring? : boolean
/** Memory pressure check interval in milliseconds (default 30s) */
memoryCheckInterval? : number
2025-08-26 12:32:21 -07:00
}
feat: export provider contracts for the plugin surface brainy consumes
Native accelerators (cortex) register providers for metadataIndex, graphIndex,
hnsw, entityIdMapper, cache, columnStore, and aggregation. Until now the exact
method/property surface brainy calls on each was implicit — a provider could
drop a member brainy depends on and only fail at runtime when that path ran.
This defines and exports the provider contracts from the stable
@soulcraft/brainy/plugin entrypoint:
- MetadataIndexProvider, GraphIndexProvider, HnswProvider,
EntityIdMapperProvider, CacheProvider — each typed as exactly the surface
brainy calls (optional/feature-detected members like HNSW setPersistMode and
enableCOW are intentionally excluded).
- Re-exports ColumnStoreProvider and AggregationProvider (and the aggregate
types) from the same entrypoint so a plugin author can import the whole
provider surface from one place.
Brainy's own baseline classes now `implements` these contracts
(MetadataIndexManager, GraphAdjacencyIndex, HNSWIndex, EntityIdMapper,
UnifiedCache), so the interfaces can never silently diverge from what brainy
ships — and any provider that declares `implements` gets a compile error the
moment brainy starts requiring a new member.
The embeddings/embedBatch providers stay typed by the existing EmbeddingFunction
(no duplicate interface added). Type-only changes; no runtime behavior change.
2026-05-27 14:45:40 -07:00
/ * *
* Single cost - aware cache for HNSW and MetadataIndex .
*
* Implements { @link CacheProvider } : the surface Brainy calls on whatever cache
* is installed as the global cache ( its own instance , or a registered
* ` 'cache' ` provider such as Cortex ' s native eviction engine ) .
* /
export class UnifiedCache implements CacheProvider {
2025-08-26 12:32:21 -07:00
private cache = new Map < string , CacheItem > ( )
private access = new Map < string , number > ( ) // Access counts
private loadingPromises = new Map < string , Promise < any > > ( )
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
private typeAccessCounts = { vectors : 0 , metadata : 0 , embedding : 0 , other : 0 }
2025-08-26 12:32:21 -07:00
private totalAccessCount = 0
private currentSize = 0
2026-03-22 16:52:02 -07:00
private maxSize : number
2025-08-26 12:32:21 -07:00
private readonly config : UnifiedCacheConfig
2026-01-27 15:38:21 -08:00
// Memory management
2025-10-10 14:09:30 -07:00
private readonly memoryInfo : MemoryInfo
private readonly allocationStrategy : CacheAllocationStrategy
private memoryPressureCheckTimer : NodeJS.Timeout | null = null
private lastMemoryWarning = 0
2025-08-26 12:32:21 -07:00
constructor ( config : UnifiedCacheConfig = { } ) {
2026-01-27 15:38:21 -08:00
// Adaptive cache sizing
2025-10-10 14:09:30 -07:00
const recommendation = getRecommendedCacheConfig ( {
manualSize : config.maxSize ,
minSize : config.minSize ,
developmentMode : config.developmentMode
} )
this . memoryInfo = recommendation . memoryInfo
this . allocationStrategy = recommendation . allocation
this . maxSize = recommendation . allocation . cacheSize
2026-01-27 15:38:21 -08:00
// Log allocation decision (includes model memory)
2025-10-10 14:09:30 -07:00
prodLog . info (
` UnifiedCache initialized: ${ formatBytes ( this . maxSize ) } ` +
` ( ${ this . allocationStrategy . environment } mode, ` +
` ${ ( this . allocationStrategy . ratio * 100 ) . toFixed ( 0 ) } % of ${ formatBytes ( this . allocationStrategy . availableForCache ) } ` +
` after ${ formatBytes ( this . allocationStrategy . modelMemory ) } ${ this . allocationStrategy . modelPrecision . toUpperCase ( ) } model) `
)
// Log memory detection details
prodLog . debug (
` Memory detection: source= ${ this . memoryInfo . source } , ` +
` container= ${ this . memoryInfo . isContainer } , ` +
` system= ${ formatBytes ( this . memoryInfo . systemTotal ) } , ` +
` free= ${ formatBytes ( this . memoryInfo . free ) } , ` +
` totalAvailable= ${ formatBytes ( this . memoryInfo . available ) } , ` +
` modelReserved= ${ formatBytes ( this . allocationStrategy . modelMemory ) } , ` +
` availableForCache= ${ formatBytes ( this . allocationStrategy . availableForCache ) } `
)
// Log warnings if any
for ( const warning of recommendation . warnings ) {
prodLog . warn ( ` UnifiedCache: ${ warning } ` )
}
// Finalize configuration
2025-08-26 12:32:21 -07:00
this . config = {
enableRequestCoalescing : true ,
enableFairnessCheck : true ,
2026-01-27 15:38:21 -08:00
fairnessCheckInterval : 30000 , // Check fairness every 30 seconds (was 60s)
2025-08-26 12:32:21 -07:00
persistPatterns : true ,
2025-10-10 14:09:30 -07:00
enableMemoryMonitoring : true ,
memoryCheckInterval : 30000 , // Check memory every 30s
2025-08-26 12:32:21 -07:00
. . . config
}
2025-10-10 14:09:30 -07:00
// Start monitoring
2025-08-26 12:32:21 -07:00
if ( this . config . enableFairnessCheck ) {
this . startFairnessMonitor ( )
}
2025-10-10 14:09:30 -07:00
if ( this . config . enableMemoryMonitoring ) {
this . startMemoryPressureMonitor ( )
}
2025-08-26 12:32:21 -07:00
}
/ * *
* Get item from cache with request coalescing
* /
async get ( key : string , loadFn ? : ( ) = > Promise < any > ) : Promise < any > {
// Update access tracking
this . access . set ( key , ( this . access . get ( key ) || 0 ) + 1 )
this . totalAccessCount ++
// Check if in cache
const item = this . cache . get ( key )
if ( item ) {
item . lastAccess = Date . now ( )
item . accessCount ++
this . typeAccessCounts [ item . type ] ++
return item . data
}
// If no load function, return undefined
if ( ! loadFn ) {
return undefined
}
// Request coalescing - prevent stampede
if ( this . config . enableRequestCoalescing && this . loadingPromises . has ( key ) ) {
prodLog . debug ( 'Request coalescing for key:' , key )
return this . loadingPromises . get ( key )
}
// Load data
const loadPromise = loadFn ( )
if ( this . config . enableRequestCoalescing ) {
this . loadingPromises . set ( key , loadPromise )
}
try {
const data = await loadPromise
return data
} finally {
if ( this . config . enableRequestCoalescing ) {
this . loadingPromises . delete ( key )
}
}
}
2025-10-10 14:09:30 -07:00
/ * *
2026-01-27 15:38:21 -08:00
* Synchronous cache lookup
2025-10-10 14:09:30 -07:00
* Returns cached data immediately or undefined if not cached
* Use for sync fast path optimization - zero async overhead
* /
getSync ( key : string ) : any | undefined {
// Check if in cache
const item = this . cache . get ( key )
if ( item ) {
// Update access tracking synchronously
this . access . set ( key , ( this . access . get ( key ) || 0 ) + 1 )
this . totalAccessCount ++
item . lastAccess = Date . now ( )
item . accessCount ++
this . typeAccessCounts [ item . type ] ++
return item . data
}
return undefined
}
2025-08-26 12:32:21 -07:00
/ * *
* Set item in cache with cost - aware eviction
* /
set (
key : string ,
data : any ,
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
type : 'vectors' | 'metadata' | 'embedding' | 'other' ,
2025-08-26 12:32:21 -07:00
size : number ,
rebuildCost : number = 1
) : void {
// Make room if needed
while ( this . currentSize + size > this . maxSize && this . cache . size > 0 ) {
this . evictLowestValue ( )
}
// Add to cache
const item : CacheItem = {
key ,
type ,
data ,
size ,
rebuildCost ,
lastAccess : Date.now ( ) ,
accessCount : 1
}
// Update or add
const existing = this . cache . get ( key )
if ( existing ) {
this . currentSize -= existing . size
}
this . cache . set ( key , item )
this . currentSize += size
this . typeAccessCounts [ type ] ++
this . totalAccessCount ++
2025-10-13 11:50:53 -07:00
2026-01-27 15:38:21 -08:00
// Proactive fairness check: Check immediately if adding to a dominant type
2025-10-13 11:50:53 -07:00
// This prevents imbalance formation instead of reacting to it
if ( this . config . enableFairnessCheck && this . cache . size > 10 ) {
this . checkProactiveFairness ( type )
}
2025-08-26 12:32:21 -07:00
}
/ * *
* Evict item with lowest value ( access count / rebuild cost )
* /
private evictLowestValue ( ) : void {
let victim : string | null = null
let lowestScore = Infinity
for ( const [ key , item ] of this . cache ) {
2025-10-13 11:21:19 -07:00
// Calculate value score: access frequency * rebuild cost (higher is better)
2025-08-26 12:32:21 -07:00
const accessScore = ( this . access . get ( key ) || 1 )
2025-10-13 11:21:19 -07:00
const score = accessScore * item . rebuildCost
2025-08-26 12:32:21 -07:00
if ( score < lowestScore ) {
lowestScore = score
victim = key
}
}
if ( victim ) {
const item = this . cache . get ( victim ) !
prodLog . debug ( ` Evicting ${ victim } (type: ${ item . type } , score: ${ lowestScore } ) ` )
this . currentSize -= item . size
this . cache . delete ( victim )
// Keep access count for a while to prevent re-caching cold items
// this.access.delete(victim) // Don't delete immediately
}
}
/ * *
* Size - aware eviction - try to match needed size
* /
evictForSize ( bytesNeeded : number ) : boolean {
const candidates : Array < [ string , number , CacheItem ] > = [ ]
2025-10-13 11:21:19 -07:00
2025-08-26 12:32:21 -07:00
for ( const [ key , item ] of this . cache ) {
2025-10-13 11:21:19 -07:00
const score = ( this . access . get ( key ) || 1 ) * item . rebuildCost
2025-08-26 12:32:21 -07:00
candidates . push ( [ key , score , item ] )
}
// Sort by score (lower is worse)
candidates . sort ( ( a , b ) = > a [ 1 ] - b [ 1 ] )
let freedBytes = 0
const toEvict : string [ ] = [ ]
// Try to free exactly what we need
for ( const [ key , , item ] of candidates ) {
toEvict . push ( key )
freedBytes += item . size
if ( freedBytes >= bytesNeeded ) {
break
}
}
// Evict selected items
for ( const key of toEvict ) {
const item = this . cache . get ( key ) !
this . currentSize -= item . size
this . cache . delete ( key )
}
return freedBytes >= bytesNeeded
}
/ * *
* Fairness monitoring - prevent one type from hogging cache
* /
private startFairnessMonitor ( ) : void {
setInterval ( ( ) = > {
this . checkFairness ( )
} , this . config . fairnessCheckInterval ! )
}
private checkFairness ( ) : void {
// Calculate type ratios in cache
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
const typeSizes = { vectors : 0 , metadata : 0 , embedding : 0 , other : 0 }
const typeCounts = { vectors : 0 , metadata : 0 , embedding : 0 , other : 0 }
2025-08-26 12:32:21 -07:00
for ( const item of this . cache . values ( ) ) {
typeSizes [ item . type ] += item . size
typeCounts [ item . type ] ++
}
// Calculate access ratios
const totalAccess = this . totalAccessCount || 1
const accessRatios = {
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2)
Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral
config surface alongside the legacy config.hnsw path (which becomes a compat
shim removed in the final cleanup commit).
CHANGES
src/utils/recallPreset.ts (NEW)
- DEFAULT_RECALL = 'balanced'
- HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch).
'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0
upgrade with no explicit recall value is a no-op.
- resolveRecallHnswKnobs(preset) — preset → knobs.
- resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides
win. Explicit knobs always beat the preset.
src/types/brainy.types.ts
- New optional `vector` field on BrainyConfig:
vector?: {
recall?: 'fast' | 'balanced' | 'accurate'
quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? }
vectorStorage?: 'memory' | 'lazy'
advanced?: {
hnsw?: { M?, efConstruction?, efSearch?, ml? }
diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... }
}
}
- `hnsw` field marked @deprecated with note that it stays as a compat shim
during the 8.0 rename and is removed in the final cleanup commit.
src/utils/unifiedCache.ts
- Cache-category union widened from
'hnsw' | 'metadata' | 'embedding' | 'other'
to
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
- 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness
typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts,
evictType loop) all gained the 'vectors' key with initial value 0.
- The hard-coded fairness-check loop now iterates both keys.
- Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'.
src/brainy.ts (normalizeConfig)
- Adds the new `vector` field to the Required<BrainyConfig> return shape so
TS compiles. Reads config?.vector ?? undefined as any (same pattern as
the other optional config fields).
NO-OP scope
No behavioural change. The new config.vector path is silently ignored at
runtime in this commit — wired up in step 4 (storage + index resolution).
Pure surface plumbing. Tests + build green.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
vectors : this.typeAccessCounts.vectors / totalAccess ,
2025-08-26 12:32:21 -07:00
metadata : this.typeAccessCounts.metadata / totalAccess ,
embedding : this.typeAccessCounts.embedding / totalAccess ,
other : this.typeAccessCounts.other / totalAccess
}
// Calculate size ratios
const totalSize = this . currentSize || 1
const sizeRatios = {
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2)
Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral
config surface alongside the legacy config.hnsw path (which becomes a compat
shim removed in the final cleanup commit).
CHANGES
src/utils/recallPreset.ts (NEW)
- DEFAULT_RECALL = 'balanced'
- HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch).
'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0
upgrade with no explicit recall value is a no-op.
- resolveRecallHnswKnobs(preset) — preset → knobs.
- resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides
win. Explicit knobs always beat the preset.
src/types/brainy.types.ts
- New optional `vector` field on BrainyConfig:
vector?: {
recall?: 'fast' | 'balanced' | 'accurate'
quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? }
vectorStorage?: 'memory' | 'lazy'
advanced?: {
hnsw?: { M?, efConstruction?, efSearch?, ml? }
diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... }
}
}
- `hnsw` field marked @deprecated with note that it stays as a compat shim
during the 8.0 rename and is removed in the final cleanup commit.
src/utils/unifiedCache.ts
- Cache-category union widened from
'hnsw' | 'metadata' | 'embedding' | 'other'
to
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
- 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness
typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts,
evictType loop) all gained the 'vectors' key with initial value 0.
- The hard-coded fairness-check loop now iterates both keys.
- Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'.
src/brainy.ts (normalizeConfig)
- Adds the new `vector` field to the Required<BrainyConfig> return shape so
TS compiles. Reads config?.vector ?? undefined as any (same pattern as
the other optional config fields).
NO-OP scope
No behavioural change. The new config.vector path is silently ignored at
runtime in this commit — wired up in step 4 (storage + index resolution).
Pure surface plumbing. Tests + build green.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
vectors : typeSizes.vectors / totalSize ,
2025-08-26 12:32:21 -07:00
metadata : typeSizes.metadata / totalSize ,
embedding : typeSizes.embedding / totalSize ,
other : typeSizes.other / totalSize
}
2026-01-27 15:38:21 -08:00
// Check for starvation (more aggressive - 70% cache with <15% accesses)
2025-10-13 11:50:53 -07:00
// Previous: 90% cache, <10% access (too lenient, caused thrashing)
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
for ( const type of [ 'vectors' , 'metadata' , 'embedding' , 'other' ] as const ) {
2025-10-13 11:50:53 -07:00
if ( sizeRatios [ type ] > 0.7 && accessRatios [ type ] < 0.15 ) {
2025-08-26 12:32:21 -07:00
prodLog . warn ( ` Type ${ type } is hogging cache ( ${ ( sizeRatios [ type ] * 100 ) . toFixed ( 1 ) } % size, ${ ( accessRatios [ type ] * 100 ) . toFixed ( 1 ) } % access) ` )
this . evictType ( type )
}
}
}
2025-10-13 11:50:53 -07:00
/ * *
2026-01-27 15:38:21 -08:00
* Proactive fairness check
2025-10-13 11:50:53 -07:00
* Called immediately when adding items to prevent imbalance formation
* Uses same thresholds as periodic check but runs on - demand
* /
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
private checkProactiveFairness ( addedType : 'vectors' | 'metadata' | 'embedding' | 'other' ) : void {
2025-10-13 11:50:53 -07:00
// Quick check: only evaluate the type being added
let typeSize = 0
for ( const item of this . cache . values ( ) ) {
if ( item . type === addedType ) {
typeSize += item . size
}
}
const sizeRatio = typeSize / ( this . currentSize || 1 )
const accessRatio = this . typeAccessCounts [ addedType ] / ( this . totalAccessCount || 1 )
// Same threshold as periodic check: 70% size, <15% access
if ( sizeRatio > 0.7 && accessRatio < 0.15 ) {
prodLog . debug ( ` Proactive fairness: ${ addedType } reaching dominance ( ${ ( sizeRatio * 100 ) . toFixed ( 1 ) } % size, ${ ( accessRatio * 100 ) . toFixed ( 1 ) } % access) ` )
this . evictType ( addedType )
}
}
2025-08-26 12:32:21 -07:00
/ * *
* Force evict items of a specific type
* /
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
private evictType ( type : 'vectors' | 'metadata' | 'embedding' | 'other' ) : void {
2025-08-26 12:32:21 -07:00
const candidates : Array < [ string , number , CacheItem ] > = [ ]
2025-10-13 11:21:19 -07:00
2025-08-26 12:32:21 -07:00
for ( const [ key , item ] of this . cache ) {
if ( item . type === type ) {
2025-10-13 11:21:19 -07:00
const score = ( this . access . get ( key ) || 1 ) * item . rebuildCost
2025-08-26 12:32:21 -07:00
candidates . push ( [ key , score , item ] )
}
}
// Sort by score (lower is worse)
candidates . sort ( ( a , b ) = > a [ 1 ] - b [ 1 ] )
2026-01-27 15:38:21 -08:00
// Evict bottom 50% of this type (was 20%, too slow to prevent thrashing)
2025-10-13 11:50:53 -07:00
const evictCount = Math . max ( 1 , Math . floor ( candidates . length * 0.5 ) )
2025-08-26 12:32:21 -07:00
for ( let i = 0 ; i < evictCount && i < candidates . length ; i ++ ) {
const [ key , , item ] = candidates [ i ]
this . currentSize -= item . size
this . cache . delete ( key )
prodLog . debug ( ` Fairness eviction: ${ key } (type: ${ type } ) ` )
}
}
/ * *
* Delete specific item from cache
* /
delete ( key : string ) : boolean {
const item = this . cache . get ( key )
if ( item ) {
this . currentSize -= item . size
this . cache . delete ( key )
return true
}
return false
}
2025-12-04 11:22:30 -08:00
/ * *
* Delete all items with keys starting with the given prefix
2026-01-27 15:38:21 -08:00
* Added for VFS cache invalidation ( fixes stale parent ID bug )
2025-12-04 11:22:30 -08:00
* @param prefix - The key prefix to match
* @returns Number of items deleted
* /
deleteByPrefix ( prefix : string ) : number {
let deleted = 0
for ( const [ key , item ] of this . cache ) {
if ( key . startsWith ( prefix ) ) {
this . currentSize -= item . size
this . cache . delete ( key )
deleted ++
}
}
return deleted
}
2026-03-22 16:52:02 -07:00
/ * *
* Dynamically resize the cache maximum . If the new size is smaller than
* the current usage , evicts lowest - value items until the cache fits within
* the new budget . Used by Cortex ' s ResourceManager to rebalance cache vs
* instance memory as brainy instances are created and evicted .
*
* @param newMaxSize - New maximum cache size in bytes
* /
setMaxSize ( newMaxSize : number ) : void {
this . maxSize = Math . max ( newMaxSize , 64 * 1024 * 1024 ) // Floor at 64MB
while ( this . currentSize > this . maxSize && this . cache . size > 0 ) {
this . evictLowestValue ( )
}
}
/ * *
* Get the current maximum cache size in bytes .
* /
getMaxSize ( ) : number {
return this . maxSize
}
2025-08-26 12:32:21 -07:00
/ * *
* Clear cache or specific type
* /
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
clear ( type ? : 'vectors' | 'metadata' | 'embedding' | 'other' ) : void {
2025-08-26 12:32:21 -07:00
if ( ! type ) {
this . cache . clear ( )
this . currentSize = 0
return
}
for ( const [ key , item ] of this . cache ) {
if ( item . type === type ) {
this . currentSize -= item . size
this . cache . delete ( key )
}
}
}
/ * *
2025-10-10 14:09:30 -07:00
* Start memory pressure monitoring
* Periodically checks if we ' re approaching memory limits
* /
private startMemoryPressureMonitor ( ) : void {
const checkInterval = this . config . memoryCheckInterval || 30000
this . memoryPressureCheckTimer = setInterval ( ( ) = > {
this . checkMemoryPressure ( )
} , checkInterval )
// Unref so it doesn't keep process alive
if ( this . memoryPressureCheckTimer . unref ) {
this . memoryPressureCheckTimer . unref ( )
}
}
/ * *
* Check current memory pressure and warn if needed
* /
private checkMemoryPressure ( ) : void {
const pressure = checkMemoryPressure ( this . currentSize , this . memoryInfo )
// Only log warnings every 5 minutes to avoid spam
const now = Date . now ( )
const fiveMinutes = 5 * 60 * 1000
if ( pressure . warnings . length > 0 && now - this . lastMemoryWarning > fiveMinutes ) {
for ( const warning of pressure . warnings ) {
prodLog . warn ( ` UnifiedCache: ${ warning } ` )
}
this . lastMemoryWarning = now
}
// If critical, force aggressive eviction
if ( pressure . pressure === 'critical' ) {
const targetSize = Math . floor ( this . maxSize * 0.7 ) // Evict to 70%
const bytesToFree = this . currentSize - targetSize
if ( bytesToFree > 0 ) {
prodLog . warn (
` UnifiedCache: Critical memory pressure - forcing eviction of ${ formatBytes ( bytesToFree ) } `
)
this . evictForSize ( bytesToFree )
}
}
}
/ * *
* Get cache statistics with memory information
2025-08-26 12:32:21 -07:00
* /
getStats() {
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6)
Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'-
flavoured public name introduced as a compat shim in steps 1-5. The
algorithm-neutral surface is now the only surface.
REMOVED — PHASE A (type aliases)
src/plugin.ts
- Removed `export type HnswProvider = VectorIndexProvider` alias.
- Removed `export type DiskAnnProvider = VectorIndexProvider` alias.
src/index.ts
- Removed `export const HNSWIndex = JsHnswVectorIndex` alias.
src/types/brainy.types.ts
- Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field
carries the same boolean).
src/brainy.ts
- `stats()` no longer emits the legacy `hnsw` field on `indexHealth`.
REMOVED — PHASE B (storage adapter rename, 8 adapters)
Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and
`getHNSWData` → `getVectorIndexData` across:
- src/storage/adapters/baseStorageAdapter.ts (abstract declarations)
- src/storage/adapters/fileSystemStorage.ts
- src/storage/adapters/gcsStorage.ts
- src/storage/adapters/r2Storage.ts
- src/storage/adapters/s3CompatibleStorage.ts
- src/storage/adapters/azureBlobStorage.ts
- src/storage/adapters/opfsStorage.ts
- src/storage/adapters/memoryStorage.ts
- src/storage/adapters/historicalStorageAdapter.ts
- src/brainy.ts (call sites)
- src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites)
The default-delegation wrappers added in scaffold step 4 are removed
(would have been duplicate declarations after the rename).
REMOVED — PHASE C (cache category 'hnsw')
src/utils/unifiedCache.ts
- Cache-category union narrowed from
'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other'
to
'vectors' | 'metadata' | 'embedding' | 'other'
- typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios,
and the fairness-check iterator all drop the 'hnsw' key.
- Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory
category. Cache is rebuildable; entries naturally don't exist after
a restart, so no migration path is needed.
src/hnsw/hnswIndex.ts
- 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'.
- Cache key prefix `hnsw:vector:` → `vector:`.
- typeCounts/typeSizes/typeAccessCounts accessor renames
`.hnsw` → `.vectors`.
tests/unit/utils/unifiedCache-eviction.test.ts
- Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...).
- Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`.
REMOVED — PHASE D (config.hnsw)
src/types/brainy.types.ts
- Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is
`BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`.
src/brainy.ts
- normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>.
- setupIndex() rewired:
- Reads from `this.config.vector` (not `this.config.hnsw`).
- Calls `resolveJsHnswConfig(this.config.vector)` to translate the
`recall` preset into M / efConstruction / efSearch knobs (with
`advanced.hnsw` overrides winning when supplied).
- Imports `resolveJsHnswConfig` from './utils/recallPreset.js'.
NOT IN THIS COMMIT (deliberately)
- Persisted file path migration `_system/hnsw-*.json` →
`_system/vector-index-*.json`. Requires dual-read logic on boot
across 8 storage adapters. Per integration doc lines 531-539:
"Reader accepts either spelling on load; writer emits the new spelling
only; brains self-migrate on the next persist after upgrade." This is
a separate body of work and lands in a follow-up commit before 8.0 GA.
- `config.hnswPersistMode` top-level field is unchanged. It's not in
the rename inventory; a future commit can fold it into
`config.vector.persistMode` if desired.
- strictConfig enforcement wiring. The field is accepted by
normalizeConfig() with default 'warn'; the actual warning emission at
knob-mismatch sites lands when the config-resolution layer is touched
for the persisted-path migration.
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit (one test fixture updated to use the new
category name; assertion still passes after fixture rename)
- npm run build: clean
The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end:
VectorIndexProvider, config.vector.recall, JsHnswVectorIndex,
saveVectorIndexData, 'vectors' cache category, indexHealth.vector,
strictConfig — all standalone. No legacy 'hnsw' name remains in any
public type or method signature.
2026-06-09 13:28:53 -07:00
const typeSizes = { vectors : 0 , metadata : 0 , embedding : 0 , other : 0 }
const typeCounts = { vectors : 0 , metadata : 0 , embedding : 0 , other : 0 }
2025-08-26 12:32:21 -07:00
for ( const item of this . cache . values ( ) ) {
typeSizes [ item . type ] += item . size
typeCounts [ item . type ] ++
}
2025-10-10 14:09:30 -07:00
const hitRate = this . cache . size > 0 ?
Array . from ( this . cache . values ( ) ) . reduce ( ( sum , item ) = > sum + item . accessCount , 0 ) / this . totalAccessCount : 0
2025-08-26 12:32:21 -07:00
return {
2025-10-10 14:09:30 -07:00
// Cache statistics
2025-08-26 12:32:21 -07:00
totalSize : this.currentSize ,
maxSize : this.maxSize ,
utilization : this.currentSize / this . maxSize ,
itemCount : this.cache.size ,
typeSizes ,
typeCounts ,
typeAccessCounts : this.typeAccessCounts ,
totalAccessCount : this.totalAccessCount ,
2025-10-10 14:09:30 -07:00
hitRate ,
2026-01-27 15:38:21 -08:00
// Memory management
2025-10-10 14:09:30 -07:00
memory : {
available : this.memoryInfo.available ,
source : this.memoryInfo.source ,
isContainer : this.memoryInfo.isContainer ,
systemTotal : this.memoryInfo.systemTotal ,
allocationRatio : this.allocationStrategy.ratio ,
environment : this.allocationStrategy.environment
}
}
}
/ * *
* Get detailed memory information
* /
getMemoryInfo() {
return {
memoryInfo : { . . . this . memoryInfo } ,
allocationStrategy : { . . . this . allocationStrategy } ,
currentPressure : checkMemoryPressure ( this . currentSize , this . memoryInfo )
2025-08-26 12:32:21 -07:00
}
}
/ * *
* Save access patterns for cold start optimization
* /
async saveAccessPatterns ( ) : Promise < any > {
if ( ! this . config . persistPatterns ) return
const patterns = Array . from ( this . cache . entries ( ) )
. map ( ( [ key , item ] ) = > ( {
key ,
type : item . type ,
accessCount : this.access.get ( key ) || 0 ,
size : item.size ,
rebuildCost : item.rebuildCost
} ) )
. sort ( ( a , b ) = > b . accessCount - a . accessCount )
return {
patterns ,
typeAccessCounts : this.typeAccessCounts ,
timestamp : Date.now ( )
}
}
/ * *
* Load access patterns for warm start
* /
async loadAccessPatterns ( patterns : any ) : Promise < void > {
if ( ! patterns ? . patterns ) return
// Pre-populate access counts
for ( const pattern of patterns . patterns ) {
this . access . set ( pattern . key , pattern . accessCount )
}
// Restore type access counts
if ( patterns . typeAccessCounts ) {
this . typeAccessCounts = patterns . typeAccessCounts
}
prodLog . debug ( 'Loaded access patterns:' , patterns . patterns . length , 'items' )
}
}
// Export singleton for global coordination
let globalCache : UnifiedCache | null = null
export function getGlobalCache ( config? : UnifiedCacheConfig ) : UnifiedCache {
if ( ! globalCache ) {
globalCache = new UnifiedCache ( config )
}
return globalCache
}
feat: wire plugin system with provider resolution, storage factories, and browser deprecation
- Wire PluginRegistry into Brainy init() with provider resolution for distance,
metadataIndex, graphIndex, embeddings, roaring, msgpack, and storage adapters
- Add setupStorage() factory that resolves storage:* providers from plugins before
falling back to built-in createStorage()
- Export internals API (setGlobalCache, UnifiedCache, EntityIdMapper, etc.) for
cortex plugin consumption
- Add plugin.test.ts verifying registration, activation, and provider resolution
- Deprecate browser support (OPFS, Web Workers, WASM embeddings) with warnings
in preparation for v8.0 server-only release
- FileSystemStorage: fix setupStorage resolution for mmap-filesystem provider
2026-01-31 12:02:13 -08:00
export function setGlobalCache ( cache : UnifiedCache ) : void {
globalCache = cache
}
2025-08-26 12:32:21 -07:00
export function clearGlobalCache ( ) : void {
if ( globalCache ) {
globalCache . clear ( )
globalCache = null
}
}