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 {
|
||||
defaultEmbeddingFunction,
|
||||
cosineDistance
|
||||
cosineDistance,
|
||||
getBrainyVersion
|
||||
} from './utils/index.js'
|
||||
import { embeddingManager } from './embeddings/EmbeddingManager.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 { createPipeline } from './streaming/pipeline.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 {
|
||||
ValidationConfig,
|
||||
|
|
@ -142,6 +145,9 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
error: typeof console.error
|
||||
}
|
||||
|
||||
// Plugin system
|
||||
private pluginRegistry = new PluginRegistry()
|
||||
|
||||
// Sub-APIs (lazy-loaded)
|
||||
private _neural?: ImprovedNeuralAPI
|
||||
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')
|
||||
}
|
||||
|
||||
// Auto-detect and activate plugins (e.g., @soulcraft/brainy-cortex)
|
||||
await this.loadPlugins()
|
||||
|
||||
// Setup index now that we have storage
|
||||
this.index = this.setupIndex()
|
||||
|
||||
|
|
@ -6534,6 +6543,51 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
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
|
||||
*
|
||||
|
|
@ -6547,6 +6601,9 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
await this.index.flush()
|
||||
}
|
||||
|
||||
// Deactivate plugins
|
||||
await this.pluginRegistry.deactivateAll()
|
||||
|
||||
// Shutdown augmentations
|
||||
const augs = this.augmentationRegistry.getAll()
|
||||
for (const aug of augs) {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,10 @@ export {
|
|||
// Export version utilities
|
||||
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
|
||||
import {
|
||||
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