2025-08-26 12:32:21 -07:00
|
|
|
# Creating Augmentations for Brainy
|
|
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
> **Updated** - Includes metadata structure changes and type system improvements
|
2025-10-17 12:29:27 -07:00
|
|
|
|
2025-08-26 12:32:21 -07:00
|
|
|
## The BrainyAugmentation Interface
|
|
|
|
|
|
|
|
|
|
Every augmentation implements this simple yet powerful interface:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface BrainyAugmentation {
|
2026-01-27 15:38:21 -08:00
|
|
|
// Identification
|
|
|
|
|
name: string // Unique name for your augmentation
|
|
|
|
|
|
|
|
|
|
// Execution control
|
|
|
|
|
timing: 'before' | 'after' | 'around' | 'replace' // When to execute
|
|
|
|
|
operations: string[] // Which operations to intercept
|
|
|
|
|
priority: number // Execution order (higher = first)
|
|
|
|
|
|
|
|
|
|
// Lifecycle methods
|
|
|
|
|
initialize(context: AugmentationContext): Promise<void>
|
|
|
|
|
execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T>
|
|
|
|
|
shutdown?(): Promise<void> // Optional cleanup
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
## Breaking Changes for Augmentation Developers
|
2025-10-17 12:29:27 -07:00
|
|
|
|
|
|
|
|
### 1. Metadata Structure Separation
|
2026-01-27 15:38:21 -08:00
|
|
|
Brainy introduces strict metadata/vector separation for billion-scale performance:
|
2025-10-17 12:29:27 -07:00
|
|
|
|
|
|
|
|
```typescript
|
2026-01-27 15:38:21 -08:00
|
|
|
// ✅ Metadata has required type field
|
2025-10-17 12:29:27 -07:00
|
|
|
interface NounMetadata {
|
2026-01-27 15:38:21 -08:00
|
|
|
noun: NounType // Required! Must be a valid noun type
|
|
|
|
|
[key: string]: any // Your custom metadata
|
2025-10-17 12:29:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface VerbMetadata {
|
2026-01-27 15:38:21 -08:00
|
|
|
verb: VerbType // Required! Must be a valid verb type
|
|
|
|
|
sourceId: string
|
|
|
|
|
targetId: string
|
|
|
|
|
[key: string]: any
|
2025-10-17 12:29:27 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. Storage Adapter Return Types
|
|
|
|
|
Storage adapters now return different types at different boundaries:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// Internal methods: Pure structures (no metadata)
|
|
|
|
|
abstract _getNoun(id: string): Promise<HNSWNoun | null>
|
|
|
|
|
|
|
|
|
|
// Public API: WithMetadata structures
|
|
|
|
|
abstract getNoun(id: string): Promise<HNSWNounWithMetadata | null>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. Verb Property Renamed
|
|
|
|
|
The verb relationship field changed from `type` to `verb`:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ v3.x
|
|
|
|
|
verb.type === 'relatedTo'
|
|
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
// ✅ Current
|
2025-10-17 12:29:27 -07:00
|
|
|
verb.verb === 'relatedTo'
|
|
|
|
|
```
|
|
|
|
|
|
2025-08-26 12:32:21 -07:00
|
|
|
## Creating a Storage Augmentation
|
|
|
|
|
|
2025-10-17 12:29:27 -07:00
|
|
|
Storage augmentations are special - they provide the storage backend for Brainy.
|
|
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
### Important: Storage Requirements
|
2025-10-17 12:29:27 -07:00
|
|
|
|
|
|
|
|
Your storage adapter MUST:
|
|
|
|
|
1. **Wrap metadata** with required `noun`/`verb` fields
|
|
|
|
|
2. **Return pure structures** from internal `_methods`
|
|
|
|
|
3. **Return WithMetadata types** from public methods
|
2025-08-26 12:32:21 -07:00
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { StorageAugmentation } from 'brainy/augmentations'
|
2025-10-17 12:29:27 -07:00
|
|
|
import { BaseStorageAdapter, HNSWNoun, HNSWNounWithMetadata, NounMetadata } from 'brainy'
|
|
|
|
|
|
|
|
|
|
export class MyCustomStorage extends BaseStorageAdapter {
|
2026-01-27 15:38:21 -08:00
|
|
|
// Internal method: Returns pure structure
|
|
|
|
|
async _getNoun(id: string): Promise<HNSWNoun | null> {
|
|
|
|
|
const data = await this.fetchFromDatabase(id)
|
|
|
|
|
return data ? {
|
|
|
|
|
id: data.id,
|
|
|
|
|
vector: data.vector,
|
|
|
|
|
nounType: data.type
|
|
|
|
|
} : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Public method: Returns WithMetadata structure
|
|
|
|
|
async getNoun(id: string): Promise<HNSWNounWithMetadata | null> {
|
|
|
|
|
const noun = await this._getNoun(id)
|
|
|
|
|
if (!noun) return null
|
|
|
|
|
|
|
|
|
|
// Fetch metadata separately
|
|
|
|
|
const metadata = await this.getNounMetadata(id)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...noun,
|
|
|
|
|
metadata: metadata || { noun: noun.nounType || 'thing' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CRITICAL: Always save with proper metadata structure
|
|
|
|
|
async saveNoun(noun: HNSWNoun, metadata?: NounMetadata): Promise<void> {
|
|
|
|
|
// Validate metadata has required 'noun' field
|
|
|
|
|
if (!metadata?.noun) {
|
|
|
|
|
throw new Error('NounMetadata requires "noun" field')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.database.save({
|
|
|
|
|
id: noun.id,
|
|
|
|
|
vector: noun.vector,
|
|
|
|
|
nounType: noun.nounType,
|
|
|
|
|
metadata: metadata // Stored separately
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-10-17 12:29:27 -07:00
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
|
|
|
|
|
export class MyStorageAugmentation extends StorageAugmentation {
|
2026-01-27 15:38:21 -08:00
|
|
|
private config: MyStorageConfig
|
|
|
|
|
|
|
|
|
|
constructor(config: MyStorageConfig) {
|
|
|
|
|
super()
|
|
|
|
|
this.name = 'my-custom-storage'
|
|
|
|
|
this.config = config
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called during storage resolution phase
|
|
|
|
|
async provideStorage(): Promise<StorageAdapter> {
|
|
|
|
|
const storage = new MyCustomStorage(this.config)
|
|
|
|
|
this.storageAdapter = storage
|
|
|
|
|
return storage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called during augmentation initialization
|
|
|
|
|
protected async onInitialize(): Promise<void> {
|
|
|
|
|
await this.storageAdapter!.init()
|
|
|
|
|
this.log(`Custom storage initialized`)
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Using Your Storage Augmentation
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// Register before brain.init()
|
2025-09-30 17:09:15 -07:00
|
|
|
const brain = new Brainy()
|
2025-08-26 12:32:21 -07:00
|
|
|
brain.augmentations.register(new MyStorageAugmentation({
|
2026-01-27 15:38:21 -08:00
|
|
|
connectionString: 'redis://localhost:6379'
|
2025-08-26 12:32:21 -07:00
|
|
|
}))
|
2026-01-27 15:38:21 -08:00
|
|
|
await brain.init() // Will use your storage!
|
2025-08-26 12:32:21 -07:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Creating a Feature Augmentation
|
|
|
|
|
|
|
|
|
|
Here's a complete example of a caching augmentation:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { BaseAugmentation, BrainyAugmentation } from 'brainy/augmentations'
|
|
|
|
|
|
|
|
|
|
export class CachingAugmentation extends BaseAugmentation {
|
2026-01-27 15:38:21 -08:00
|
|
|
private cache = new Map<string, any>()
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
super()
|
|
|
|
|
this.name = 'smart-cache'
|
|
|
|
|
this.timing = 'around' // Wrap operations
|
|
|
|
|
this.operations = ['search'] // Only cache searches
|
|
|
|
|
this.priority = 50 // Mid-priority
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
|
|
|
|
|
if (operation === 'search') {
|
|
|
|
|
// Check cache
|
|
|
|
|
const cacheKey = JSON.stringify(params)
|
|
|
|
|
if (this.cache.has(cacheKey)) {
|
|
|
|
|
this.log('Cache hit!')
|
|
|
|
|
return this.cache.get(cacheKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute and cache
|
|
|
|
|
const result = await next()
|
|
|
|
|
this.cache.set(cacheKey, result)
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pass through other operations
|
|
|
|
|
return next()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected async onInitialize(): Promise<void> {
|
|
|
|
|
this.log('Cache initialized')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async shutdown(): Promise<void> {
|
|
|
|
|
this.cache.clear()
|
|
|
|
|
await super.shutdown()
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## The Four Timing Modes
|
|
|
|
|
|
|
|
|
|
### 1. `before` - Pre-processing
|
|
|
|
|
```typescript
|
|
|
|
|
timing = 'before'
|
|
|
|
|
async execute(op, params, next) {
|
2026-01-27 15:38:21 -08:00
|
|
|
// Validate/transform input
|
|
|
|
|
const validated = await validate(params)
|
|
|
|
|
return next(validated) // Pass modified params
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
### 2. `after` - Post-processing
|
2025-08-26 12:32:21 -07:00
|
|
|
```typescript
|
|
|
|
|
timing = 'after'
|
|
|
|
|
async execute(op, params, next) {
|
2026-01-27 15:38:21 -08:00
|
|
|
const result = await next()
|
|
|
|
|
// Log, analyze, or modify result
|
|
|
|
|
console.log(`Operation ${op} returned:`, result)
|
|
|
|
|
return result
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. `around` - Wrapping (middleware)
|
|
|
|
|
```typescript
|
|
|
|
|
timing = 'around'
|
|
|
|
|
async execute(op, params, next) {
|
2026-01-27 15:38:21 -08:00
|
|
|
console.log('Starting', op)
|
|
|
|
|
try {
|
|
|
|
|
const result = await next()
|
|
|
|
|
console.log('Success', op)
|
|
|
|
|
return result
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('Failed', op, error)
|
|
|
|
|
throw error
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. `replace` - Complete replacement
|
|
|
|
|
```typescript
|
|
|
|
|
timing = 'replace'
|
|
|
|
|
async execute(op, params, next) {
|
2026-01-27 15:38:21 -08:00
|
|
|
// Don't call next() - replace entirely!
|
|
|
|
|
return myCustomImplementation(params)
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Operations You Can Intercept
|
|
|
|
|
|
|
|
|
|
Common operations in Brainy:
|
|
|
|
|
- `'storage'` - Storage resolution (special)
|
docs: add deprecation warnings for addNoun and addVerb methods
- Add @deprecated JSDoc tags to TypeScript definitions
- Update all documentation examples to use modern add() and relate() API
- Preserve batch operations (addNouns, addVerbs) as they remain current
- Mark deprecated methods in both source and compiled definitions
Migration guide:
- addNoun(data, type, metadata) → add(data, { nounType: type, ...metadata })
- addVerb(source, target, type, metadata) → relate(source, target, type, metadata)
2025-09-17 10:48:41 -07:00
|
|
|
- `'add'` - Adding data
|
2025-08-26 12:32:21 -07:00
|
|
|
- `'search'`, `'similar'` - Searching
|
|
|
|
|
- `'update'`, `'delete'` - Modifications
|
|
|
|
|
- `'saveNoun'`, `'saveVerb'` - Storage operations
|
|
|
|
|
- `'all'` - Intercept everything
|
|
|
|
|
|
|
|
|
|
## Context Available to Augmentations
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface AugmentationContext {
|
2026-01-27 15:38:21 -08:00
|
|
|
brain: Brainy // The brain instance
|
|
|
|
|
storage: StorageAdapter // Storage backend
|
|
|
|
|
config: BrainyConfig // Configuration
|
|
|
|
|
log: (message: string, level?: 'info' | 'warn' | 'error') => void
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Real-World Examples
|
|
|
|
|
|
|
|
|
|
### 1. Redis Storage Augmentation
|
|
|
|
|
```typescript
|
|
|
|
|
export class RedisStorageAugmentation extends StorageAugmentation {
|
2026-01-27 15:38:21 -08:00
|
|
|
async provideStorage(): Promise<StorageAdapter> {
|
|
|
|
|
return new RedisAdapter({
|
|
|
|
|
host: 'localhost',
|
|
|
|
|
port: 6379,
|
|
|
|
|
// Implement full StorageAdapter interface
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. Audit Trail Augmentation
|
|
|
|
|
```typescript
|
|
|
|
|
export class AuditAugmentation extends BaseAugmentation {
|
2026-01-27 15:38:21 -08:00
|
|
|
timing = 'after'
|
|
|
|
|
operations = ['add', 'update', 'delete']
|
|
|
|
|
|
|
|
|
|
async execute(op, params, next) {
|
|
|
|
|
const result = await next()
|
|
|
|
|
|
|
|
|
|
// Log to audit trail
|
|
|
|
|
await this.logAudit({
|
|
|
|
|
operation: op,
|
|
|
|
|
params,
|
|
|
|
|
result,
|
|
|
|
|
timestamp: new Date(),
|
|
|
|
|
user: this.context.config.currentUser
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. Rate Limiting Augmentation
|
|
|
|
|
```typescript
|
|
|
|
|
export class RateLimitAugmentation extends BaseAugmentation {
|
2026-01-27 15:38:21 -08:00
|
|
|
timing = 'before'
|
|
|
|
|
operations = ['search']
|
|
|
|
|
private limiter = new RateLimiter({ rps: 100 })
|
|
|
|
|
|
|
|
|
|
async execute(op, params, next) {
|
|
|
|
|
await this.limiter.acquire() // Wait if rate limited
|
|
|
|
|
return next()
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Publishing to Brain Cloud Marketplace
|
|
|
|
|
|
|
|
|
|
Future capability for premium augmentations:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// package.json
|
|
|
|
|
{
|
2026-01-27 15:38:21 -08:00
|
|
|
"name": "@brain-cloud/redis-storage",
|
|
|
|
|
"brainy": {
|
|
|
|
|
"type": "augmentation",
|
|
|
|
|
"category": "storage",
|
|
|
|
|
"premium": true
|
|
|
|
|
}
|
2025-08-26 12:32:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Users can install via:
|
|
|
|
|
// brainy augment install redis-storage
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Best Practices
|
|
|
|
|
|
2025-10-17 12:29:27 -07:00
|
|
|
### General Practices
|
|
|
|
|
|
2025-08-26 12:32:21 -07:00
|
|
|
1. **Use BaseAugmentation** - Provides common functionality
|
|
|
|
|
2. **Set appropriate priority** - Storage (100), System (80-99), Features (10-50)
|
|
|
|
|
3. **Be selective with operations** - Don't use 'all' unless necessary
|
|
|
|
|
4. **Handle errors gracefully** - Don't break the chain
|
|
|
|
|
5. **Clean up in shutdown()** - Release resources
|
|
|
|
|
6. **Log appropriately** - Use context.log() for consistent output
|
|
|
|
|
7. **Document your augmentation** - Include examples
|
|
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
### Specific Best Practices
|
2025-10-17 12:29:27 -07:00
|
|
|
|
|
|
|
|
8. **Always include `noun` field** when creating/modifying NounMetadata:
|
2026-01-27 15:38:21 -08:00
|
|
|
```typescript
|
|
|
|
|
const metadata: NounMetadata = {
|
|
|
|
|
noun: 'thing', // REQUIRED!
|
|
|
|
|
yourField: 'value'
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-10-17 12:29:27 -07:00
|
|
|
|
|
|
|
|
9. **Use `verb` property** not `type` when working with relationships:
|
2026-01-27 15:38:21 -08:00
|
|
|
```typescript
|
|
|
|
|
// ✅ Correct
|
|
|
|
|
if (verb.verb === 'relatedTo') { ... }
|
2025-10-17 12:29:27 -07:00
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
// ❌ Wrong (v3.x pattern)
|
|
|
|
|
if (verb.type === 'relatedTo') { ... }
|
|
|
|
|
```
|
2025-10-17 12:29:27 -07:00
|
|
|
|
|
|
|
|
10. **Access metadata correctly** from storage:
|
2026-01-27 15:38:21 -08:00
|
|
|
```typescript
|
|
|
|
|
// ✅ Correct - metadata is already structured
|
|
|
|
|
const nounType = noun.metadata.noun
|
2025-10-17 12:29:27 -07:00
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
// ⚠️ Fallback pattern for robustness
|
|
|
|
|
const nounType = noun.metadata?.noun || 'thing'
|
|
|
|
|
```
|
2025-10-17 12:29:27 -07:00
|
|
|
|
|
|
|
|
11. **Respect the two-file storage pattern** - Don't mix vector and metadata operations:
|
2026-01-27 15:38:21 -08:00
|
|
|
```typescript
|
|
|
|
|
// ✅ Good - Separate concerns
|
|
|
|
|
await storage.saveNoun(noun)
|
|
|
|
|
await storage.saveMetadata(noun.id, metadata)
|
2025-10-17 12:29:27 -07:00
|
|
|
|
2026-01-27 15:38:21 -08:00
|
|
|
// ❌ Bad - Mixing concerns
|
|
|
|
|
await storage.saveNounWithEverything(combinedData)
|
|
|
|
|
```
|
2025-10-17 12:29:27 -07:00
|
|
|
|
2025-08-26 12:32:21 -07:00
|
|
|
## Testing Your Augmentation
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-09-30 17:09:15 -07:00
|
|
|
import { Brainy } from 'brainy'
|
2025-08-26 12:32:21 -07:00
|
|
|
import { MyAugmentation } from './my-augmentation'
|
|
|
|
|
|
|
|
|
|
describe('MyAugmentation', () => {
|
2026-01-27 15:38:21 -08:00
|
|
|
let brain: Brainy
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy()
|
|
|
|
|
brain.augmentations.register(new MyAugmentation())
|
|
|
|
|
await brain.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.destroy()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should enhance searches', async () => {
|
|
|
|
|
// Test your augmentation's effect
|
|
|
|
|
const results = await brain.search('test')
|
|
|
|
|
expect(results).toHaveProperty('enhanced', true)
|
|
|
|
|
})
|
2025-08-26 12:32:21 -07:00
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Summary
|
|
|
|
|
|
|
|
|
|
Augmentations are Brainy's extension system. They can:
|
|
|
|
|
- Replace storage backends
|
|
|
|
|
- Add caching layers
|
|
|
|
|
- Implement audit trails
|
|
|
|
|
- Add rate limiting
|
|
|
|
|
- Sync with external systems
|
|
|
|
|
- Transform data
|
|
|
|
|
- And much more!
|
|
|
|
|
|
|
|
|
|
The unified BrainyAugmentation interface makes it easy to create powerful extensions while maintaining consistency across the entire system.
|