feat: add plugin system for cortex and storage adapters
Simple plugin architecture with two use cases: 1. Native acceleration (@soulcraft/brainy-cortex) — auto-detected 2. Custom storage adapters (e.g., Redis, DynamoDB) Plugins implement BrainyPlugin interface with activate/deactivate lifecycle. Auto-detection tries dynamic import of known packages during init(). Manual registration via brain.use(plugin) before init(). Provider keys: metadataIndex, graphIndex, entityIdMapper, cache, hnsw, roaring, embeddings, distance, msgpack, storage:<name>
This commit is contained in:
parent
35cb674157
commit
cc50ac3776
3 changed files with 246 additions and 1 deletions
|
|
@ -13,7 +13,8 @@ import { BaseStorage } from './storage/baseStorage.js'
|
||||||
import { StorageAdapter, Vector, DistanceFunction, EmbeddingFunction, GraphVerb } from './coreTypes.js'
|
import { StorageAdapter, Vector, DistanceFunction, EmbeddingFunction, GraphVerb } from './coreTypes.js'
|
||||||
import {
|
import {
|
||||||
defaultEmbeddingFunction,
|
defaultEmbeddingFunction,
|
||||||
cosineDistance
|
cosineDistance,
|
||||||
|
getBrainyVersion
|
||||||
} from './utils/index.js'
|
} from './utils/index.js'
|
||||||
import { embeddingManager } from './embeddings/EmbeddingManager.js'
|
import { embeddingManager } from './embeddings/EmbeddingManager.js'
|
||||||
import { matchesMetadataFilter } from './utils/metadataFilter.js'
|
import { matchesMetadataFilter } from './utils/metadataFilter.js'
|
||||||
|
|
@ -33,6 +34,8 @@ import { BlobStorage } from './storage/cow/BlobStorage.js'
|
||||||
import { NULL_HASH } from './storage/cow/constants.js'
|
import { NULL_HASH } from './storage/cow/constants.js'
|
||||||
import { createPipeline } from './streaming/pipeline.js'
|
import { createPipeline } from './streaming/pipeline.js'
|
||||||
import { configureLogger, LogLevel } from './utils/logger.js'
|
import { configureLogger, LogLevel } from './utils/logger.js'
|
||||||
|
import { PluginRegistry } from './plugin.js'
|
||||||
|
import type { BrainyPlugin, BrainyPluginContext } from './plugin.js'
|
||||||
import { TransactionManager } from './transaction/TransactionManager.js'
|
import { TransactionManager } from './transaction/TransactionManager.js'
|
||||||
import {
|
import {
|
||||||
ValidationConfig,
|
ValidationConfig,
|
||||||
|
|
@ -142,6 +145,9 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
||||||
error: typeof console.error
|
error: typeof console.error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plugin system
|
||||||
|
private pluginRegistry = new PluginRegistry()
|
||||||
|
|
||||||
// Sub-APIs (lazy-loaded)
|
// Sub-APIs (lazy-loaded)
|
||||||
private _neural?: ImprovedNeuralAPI
|
private _neural?: ImprovedNeuralAPI
|
||||||
private _nlp?: NaturalLanguageProcessor
|
private _nlp?: NaturalLanguageProcessor
|
||||||
|
|
@ -267,6 +273,9 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
||||||
(this.storage as any).enableCOWLightweight((this.config.storage as any)?.branch || 'main')
|
(this.storage as any).enableCOWLightweight((this.config.storage as any)?.branch || 'main')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-detect and activate plugins (e.g., @soulcraft/brainy-cortex)
|
||||||
|
await this.loadPlugins()
|
||||||
|
|
||||||
// Setup index now that we have storage
|
// Setup index now that we have storage
|
||||||
this.index = this.setupIndex()
|
this.index = this.setupIndex()
|
||||||
|
|
||||||
|
|
@ -6534,6 +6543,51 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
||||||
await this.metadataIndex.detectAndRepairCorruption()
|
await this.metadataIndex.detectAndRepairCorruption()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a plugin manually.
|
||||||
|
*
|
||||||
|
* Must be called BEFORE init(). Plugins registered after init()
|
||||||
|
* will not be activated.
|
||||||
|
*/
|
||||||
|
use(plugin: BrainyPlugin): this {
|
||||||
|
this.pluginRegistry.register(plugin)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of active plugin names.
|
||||||
|
*/
|
||||||
|
getActivePlugins(): string[] {
|
||||||
|
return this.pluginRegistry.getActivePlugins()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-detect and activate plugins.
|
||||||
|
* Called internally during init().
|
||||||
|
*/
|
||||||
|
private async loadPlugins(): Promise<void> {
|
||||||
|
// Auto-detect installed plugins (e.g., @soulcraft/brainy-cortex)
|
||||||
|
const pluginPackages = (this.config as any).plugins as string[] | undefined
|
||||||
|
await this.pluginRegistry.autoDetect(pluginPackages || [])
|
||||||
|
|
||||||
|
// Create plugin context
|
||||||
|
const context: BrainyPluginContext = {
|
||||||
|
registerProvider: (key, impl) => this.pluginRegistry.registerProvider(key, impl),
|
||||||
|
version: getBrainyVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate all registered plugins
|
||||||
|
const activated = await this.pluginRegistry.activateAll(context)
|
||||||
|
if (activated.length > 0) {
|
||||||
|
// Only log if not in silent mode
|
||||||
|
if (!this.config.silent) {
|
||||||
|
for (const name of activated) {
|
||||||
|
console.log(`[brainy] Plugin activated: ${name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close and cleanup
|
* Close and cleanup
|
||||||
*
|
*
|
||||||
|
|
@ -6547,6 +6601,9 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
||||||
await this.index.flush()
|
await this.index.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deactivate plugins
|
||||||
|
await this.pluginRegistry.deactivateAll()
|
||||||
|
|
||||||
// Shutdown augmentations
|
// Shutdown augmentations
|
||||||
const augs = this.augmentationRegistry.getAll()
|
const augs = this.augmentationRegistry.getAll()
|
||||||
for (const aug of augs) {
|
for (const aug of augs) {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,10 @@ export {
|
||||||
// Export version utilities
|
// Export version utilities
|
||||||
export { getBrainyVersion } from './utils/version.js'
|
export { getBrainyVersion } from './utils/version.js'
|
||||||
|
|
||||||
|
// Export plugin system
|
||||||
|
export type { BrainyPlugin, BrainyPluginContext, StorageAdapterFactory } from './plugin.js'
|
||||||
|
export { PluginRegistry } from './plugin.js'
|
||||||
|
|
||||||
// Export embedding functionality
|
// Export embedding functionality
|
||||||
import {
|
import {
|
||||||
UniversalSentenceEncoder,
|
UniversalSentenceEncoder,
|
||||||
|
|
|
||||||
184
src/plugin.ts
Normal file
184
src/plugin.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
/**
|
||||||
|
* Brainy Plugin System
|
||||||
|
*
|
||||||
|
* Simple plugin architecture for two use cases:
|
||||||
|
* 1. Native acceleration (@soulcraft/brainy-cortex)
|
||||||
|
* 2. Custom storage adapters (e.g., Redis, DynamoDB, custom backends)
|
||||||
|
*
|
||||||
|
* Plugins are auto-detected by package name or registered manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { StorageAdapter } from './coreTypes.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin interface — all brainy plugins must implement this.
|
||||||
|
*/
|
||||||
|
export interface BrainyPlugin {
|
||||||
|
/** Unique plugin name (typically the npm package name) */
|
||||||
|
name: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by brainy during init() to activate the plugin.
|
||||||
|
* Return true if activation succeeded, false to skip.
|
||||||
|
*/
|
||||||
|
activate(context: BrainyPluginContext): Promise<boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when brainy.close() is invoked. Optional cleanup.
|
||||||
|
*/
|
||||||
|
deactivate?(): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context passed to plugins during activation.
|
||||||
|
*/
|
||||||
|
export interface BrainyPluginContext {
|
||||||
|
/**
|
||||||
|
* Register a provider for a named subsystem.
|
||||||
|
*
|
||||||
|
* Well-known provider keys (used by cortex):
|
||||||
|
* - 'metadataIndex' — MetadataIndexManager replacement
|
||||||
|
* - 'graphIndex' — GraphAdjacencyIndex replacement
|
||||||
|
* - 'entityIdMapper' — EntityIdMapper replacement
|
||||||
|
* - 'cache' — UnifiedCache replacement
|
||||||
|
* - 'hnsw' — HNSWIndex replacement
|
||||||
|
* - 'roaring' — RoaringBitmap32 replacement
|
||||||
|
* - 'embeddings' — Embedding engine replacement
|
||||||
|
* - 'distance' — Distance function overrides
|
||||||
|
* - 'msgpack' — Msgpack encode/decode
|
||||||
|
*
|
||||||
|
* Storage adapter keys:
|
||||||
|
* - 'storage:<name>' — Custom storage adapter factory
|
||||||
|
*/
|
||||||
|
registerProvider(key: string, implementation: unknown): void
|
||||||
|
|
||||||
|
/** Brainy version for compatibility checks */
|
||||||
|
readonly version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage adapter factory — plugins register these to provide
|
||||||
|
* new storage backends that users reference by name.
|
||||||
|
*
|
||||||
|
* Example: A Redis plugin registers 'storage:redis', then users
|
||||||
|
* can use `new Brainy({ storage: 'redis', redis: { host: '...' } })`
|
||||||
|
*/
|
||||||
|
export interface StorageAdapterFactory {
|
||||||
|
create(config: Record<string, unknown>): StorageAdapter | Promise<StorageAdapter>
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin registry — manages plugin lifecycle and provider resolution.
|
||||||
|
*/
|
||||||
|
export class PluginRegistry {
|
||||||
|
private plugins: Map<string, BrainyPlugin> = new Map()
|
||||||
|
private providers: Map<string, unknown> = new Map()
|
||||||
|
private activated: Set<string> = new Set()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a plugin manually.
|
||||||
|
*/
|
||||||
|
register(plugin: BrainyPlugin): void {
|
||||||
|
this.plugins.set(plugin.name, plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-detect known plugins by attempting dynamic import.
|
||||||
|
* Additional package names can be passed for third-party plugins.
|
||||||
|
*/
|
||||||
|
async autoDetect(additionalPackages: string[] = []): Promise<void> {
|
||||||
|
const packages = [
|
||||||
|
'@soulcraft/brainy-cortex',
|
||||||
|
...additionalPackages
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const pkg of packages) {
|
||||||
|
try {
|
||||||
|
const mod = await import(pkg)
|
||||||
|
const plugin: BrainyPlugin = mod.default || mod
|
||||||
|
if (plugin && typeof plugin.activate === 'function' && plugin.name) {
|
||||||
|
this.plugins.set(plugin.name, plugin)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Package not installed — skip silently
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate all registered plugins.
|
||||||
|
*/
|
||||||
|
async activateAll(context: BrainyPluginContext): Promise<string[]> {
|
||||||
|
const activated: string[] = []
|
||||||
|
|
||||||
|
for (const [name, plugin] of this.plugins) {
|
||||||
|
if (this.activated.has(name)) continue
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await plugin.activate(context)
|
||||||
|
if (success) {
|
||||||
|
this.activated.add(name)
|
||||||
|
activated.push(name)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[brainy] Plugin ${name} failed to activate:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return activated
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivate all plugins (called during close()).
|
||||||
|
*/
|
||||||
|
async deactivateAll(): Promise<void> {
|
||||||
|
for (const [name, plugin] of this.plugins) {
|
||||||
|
if (!this.activated.has(name)) continue
|
||||||
|
try {
|
||||||
|
await plugin.deactivate?.()
|
||||||
|
this.activated.delete(name)
|
||||||
|
} catch {
|
||||||
|
// Non-fatal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a registered provider by key.
|
||||||
|
*/
|
||||||
|
getProvider<T = unknown>(key: string): T | undefined {
|
||||||
|
return this.providers.get(key) as T | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a provider is registered.
|
||||||
|
*/
|
||||||
|
hasProvider(key: string): boolean {
|
||||||
|
return this.providers.has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a provider (called by plugins via BrainyPluginContext).
|
||||||
|
*/
|
||||||
|
registerProvider(key: string, implementation: unknown): void {
|
||||||
|
this.providers.set(key, implementation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a storage adapter factory by name.
|
||||||
|
*/
|
||||||
|
getStorageFactory(name: string): StorageAdapterFactory | undefined {
|
||||||
|
return this.providers.get(`storage:${name}`) as StorageAdapterFactory | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get active plugin names */
|
||||||
|
getActivePlugins(): string[] {
|
||||||
|
return [...this.activated]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if any plugins are active */
|
||||||
|
hasActivePlugins(): boolean {
|
||||||
|
return this.activated.size > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue