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.
This commit is contained in:
parent
46fc7f2c4a
commit
4b6f63ed67
6 changed files with 227 additions and 7 deletions
|
|
@ -12,6 +12,7 @@ import { GraphVerb, StorageAdapter } from '../coreTypes.js'
|
||||||
import { UnifiedCache, getGlobalCache } from '../utils/unifiedCache.js'
|
import { UnifiedCache, getGlobalCache } from '../utils/unifiedCache.js'
|
||||||
import { prodLog } from '../utils/logger.js'
|
import { prodLog } from '../utils/logger.js'
|
||||||
import { LSMTree } from './lsm/LSMTree.js'
|
import { LSMTree } from './lsm/LSMTree.js'
|
||||||
|
import type { GraphIndexProvider } from '../plugin.js'
|
||||||
|
|
||||||
export interface GraphIndexConfig {
|
export interface GraphIndexConfig {
|
||||||
maxIndexSize?: number // Default: 100000
|
maxIndexSize?: number // Default: 100000
|
||||||
|
|
@ -36,7 +37,7 @@ export interface GraphIndexStats {
|
||||||
* Memory efficient: 385x less memory (1.3GB vs 500GB for 1B relationships)
|
* Memory efficient: 385x less memory (1.3GB vs 500GB for 1B relationships)
|
||||||
* Performance: Sub-5ms neighbor lookups with bloom filter optimization
|
* Performance: Sub-5ms neighbor lookups with bloom filter optimization
|
||||||
*/
|
*/
|
||||||
export class GraphAdjacencyIndex {
|
export class GraphAdjacencyIndex implements GraphIndexProvider {
|
||||||
// LSM-tree storage for outgoing and incoming edges
|
// LSM-tree storage for outgoing and incoming edges
|
||||||
private lsmTreeSource: LSMTree // sourceId -> targetIds (outgoing edges)
|
private lsmTreeSource: LSMTree // sourceId -> targetIds (outgoing edges)
|
||||||
private lsmTreeTarget: LSMTree // targetId -> sourceIds (incoming edges)
|
private lsmTreeTarget: LSMTree // targetId -> sourceIds (incoming edges)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { getGlobalCache, UnifiedCache } from '../utils/unifiedCache.js'
|
||||||
import { prodLog } from '../utils/logger.js'
|
import { prodLog } from '../utils/logger.js'
|
||||||
import { quantizeSQ8, distanceSQ8 } from '../utils/vectorQuantization.js'
|
import { quantizeSQ8, distanceSQ8 } from '../utils/vectorQuantization.js'
|
||||||
import type { SQ8QuantizedVector } from '../utils/vectorQuantization.js'
|
import type { SQ8QuantizedVector } from '../utils/vectorQuantization.js'
|
||||||
|
import type { HnswProvider } from '../plugin.js'
|
||||||
|
|
||||||
// Default HNSW parameters
|
// Default HNSW parameters
|
||||||
const DEFAULT_CONFIG: HNSWConfig = {
|
const DEFAULT_CONFIG: HNSWConfig = {
|
||||||
|
|
@ -26,7 +27,12 @@ const DEFAULT_CONFIG: HNSWConfig = {
|
||||||
ml: 16 // Max level
|
ml: 16 // Max level
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HNSWIndex {
|
/**
|
||||||
|
* Implements {@link HnswProvider}: the vector-index surface Brainy calls on
|
||||||
|
* whatever the `'hnsw'` factory returns (its own `HNSWIndex`, or Cortex's
|
||||||
|
* native engine).
|
||||||
|
*/
|
||||||
|
export class HNSWIndex implements HnswProvider {
|
||||||
private nouns: Map<string, HNSWNoun> = new Map()
|
private nouns: Map<string, HNSWNoun> = new Map()
|
||||||
private entryPointId: string | null = null
|
private entryPointId: string | null = null
|
||||||
private maxLevel = 0
|
private maxLevel = 0
|
||||||
|
|
|
||||||
197
src/plugin.ts
197
src/plugin.ts
|
|
@ -8,7 +8,28 @@
|
||||||
* Plugins are auto-detected by package name or registered manually.
|
* Plugins are auto-detected by package name or registered manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { StorageAdapter } from './coreTypes.js'
|
import type {
|
||||||
|
StorageAdapter,
|
||||||
|
Vector,
|
||||||
|
VectorDocument,
|
||||||
|
GraphVerb,
|
||||||
|
} from './coreTypes.js'
|
||||||
|
import type { NounType, VerbType } from './types/graphTypes.js'
|
||||||
|
import type { MetadataIndexStats } from './utils/metadataIndex.js'
|
||||||
|
import type { GraphIndexStats } from './graph/graphAdjacencyIndex.js'
|
||||||
|
|
||||||
|
// Re-export the provider contracts that already live closer to their
|
||||||
|
// implementations so a plugin author (Cortex) can import the *entire*
|
||||||
|
// provider surface from one stable entrypoint: `@soulcraft/brainy/plugin`.
|
||||||
|
export type { ColumnStoreProvider } from './indexes/columnStore/types.js'
|
||||||
|
export type {
|
||||||
|
AggregationProvider,
|
||||||
|
AggregateDefinition,
|
||||||
|
AggregateGroupState,
|
||||||
|
AggregateResult,
|
||||||
|
AggregateQueryParams,
|
||||||
|
GroupByDimension,
|
||||||
|
} from './types/brainy.types.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin interface — all brainy plugins must implement this.
|
* Plugin interface — all brainy plugins must implement this.
|
||||||
|
|
@ -58,6 +79,180 @@ export interface BrainyPluginContext {
|
||||||
readonly version: string
|
readonly version: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Provider contracts — the EXACT surface Brainy calls on each registered
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// These interfaces are the type-level half of the provider-parity guarantee.
|
||||||
|
// They capture only what Brainy actually invokes, so a native accelerator
|
||||||
|
// (Cortex) that declares `implements MetadataIndexProvider` gets a compile
|
||||||
|
// error the moment a method Brainy depends on is dropped or its signature
|
||||||
|
// drifts. Brainy's own baseline classes (`MetadataIndexManager`,
|
||||||
|
// `GraphAdjacencyIndex`, `HNSWIndex`, `EntityIdMapper`, `UnifiedCache`)
|
||||||
|
// implement them too, so the contract can never silently diverge from the
|
||||||
|
// thing Brainy ships.
|
||||||
|
//
|
||||||
|
// Keep these in lockstep with the call sites in `brainy.ts` and the index
|
||||||
|
// classes. When Brainy starts calling a new member, add it here — every
|
||||||
|
// implementation then fails to compile until it provides the member.
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `'metadataIndex'` provider — a drop-in for `MetadataIndexManager`.
|
||||||
|
* Brainy calls this surface via `this.metadataIndex.*` (see `brainy.ts`) and
|
||||||
|
* the transactional add/remove operations.
|
||||||
|
*/
|
||||||
|
export interface MetadataIndexProvider {
|
||||||
|
init(): Promise<void>
|
||||||
|
flush(): Promise<void>
|
||||||
|
rebuild(): Promise<void>
|
||||||
|
|
||||||
|
addToIndex(id: string, entityOrMetadata: any, skipFlush?: boolean, deferWrites?: boolean): Promise<void>
|
||||||
|
removeFromIndex(id: string, metadata?: any): Promise<void>
|
||||||
|
|
||||||
|
getIds(field: string, value: any): Promise<string[]>
|
||||||
|
getIdsForFilter(filter: any): Promise<string[]>
|
||||||
|
getIdsForTextQuery(query: string): Promise<Array<{ id: string; matchCount: number }>>
|
||||||
|
getSortedIdsForFilter(filter: any, orderBy: string, order?: 'asc' | 'desc'): Promise<string[]>
|
||||||
|
getFilterValues(field: string): Promise<string[]>
|
||||||
|
getFilterFields(): Promise<string[]>
|
||||||
|
getFieldValueForEntity(entityId: string, field: string): Promise<any>
|
||||||
|
getFieldsForType(nounType: NounType): Promise<Array<{ field: string; affinity: number; occurrences: number; totalEntities: number }>>
|
||||||
|
getFieldStatistics(): Promise<Map<string, unknown>>
|
||||||
|
getFieldsWithCardinality(): Promise<Array<{ field: string; cardinality: number; distribution: string }>>
|
||||||
|
getOptimalQueryPlan(filters: Record<string, any>): Promise<unknown>
|
||||||
|
|
||||||
|
/** Report which index path a `where` clause on `field` will hit (drives `brain.explain()`). */
|
||||||
|
explainField(field: string): Promise<{ path: 'column-store' | 'sparse-chunked' | 'none'; notes?: string }>
|
||||||
|
|
||||||
|
getCountForCriteria(field: string, value: any): Promise<number>
|
||||||
|
getEntityCountByType(type: string): number
|
||||||
|
getEntityCountByTypeEnum(type: NounType): number
|
||||||
|
getTotalEntityCount(): number
|
||||||
|
getAllEntityCounts(): Map<string, number>
|
||||||
|
getTopNounTypes(n: number): NounType[]
|
||||||
|
getTopVerbTypes(n: number): VerbType[]
|
||||||
|
getAllNounTypeCounts(): Map<NounType, number>
|
||||||
|
getAllVerbTypeCounts(): Map<VerbType, number>
|
||||||
|
getAllVFSEntityCounts(): Promise<Map<string, number>>
|
||||||
|
|
||||||
|
detectAndRepairCorruption(): Promise<void>
|
||||||
|
validateConsistency(): Promise<{
|
||||||
|
healthy: boolean
|
||||||
|
avgEntriesPerEntity: number
|
||||||
|
entityCount: number
|
||||||
|
indexEntryCount: number
|
||||||
|
recommendation: string | null
|
||||||
|
}>
|
||||||
|
|
||||||
|
tokenize(text: string): string[]
|
||||||
|
extractTextContent(data: any): string
|
||||||
|
|
||||||
|
getStats(): Promise<MetadataIndexStats>
|
||||||
|
|
||||||
|
/** The shared UUID ↔ int mapper. Brainy reads `.getUuid(intId)` off the result. */
|
||||||
|
getIdMapper(): { getUuid(intId: number): string | undefined }
|
||||||
|
|
||||||
|
/** The column store the coordinator delegates `where`/`orderBy` to. */
|
||||||
|
readonly columnStore: import('./indexes/columnStore/types.js').ColumnStoreProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `'graphIndex'` provider — a drop-in for `GraphAdjacencyIndex`.
|
||||||
|
* Brainy calls this surface via `this.graphIndex.*` (some optional-chained).
|
||||||
|
*/
|
||||||
|
export interface GraphIndexProvider {
|
||||||
|
/** `false` until the index has loaded; Brainy probes this before fast paths. */
|
||||||
|
readonly isInitialized: boolean
|
||||||
|
|
||||||
|
getNeighbors(
|
||||||
|
id: string,
|
||||||
|
optionsOrDirection?: { direction?: 'in' | 'out' | 'both'; limit?: number; offset?: number } | 'in' | 'out' | 'both'
|
||||||
|
): Promise<string[]>
|
||||||
|
getVerbIdsBySource(sourceId: string, options?: { limit?: number; offset?: number }): Promise<string[]>
|
||||||
|
getVerbIdsByTarget(targetId: string, options?: { limit?: number; offset?: number }): Promise<string[]>
|
||||||
|
getVerbsBatchCached(verbIds: string[]): Promise<Map<string, GraphVerb>>
|
||||||
|
|
||||||
|
rebuild(): Promise<void>
|
||||||
|
flush(): Promise<void>
|
||||||
|
close(): Promise<void>
|
||||||
|
size(): number
|
||||||
|
|
||||||
|
getStats(): GraphIndexStats
|
||||||
|
getRelationshipStats(): {
|
||||||
|
totalRelationships: number
|
||||||
|
relationshipsByType: Record<string, number>
|
||||||
|
uniqueSourceNodes: number
|
||||||
|
uniqueTargetNodes: number
|
||||||
|
totalNodes: number
|
||||||
|
}
|
||||||
|
getRelationshipCountByType(type: string): number
|
||||||
|
getTotalRelationshipCount(): number
|
||||||
|
getAllRelationshipCounts(): Map<string, number>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object returned by the `'hnsw'` provider factory — a drop-in for
|
||||||
|
* `HNSWIndex`. Brainy calls this surface via `this.index.*` plus the
|
||||||
|
* transactional add/remove operations. `enableCOW`, `getItem`, and
|
||||||
|
* `setPersistMode` are intentionally absent: Brainy guards each with
|
||||||
|
* feature-detection (`typeof x === 'function'`), so they are optional and not
|
||||||
|
* part of the required contract (Brainy's own `HNSWIndex` omits
|
||||||
|
* `setPersistMode`, for instance).
|
||||||
|
*/
|
||||||
|
export interface HnswProvider {
|
||||||
|
addItem(item: VectorDocument): Promise<string>
|
||||||
|
removeItem(id: string): Promise<boolean>
|
||||||
|
search(
|
||||||
|
queryVector: Vector,
|
||||||
|
k?: number,
|
||||||
|
filter?: (id: string) => Promise<boolean>,
|
||||||
|
options?: { rerank?: { multiplier: number }; candidateIds?: string[] }
|
||||||
|
): Promise<Array<[string, number]>>
|
||||||
|
size(): number
|
||||||
|
clear(): void
|
||||||
|
rebuild(options?: any): Promise<void>
|
||||||
|
flush(): Promise<number>
|
||||||
|
getPersistMode(): 'immediate' | 'deferred'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `'entityIdMapper'` provider — a drop-in for `EntityIdMapper`. Injected
|
||||||
|
* into the TypeScript `MetadataIndexManager` when a native metadata index is
|
||||||
|
* not also registered; that coordinator calls this full surface (incl.
|
||||||
|
* `getAllIntIds`, the all-ids universe for negation / `exists:false` filters).
|
||||||
|
*/
|
||||||
|
export interface EntityIdMapperProvider {
|
||||||
|
init(): Promise<void>
|
||||||
|
getOrAssign(uuid: string): number
|
||||||
|
getUuid(intId: number): string | undefined
|
||||||
|
getInt(uuid: string): number | undefined
|
||||||
|
remove(uuid: string): boolean
|
||||||
|
flush(): Promise<void>
|
||||||
|
clear(): Promise<void>
|
||||||
|
getAllIntIds(): number[]
|
||||||
|
intsIterableToUuids(ints: Iterable<number>): string[]
|
||||||
|
readonly size: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `'cache'` provider — a drop-in for `UnifiedCache`. Brainy installs it as
|
||||||
|
* the global cache (`setGlobalCache`) and calls this surface via
|
||||||
|
* `getGlobalCache()`.
|
||||||
|
*/
|
||||||
|
export interface CacheProvider {
|
||||||
|
getSync(key: string): any | undefined
|
||||||
|
set(key: string, data: any, type: 'hnsw' | 'metadata' | 'embedding' | 'other', size: number, rebuildCost?: number): void
|
||||||
|
delete(key: string): boolean
|
||||||
|
deleteByPrefix(prefix: string): number
|
||||||
|
clear(type?: 'hnsw' | 'metadata' | 'embedding' | 'other'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `'embeddings'` / `'embedBatch'` providers are function-shaped and already
|
||||||
|
// typed by the existing `EmbeddingFunction` (see `coreTypes.ts`), which Brainy
|
||||||
|
// uses at the `getProvider('embeddings')` call site. No separate interface is
|
||||||
|
// added here to avoid a duplicate, unwired contract.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage adapter factory — plugins register these to provide
|
* Storage adapter factory — plugins register these to provide
|
||||||
* new storage backends that users reference by name.
|
* new storage backends that users reference by name.
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { StorageAdapter } from '../coreTypes.js'
|
import type { StorageAdapter } from '../coreTypes.js'
|
||||||
|
import type { EntityIdMapperProvider } from '../plugin.js'
|
||||||
|
|
||||||
export interface EntityIdMapperOptions {
|
export interface EntityIdMapperOptions {
|
||||||
storage: StorageAdapter
|
storage: StorageAdapter
|
||||||
|
|
@ -27,9 +28,12 @@ export interface EntityIdMapperData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps entity UUIDs to integer IDs for use with Roaring Bitmaps
|
* Maps entity UUIDs to integer IDs for use with Roaring Bitmaps.
|
||||||
|
*
|
||||||
|
* Implements {@link EntityIdMapperProvider}: the surface a registered
|
||||||
|
* `'entityIdMapper'` provider (e.g. Cortex's native mapper) must also satisfy.
|
||||||
*/
|
*/
|
||||||
export class EntityIdMapper {
|
export class EntityIdMapper implements EntityIdMapperProvider {
|
||||||
private storage: StorageAdapter
|
private storage: StorageAdapter
|
||||||
private storageKey: string
|
private storageKey: string
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { StorageAdapter, resolveEntityField } from '../coreTypes.js'
|
import { StorageAdapter, resolveEntityField } from '../coreTypes.js'
|
||||||
import { ColumnStore } from '../indexes/columnStore/ColumnStore.js'
|
import { ColumnStore } from '../indexes/columnStore/ColumnStore.js'
|
||||||
|
import type { MetadataIndexProvider } from '../plugin.js'
|
||||||
import { MetadataIndexCache, MetadataIndexCacheConfig } from './metadataIndexCache.js'
|
import { MetadataIndexCache, MetadataIndexCacheConfig } from './metadataIndexCache.js'
|
||||||
import { compareCodePoints } from './collation.js'
|
import { compareCodePoints } from './collation.js'
|
||||||
import { prodLog } from './logger.js'
|
import { prodLog } from './logger.js'
|
||||||
|
|
@ -103,7 +104,12 @@ interface FieldStats {
|
||||||
normalizationStrategy?: 'none' | 'precision' | 'bucket'
|
normalizationStrategy?: 'none' | 'precision' | 'bucket'
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MetadataIndexManager {
|
/**
|
||||||
|
* Implements {@link MetadataIndexProvider}: the metadata-index surface Brainy
|
||||||
|
* calls on whatever the `'metadataIndex'` provider resolves to (its own
|
||||||
|
* manager, or Cortex's native Rust engine).
|
||||||
|
*/
|
||||||
|
export class MetadataIndexManager implements MetadataIndexProvider {
|
||||||
private storage: StorageAdapter
|
private storage: StorageAdapter
|
||||||
private config: Required<MetadataIndexConfig>
|
private config: Required<MetadataIndexConfig>
|
||||||
private isRebuilding = false
|
private isRebuilding = false
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
type MemoryInfo,
|
type MemoryInfo,
|
||||||
type CacheAllocationStrategy
|
type CacheAllocationStrategy
|
||||||
} from './memoryDetection.js'
|
} from './memoryDetection.js'
|
||||||
|
import type { CacheProvider } from '../plugin.js'
|
||||||
|
|
||||||
export interface CacheItem {
|
export interface CacheItem {
|
||||||
key: string
|
key: string
|
||||||
|
|
@ -57,7 +58,14 @@ export interface UnifiedCacheConfig {
|
||||||
memoryCheckInterval?: number
|
memoryCheckInterval?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedCache {
|
/**
|
||||||
|
* 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 {
|
||||||
private cache = new Map<string, CacheItem>()
|
private cache = new Map<string, CacheItem>()
|
||||||
private access = new Map<string, number>() // Access counts
|
private access = new Map<string, number>() // Access counts
|
||||||
private loadingPromises = new Map<string, Promise<any>>()
|
private loadingPromises = new Map<string, Promise<any>>()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue