chore: recovery checkpoint - v3.0 API successfully recovered
CRITICAL CHECKPOINT - DO NOT PUSH TO GITHUB Recovery Status: - Successfully recovered brainy.ts from compiled JavaScript - All core v3.0 API methods functional (add, get, update, delete, relate, find, etc.) - Neural subsystem intact (562KB embedded patterns, NLP working) - Augmentation pipeline operational (20+ augmentations) - HNSW clustering system complete - Triple Intelligence compiled (needs constructor fix) - Test suite validates functionality Changes preserved: - 898 files with changes from last 3 days - 144,475 insertions - All augmentation improvements - All test coverage enhancements - Complete v3.0 feature set This is a LOCAL checkpoint only - contains recovered work after corruption incident. Created backup in .backups/brainy-full-20250910-151314.tar.gz Branch: recovery-checkpoint-20250910-151433 Date: Wed Sep 10 03:18:04 PM PDT 2025
This commit is contained in:
parent
f65455fb22
commit
8ff382ca3b
895 changed files with 143654 additions and 28268 deletions
94
.recovery-workspace/dist-backup-20250910-141917/augmentations/AugmentationMetadataContract.d.ts
vendored
Normal file
94
.recovery-workspace/dist-backup-20250910-141917/augmentations/AugmentationMetadataContract.d.ts
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Augmentation Metadata Contract System
|
||||
*
|
||||
* Prevents accidental metadata corruption while allowing intentional enrichment
|
||||
* Each augmentation declares its metadata intentions upfront
|
||||
*/
|
||||
export interface AugmentationMetadataContract {
|
||||
name: string;
|
||||
version: string;
|
||||
reads?: {
|
||||
userFields?: string[];
|
||||
internalFields?: string[];
|
||||
augmentationFields?: string[];
|
||||
};
|
||||
writes?: {
|
||||
userFields?: Array<{
|
||||
field: string;
|
||||
type: 'create' | 'update' | 'merge' | 'delete';
|
||||
description: string;
|
||||
example?: any;
|
||||
}>;
|
||||
augmentationFields?: Array<{
|
||||
field: string;
|
||||
description: string;
|
||||
}>;
|
||||
internalFields?: Array<{
|
||||
field: string;
|
||||
permission: 'granted' | 'requested';
|
||||
reason: string;
|
||||
}>;
|
||||
};
|
||||
conflictResolution?: {
|
||||
strategy: 'error' | 'warn' | 'merge' | 'skip' | 'override';
|
||||
priority?: number;
|
||||
};
|
||||
guarantees?: {
|
||||
preservesExisting?: boolean;
|
||||
reversible?: boolean;
|
||||
idempotent?: boolean;
|
||||
validatesTypes?: boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Runtime metadata safety enforcer
|
||||
*/
|
||||
export declare class MetadataSafetyEnforcer {
|
||||
private contracts;
|
||||
private modifications;
|
||||
/**
|
||||
* Register an augmentation's contract
|
||||
*/
|
||||
registerContract(contract: AugmentationMetadataContract): void;
|
||||
/**
|
||||
* Check if an augmentation can modify a field
|
||||
*/
|
||||
canModifyField(augName: string, field: string, value: any): {
|
||||
allowed: boolean;
|
||||
reason?: string;
|
||||
warnings?: string[];
|
||||
};
|
||||
/**
|
||||
* Create safe metadata proxy for an augmentation
|
||||
*/
|
||||
createSafeProxy(metadata: any, augName: string): any;
|
||||
}
|
||||
/**
|
||||
* Example augmentation contracts
|
||||
*/
|
||||
export declare const EXAMPLE_CONTRACTS: Record<string, AugmentationMetadataContract>;
|
||||
/**
|
||||
* Augmentation base class with safety
|
||||
*/
|
||||
export declare abstract class SafeAugmentation {
|
||||
protected enforcer: MetadataSafetyEnforcer;
|
||||
protected contract: AugmentationMetadataContract;
|
||||
constructor(contract: AugmentationMetadataContract);
|
||||
/**
|
||||
* Get safe metadata proxy
|
||||
*/
|
||||
protected getSafeMetadata(metadata: any): any;
|
||||
/**
|
||||
* Abstract method to implement augmentation logic
|
||||
*/
|
||||
abstract execute(metadata: any): Promise<any>;
|
||||
}
|
||||
/**
|
||||
* Example: Category enricher implementation
|
||||
*/
|
||||
export declare class CategoryEnricherAugmentation extends SafeAugmentation {
|
||||
constructor();
|
||||
execute(metadata: any): Promise<any>;
|
||||
private detectCategory;
|
||||
private detectSubcategories;
|
||||
}
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
/**
|
||||
* Augmentation Metadata Contract System
|
||||
*
|
||||
* Prevents accidental metadata corruption while allowing intentional enrichment
|
||||
* Each augmentation declares its metadata intentions upfront
|
||||
*/
|
||||
/**
|
||||
* Runtime metadata safety enforcer
|
||||
*/
|
||||
export class MetadataSafetyEnforcer {
|
||||
constructor() {
|
||||
this.contracts = new Map();
|
||||
this.modifications = new Map(); // field -> augmentations that modify it
|
||||
}
|
||||
/**
|
||||
* Register an augmentation's contract
|
||||
*/
|
||||
registerContract(contract) {
|
||||
this.contracts.set(contract.name, contract);
|
||||
// Track which augmentations modify which fields
|
||||
if (contract.writes?.userFields) {
|
||||
for (const fieldDef of contract.writes.userFields) {
|
||||
if (!this.modifications.has(fieldDef.field)) {
|
||||
this.modifications.set(fieldDef.field, new Set());
|
||||
}
|
||||
this.modifications.get(fieldDef.field).add(contract.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if an augmentation can modify a field
|
||||
*/
|
||||
canModifyField(augName, field, value) {
|
||||
const contract = this.contracts.get(augName);
|
||||
if (!contract) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Augmentation '${augName}' has no registered contract`
|
||||
};
|
||||
}
|
||||
// Check if field is in user namespace
|
||||
if (!field.startsWith('_brainy.') && !field.startsWith('_augmentations.')) {
|
||||
// It's a user field
|
||||
const declaredField = contract.writes?.userFields?.find(f => f.field === field);
|
||||
if (!declaredField) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Augmentation '${augName}' did not declare intent to modify '${field}'`
|
||||
};
|
||||
}
|
||||
// Check for conflicts
|
||||
const modifiers = this.modifications.get(field);
|
||||
if (modifiers && modifiers.size > 1) {
|
||||
const others = Array.from(modifiers).filter(a => a !== augName);
|
||||
return {
|
||||
allowed: true, // Still allowed but with warning
|
||||
warnings: [`Field '${field}' is also modified by: ${others.join(', ')}`]
|
||||
};
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
// Check internal fields
|
||||
if (field.startsWith('_brainy.')) {
|
||||
const internalField = contract.writes?.internalFields?.find(f => field === `_brainy.${f.field}`);
|
||||
if (!internalField) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Augmentation '${augName}' cannot modify internal field '${field}'`
|
||||
};
|
||||
}
|
||||
if (internalField.permission !== 'granted') {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Permission not granted for internal field '${field}'`
|
||||
};
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
// Check augmentation namespace
|
||||
if (field.startsWith('_augmentations.')) {
|
||||
const parts = field.split('.');
|
||||
const targetAug = parts[1];
|
||||
// Can only modify own namespace
|
||||
if (targetAug !== augName) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Cannot modify another augmentation's namespace: ${targetAug}`
|
||||
};
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
/**
|
||||
* Create safe metadata proxy for an augmentation
|
||||
*/
|
||||
createSafeProxy(metadata, augName) {
|
||||
const self = this;
|
||||
return new Proxy(metadata, {
|
||||
set(target, prop, value) {
|
||||
const field = String(prop);
|
||||
// Check permission
|
||||
const permission = self.canModifyField(augName, field, value);
|
||||
if (!permission.allowed) {
|
||||
throw new Error(`[${augName}] ${permission.reason}`);
|
||||
}
|
||||
if (permission.warnings) {
|
||||
console.warn(`[${augName}] Warning:`, ...permission.warnings);
|
||||
}
|
||||
// Track modification for audit
|
||||
if (!target._audit) {
|
||||
target._audit = [];
|
||||
}
|
||||
target._audit.push({
|
||||
augmentation: augName,
|
||||
field,
|
||||
oldValue: target[prop],
|
||||
newValue: value,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
target[prop] = value;
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, prop) {
|
||||
const field = String(prop);
|
||||
const permission = self.canModifyField(augName, field, undefined);
|
||||
if (!permission.allowed) {
|
||||
throw new Error(`[${augName}] Cannot delete field: ${permission.reason}`);
|
||||
}
|
||||
delete target[prop];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Example augmentation contracts
|
||||
*/
|
||||
export const EXAMPLE_CONTRACTS = {
|
||||
// Enrichment augmentation that adds categories
|
||||
categoryEnricher: {
|
||||
name: 'categoryEnricher',
|
||||
version: '1.0.0',
|
||||
reads: {
|
||||
userFields: ['title', 'description', 'content']
|
||||
},
|
||||
writes: {
|
||||
userFields: [
|
||||
{
|
||||
field: 'category',
|
||||
type: 'create',
|
||||
description: 'Auto-detected category',
|
||||
example: 'technology'
|
||||
},
|
||||
{
|
||||
field: 'subcategories',
|
||||
type: 'create',
|
||||
description: 'List of relevant subcategories',
|
||||
example: ['web', 'framework']
|
||||
}
|
||||
],
|
||||
augmentationFields: [
|
||||
{
|
||||
field: 'confidence',
|
||||
description: 'Confidence score of categorization'
|
||||
}
|
||||
]
|
||||
},
|
||||
guarantees: {
|
||||
preservesExisting: true,
|
||||
idempotent: true
|
||||
}
|
||||
},
|
||||
// Translation augmentation
|
||||
translator: {
|
||||
name: 'translator',
|
||||
version: '1.0.0',
|
||||
reads: {
|
||||
userFields: ['title', 'description']
|
||||
},
|
||||
writes: {
|
||||
userFields: [
|
||||
{
|
||||
field: 'translations',
|
||||
type: 'merge',
|
||||
description: 'Translations in multiple languages',
|
||||
example: { es: 'Título', fr: 'Titre' }
|
||||
},
|
||||
{
|
||||
field: 'detectedLanguage',
|
||||
type: 'create',
|
||||
description: 'Detected source language',
|
||||
example: 'en'
|
||||
}
|
||||
]
|
||||
},
|
||||
conflictResolution: {
|
||||
strategy: 'merge',
|
||||
priority: 10
|
||||
}
|
||||
},
|
||||
// Sentiment analyzer
|
||||
sentimentAnalyzer: {
|
||||
name: 'sentimentAnalyzer',
|
||||
version: '1.0.0',
|
||||
reads: {
|
||||
userFields: ['content', 'description', 'reviews']
|
||||
},
|
||||
writes: {
|
||||
userFields: [
|
||||
{
|
||||
field: 'sentiment',
|
||||
type: 'update',
|
||||
description: 'Overall sentiment score',
|
||||
example: { score: 0.8, label: 'positive' }
|
||||
}
|
||||
],
|
||||
augmentationFields: [
|
||||
{
|
||||
field: 'analysis',
|
||||
description: 'Detailed sentiment breakdown'
|
||||
}
|
||||
]
|
||||
},
|
||||
guarantees: {
|
||||
reversible: true,
|
||||
validatesTypes: true
|
||||
}
|
||||
},
|
||||
// System augmentation with internal access
|
||||
garbageCollector: {
|
||||
name: 'garbageCollector',
|
||||
version: '1.0.0',
|
||||
reads: {
|
||||
internalFields: ['_brainy.createdAt', '_brainy.lastAccessed']
|
||||
},
|
||||
writes: {
|
||||
internalFields: [
|
||||
{
|
||||
field: 'deleted',
|
||||
permission: 'granted',
|
||||
reason: 'Soft delete expired items'
|
||||
},
|
||||
{
|
||||
field: 'archived',
|
||||
permission: 'granted',
|
||||
reason: 'Archive old items'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Augmentation base class with safety
|
||||
*/
|
||||
export class SafeAugmentation {
|
||||
constructor(contract) {
|
||||
this.contract = contract;
|
||||
this.enforcer = new MetadataSafetyEnforcer();
|
||||
this.enforcer.registerContract(contract);
|
||||
}
|
||||
/**
|
||||
* Get safe metadata proxy
|
||||
*/
|
||||
getSafeMetadata(metadata) {
|
||||
return this.enforcer.createSafeProxy(metadata, this.contract.name);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Example: Category enricher implementation
|
||||
*/
|
||||
export class CategoryEnricherAugmentation extends SafeAugmentation {
|
||||
constructor() {
|
||||
super(EXAMPLE_CONTRACTS.categoryEnricher);
|
||||
}
|
||||
async execute(metadata) {
|
||||
const safe = this.getSafeMetadata(metadata);
|
||||
// Read declared fields
|
||||
const title = safe.title;
|
||||
const description = safe.description;
|
||||
// Analyze and categorize
|
||||
const category = this.detectCategory(title, description);
|
||||
const subcategories = this.detectSubcategories(title, description);
|
||||
// Write to declared fields (will be checked by proxy)
|
||||
safe.category = category; // ✅ Allowed - declared in contract
|
||||
safe.subcategories = subcategories; // ✅ Allowed
|
||||
// Try to write undeclared field
|
||||
// safe.randomField = 'test' // ❌ Would throw error!
|
||||
// Write to our augmentation namespace
|
||||
if (!safe._augmentations)
|
||||
safe._augmentations = {};
|
||||
if (!safe._augmentations.categoryEnricher) {
|
||||
safe._augmentations.categoryEnricher = {};
|
||||
}
|
||||
safe._augmentations.categoryEnricher.confidence = 0.95; // ✅ Allowed
|
||||
return safe;
|
||||
}
|
||||
detectCategory(title, description) {
|
||||
// Simplified logic
|
||||
return 'technology';
|
||||
}
|
||||
detectSubcategories(title, description) {
|
||||
return ['web', 'framework'];
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=AugmentationMetadataContract.js.map
|
||||
File diff suppressed because one or more lines are too long
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/apiServerAugmentation.d.ts
vendored
Normal file
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/apiServerAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* API Server Augmentation - Universal API Exposure
|
||||
*
|
||||
* 🌐 Exposes Brainy through REST, WebSocket, and MCP
|
||||
* 🔌 Works in Node.js, Deno, and Service Workers
|
||||
* 🚀 Single augmentation for all API needs
|
||||
*
|
||||
* This unifies and replaces:
|
||||
* - BrainyMCPBroadcast (Node-specific server)
|
||||
* - WebSocketConduitAugmentation (client connections)
|
||||
* - Future REST API implementations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export interface APIServerConfig {
|
||||
enabled?: boolean;
|
||||
port?: number;
|
||||
mcpPort?: number;
|
||||
wsPort?: number;
|
||||
host?: string;
|
||||
cors?: {
|
||||
origin?: string | string[];
|
||||
credentials?: boolean;
|
||||
};
|
||||
auth?: {
|
||||
required?: boolean;
|
||||
apiKeys?: string[];
|
||||
bearerTokens?: string[];
|
||||
};
|
||||
rateLimit?: {
|
||||
windowMs?: number;
|
||||
max?: number;
|
||||
};
|
||||
ssl?: {
|
||||
cert?: string;
|
||||
key?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Unified API Server Augmentation
|
||||
* Exposes Brainy through multiple protocols
|
||||
*/
|
||||
export declare class APIServerAugmentation extends BaseAugmentation {
|
||||
readonly name = "api-server";
|
||||
readonly timing: "after";
|
||||
readonly metadata: "readonly";
|
||||
readonly operations: ("all")[];
|
||||
readonly priority = 5;
|
||||
protected config: APIServerConfig;
|
||||
private mcpService?;
|
||||
private httpServer?;
|
||||
private wsServer?;
|
||||
private clients;
|
||||
private operationHistory;
|
||||
private maxHistorySize;
|
||||
constructor(config?: APIServerConfig);
|
||||
protected onInitialize(): Promise<void>;
|
||||
/**
|
||||
* Start Node.js server with Express
|
||||
*/
|
||||
private startNodeServer;
|
||||
/**
|
||||
* Setup REST API routes
|
||||
*/
|
||||
private setupRESTRoutes;
|
||||
/**
|
||||
* Setup WebSocket server
|
||||
*/
|
||||
private setupWebSocketServer;
|
||||
/**
|
||||
* Handle WebSocket message
|
||||
*/
|
||||
private handleWebSocketMessage;
|
||||
/**
|
||||
* Execute augmentation - broadcast operations to clients
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Auth middleware for Express
|
||||
*/
|
||||
private authMiddleware;
|
||||
/**
|
||||
* Rate limiting middleware
|
||||
*/
|
||||
private rateLimitMiddleware;
|
||||
/**
|
||||
* Sanitize parameters before broadcasting
|
||||
*/
|
||||
private sanitizeParams;
|
||||
/**
|
||||
* Send heartbeats to all connected clients
|
||||
*/
|
||||
private sendHeartbeats;
|
||||
/**
|
||||
* Start Deno server
|
||||
*/
|
||||
private startDenoServer;
|
||||
/**
|
||||
* Start Service Worker (for browser)
|
||||
*/
|
||||
private startServiceWorker;
|
||||
/**
|
||||
* Shutdown the server
|
||||
*/
|
||||
protected onShutdown(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Helper function to create and configure API server
|
||||
*/
|
||||
export declare function createAPIServer(config?: APIServerConfig): APIServerAugmentation;
|
||||
|
|
@ -0,0 +1,503 @@
|
|||
/**
|
||||
* API Server Augmentation - Universal API Exposure
|
||||
*
|
||||
* 🌐 Exposes Brainy through REST, WebSocket, and MCP
|
||||
* 🔌 Works in Node.js, Deno, and Service Workers
|
||||
* 🚀 Single augmentation for all API needs
|
||||
*
|
||||
* This unifies and replaces:
|
||||
* - BrainyMCPBroadcast (Node-specific server)
|
||||
* - WebSocketConduitAugmentation (client connections)
|
||||
* - Future REST API implementations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { BrainyMCPService } from '../mcp/brainyMCPService.js';
|
||||
import { v4 as uuidv4 } from '../universal/uuid.js';
|
||||
import { isNode, isBrowser } from '../utils/environment.js';
|
||||
/**
|
||||
* Unified API Server Augmentation
|
||||
* Exposes Brainy through multiple protocols
|
||||
*/
|
||||
export class APIServerAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.name = 'api-server';
|
||||
this.timing = 'after';
|
||||
this.metadata = 'readonly'; // API server reads metadata to serve data
|
||||
this.operations = ['all'];
|
||||
this.priority = 5; // Low priority, runs after other augmentations
|
||||
this.clients = new Map();
|
||||
this.operationHistory = [];
|
||||
this.maxHistorySize = 1000;
|
||||
this.config = {
|
||||
enabled: true,
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
cors: { origin: '*', credentials: true },
|
||||
auth: { required: false },
|
||||
rateLimit: { windowMs: 60000, max: 100 },
|
||||
...config
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('API Server disabled in config');
|
||||
return;
|
||||
}
|
||||
// Initialize MCP service
|
||||
this.mcpService = new BrainyMCPService(this.context.brain, {
|
||||
enableAuth: this.config.auth?.required
|
||||
});
|
||||
// Start appropriate server based on environment
|
||||
if (isNode()) {
|
||||
await this.startNodeServer();
|
||||
}
|
||||
else if (typeof globalThis.Deno !== 'undefined') {
|
||||
await this.startDenoServer();
|
||||
}
|
||||
else if (isBrowser() && 'serviceWorker' in navigator) {
|
||||
await this.startServiceWorker();
|
||||
}
|
||||
else {
|
||||
this.log('No suitable server environment detected', 'warn');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start Node.js server with Express
|
||||
*/
|
||||
async startNodeServer() {
|
||||
try {
|
||||
// Dynamic imports for Node.js dependencies
|
||||
const express = await import('express').catch(() => null);
|
||||
const cors = await import('cors').catch(() => null);
|
||||
const ws = await import('ws').catch(() => null);
|
||||
const { createServer } = await import('http');
|
||||
if (!express || !cors || !ws) {
|
||||
this.log('Express, cors, or ws not available. Install with: npm install express cors ws', 'error');
|
||||
return;
|
||||
}
|
||||
const WebSocketServer = ws?.WebSocketServer || ws?.default?.WebSocketServer || ws?.Server;
|
||||
const app = express.default();
|
||||
// Middleware
|
||||
app.use(cors.default(this.config.cors));
|
||||
app.use((express.default || express).json({ limit: '50mb' }));
|
||||
app.use(this.authMiddleware.bind(this));
|
||||
app.use(this.rateLimitMiddleware.bind(this));
|
||||
// REST API Routes
|
||||
this.setupRESTRoutes(app);
|
||||
// Create HTTP server
|
||||
this.httpServer = createServer(app);
|
||||
// WebSocket server
|
||||
this.wsServer = new WebSocketServer({
|
||||
server: this.httpServer,
|
||||
path: '/ws'
|
||||
});
|
||||
this.setupWebSocketServer();
|
||||
// Start listening
|
||||
await new Promise((resolve, reject) => {
|
||||
this.httpServer.listen(this.config.port, this.config.host, () => {
|
||||
this.log(`🌐 API Server listening on http://${this.config.host}:${this.config.port}`);
|
||||
this.log(`🔌 WebSocket: ws://${this.config.host}:${this.config.port}/ws`);
|
||||
this.log(`🧠 MCP endpoint: http://${this.config.host}:${this.config.port}/api/mcp`);
|
||||
resolve();
|
||||
}).on('error', reject);
|
||||
});
|
||||
// Heartbeat interval
|
||||
setInterval(() => this.sendHeartbeats(), 30000);
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to start Node.js server: ${error}`, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Setup REST API routes
|
||||
*/
|
||||
setupRESTRoutes(app) {
|
||||
// Health check
|
||||
app.get('/health', (_req, res) => {
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
version: '2.0.0',
|
||||
clients: this.clients.size,
|
||||
uptime: process.uptime ? process.uptime() : 0
|
||||
});
|
||||
});
|
||||
// Search endpoint
|
||||
app.post('/api/search', async (req, res) => {
|
||||
try {
|
||||
const { query, limit = 10, options = {} } = req.body;
|
||||
const results = await this.context.brain.search(query, limit, options);
|
||||
res.json({ success: true, results });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Add data endpoint
|
||||
app.post('/api/add', async (req, res) => {
|
||||
try {
|
||||
const { content, metadata } = req.body;
|
||||
const id = await this.context.brain.addNoun(content, 'Content', metadata);
|
||||
res.json({ success: true, id });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Get by ID endpoint
|
||||
app.get('/api/get/:id', async (req, res) => {
|
||||
try {
|
||||
const data = await this.context.brain.get(req.params.id);
|
||||
if (data) {
|
||||
res.json({ success: true, data });
|
||||
}
|
||||
else {
|
||||
res.status(404).json({ success: false, error: 'Not found' });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Delete endpoint
|
||||
app.delete('/api/delete/:id', async (req, res) => {
|
||||
try {
|
||||
await this.context.brain.delete(req.params.id);
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Relate endpoint
|
||||
app.post('/api/relate', async (req, res) => {
|
||||
try {
|
||||
const { source, target, verb, metadata } = req.body;
|
||||
await this.context.brain.relate(source, target, verb, metadata);
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Find endpoint (complex queries)
|
||||
app.post('/api/find', async (req, res) => {
|
||||
try {
|
||||
const results = await this.context.brain.find(req.body);
|
||||
res.json({ success: true, results });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Cluster endpoint
|
||||
app.post('/api/cluster', async (req, res) => {
|
||||
try {
|
||||
const { algorithm = 'kmeans', options = {} } = req.body;
|
||||
const clusters = await this.context.brain.cluster(algorithm, options);
|
||||
res.json({ success: true, clusters });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// MCP endpoint
|
||||
app.post('/api/mcp', async (req, res) => {
|
||||
try {
|
||||
const response = await this.mcpService.handleRequest(req.body);
|
||||
res.json(response);
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Statistics endpoint
|
||||
app.get('/api/stats', async (_req, res) => {
|
||||
try {
|
||||
const stats = await this.context.brain.getStatistics();
|
||||
res.json({ success: true, stats });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Operation history endpoint
|
||||
app.get('/api/history', (_req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
history: this.operationHistory.slice(-100)
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Setup WebSocket server
|
||||
*/
|
||||
setupWebSocketServer() {
|
||||
if (!this.wsServer)
|
||||
return;
|
||||
this.wsServer.on('connection', (socket, request) => {
|
||||
const clientId = uuidv4();
|
||||
const client = {
|
||||
id: clientId,
|
||||
type: 'websocket',
|
||||
socket,
|
||||
subscriptions: [],
|
||||
lastSeen: Date.now()
|
||||
};
|
||||
this.clients.set(clientId, client);
|
||||
// Send welcome message
|
||||
socket.send(JSON.stringify({
|
||||
type: 'welcome',
|
||||
clientId,
|
||||
message: 'Connected to Brainy API Server',
|
||||
capabilities: ['search', 'add', 'delete', 'relate', 'subscribe', 'mcp']
|
||||
}));
|
||||
// Handle messages
|
||||
socket.on('message', async (message) => {
|
||||
try {
|
||||
const msg = JSON.parse(message);
|
||||
await this.handleWebSocketMessage(msg, client);
|
||||
}
|
||||
catch (error) {
|
||||
socket.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: error.message
|
||||
}));
|
||||
}
|
||||
});
|
||||
// Handle disconnect
|
||||
socket.on('close', () => {
|
||||
this.clients.delete(clientId);
|
||||
this.log(`Client ${clientId} disconnected`);
|
||||
});
|
||||
// Handle errors
|
||||
socket.on('error', (error) => {
|
||||
this.log(`WebSocket error for client ${clientId}: ${error}`, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handle WebSocket message
|
||||
*/
|
||||
async handleWebSocketMessage(msg, client) {
|
||||
const { socket } = client;
|
||||
switch (msg.type) {
|
||||
case 'subscribe':
|
||||
// Subscribe to operation types
|
||||
client.subscriptions = msg.operations || ['all'];
|
||||
socket.send(JSON.stringify({
|
||||
type: 'subscribed',
|
||||
operations: client.subscriptions
|
||||
}));
|
||||
break;
|
||||
case 'search':
|
||||
const searchResults = await this.context.brain.search(msg.query, msg.limit || 10, msg.options || {});
|
||||
socket.send(JSON.stringify({
|
||||
type: 'searchResults',
|
||||
requestId: msg.requestId,
|
||||
results: searchResults
|
||||
}));
|
||||
break;
|
||||
case 'add':
|
||||
const id = await this.context.brain.addNoun(msg.content, 'Content', msg.metadata);
|
||||
socket.send(JSON.stringify({
|
||||
type: 'addResult',
|
||||
requestId: msg.requestId,
|
||||
id
|
||||
}));
|
||||
break;
|
||||
case 'mcp':
|
||||
const mcpResponse = await this.mcpService.handleRequest(msg.request);
|
||||
socket.send(JSON.stringify({
|
||||
type: 'mcpResponse',
|
||||
requestId: msg.requestId,
|
||||
response: mcpResponse
|
||||
}));
|
||||
break;
|
||||
case 'heartbeat':
|
||||
client.lastSeen = Date.now();
|
||||
socket.send(JSON.stringify({
|
||||
type: 'heartbeat',
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
socket.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: `Unknown message type: ${msg.type}`
|
||||
}));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - broadcast operations to clients
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
const startTime = Date.now();
|
||||
const result = await next();
|
||||
const duration = Date.now() - startTime;
|
||||
// Record operation in history
|
||||
const historyEntry = {
|
||||
operation,
|
||||
params: this.sanitizeParams(params),
|
||||
timestamp: Date.now(),
|
||||
duration
|
||||
};
|
||||
this.operationHistory.push(historyEntry);
|
||||
if (this.operationHistory.length > this.maxHistorySize) {
|
||||
this.operationHistory.shift();
|
||||
}
|
||||
// Broadcast to subscribed WebSocket clients
|
||||
const message = JSON.stringify({
|
||||
type: 'operation',
|
||||
operation,
|
||||
params: historyEntry.params,
|
||||
timestamp: historyEntry.timestamp,
|
||||
duration
|
||||
});
|
||||
for (const client of this.clients.values()) {
|
||||
if (client.type === 'websocket' && client.socket) {
|
||||
if (client.subscriptions?.includes('all') ||
|
||||
client.subscriptions?.includes(operation)) {
|
||||
try {
|
||||
client.socket.send(message);
|
||||
}
|
||||
catch (error) {
|
||||
// Client might be disconnected
|
||||
this.clients.delete(client.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Auth middleware for Express
|
||||
*/
|
||||
authMiddleware(req, res, next) {
|
||||
if (!this.config.auth?.required) {
|
||||
return next();
|
||||
}
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
const bearerToken = req.headers.authorization?.replace('Bearer ', '');
|
||||
if (apiKey && this.config.auth.apiKeys?.includes(apiKey)) {
|
||||
return next();
|
||||
}
|
||||
if (bearerToken && this.config.auth.bearerTokens?.includes(bearerToken)) {
|
||||
return next();
|
||||
}
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
/**
|
||||
* Rate limiting middleware
|
||||
*/
|
||||
rateLimitMiddleware(req, res, next) {
|
||||
// Simple in-memory rate limiting
|
||||
// In production, use redis or proper rate limiting library
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
const now = Date.now();
|
||||
const windowMs = this.config.rateLimit?.windowMs || 60000;
|
||||
const max = this.config.rateLimit?.max || 100;
|
||||
// Clean old entries
|
||||
for (const [key, client] of this.clients.entries()) {
|
||||
if (now - client.lastSeen > windowMs) {
|
||||
this.clients.delete(key);
|
||||
}
|
||||
}
|
||||
// For now, just pass through
|
||||
// Real implementation would track requests per IP
|
||||
next();
|
||||
}
|
||||
/**
|
||||
* Sanitize parameters before broadcasting
|
||||
*/
|
||||
sanitizeParams(params) {
|
||||
if (!params)
|
||||
return params;
|
||||
const sanitized = { ...params };
|
||||
// Remove sensitive fields
|
||||
delete sanitized.password;
|
||||
delete sanitized.apiKey;
|
||||
delete sanitized.token;
|
||||
delete sanitized.secret;
|
||||
// Truncate large data
|
||||
if (sanitized.content && sanitized.content.length > 1000) {
|
||||
sanitized.content = sanitized.content.substring(0, 1000) + '...';
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
/**
|
||||
* Send heartbeats to all connected clients
|
||||
*/
|
||||
sendHeartbeats() {
|
||||
const now = Date.now();
|
||||
for (const [id, client] of this.clients.entries()) {
|
||||
if (client.type === 'websocket' && client.socket) {
|
||||
// Remove inactive clients
|
||||
if (now - client.lastSeen > 60000) {
|
||||
this.clients.delete(id);
|
||||
continue;
|
||||
}
|
||||
// Send heartbeat
|
||||
try {
|
||||
client.socket.send(JSON.stringify({
|
||||
type: 'heartbeat',
|
||||
timestamp: now
|
||||
}));
|
||||
}
|
||||
catch {
|
||||
// Client disconnected
|
||||
this.clients.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start Deno server
|
||||
*/
|
||||
async startDenoServer() {
|
||||
// Deno implementation would go here
|
||||
// Using Deno.serve() or oak framework
|
||||
this.log('Deno server not yet implemented', 'warn');
|
||||
}
|
||||
/**
|
||||
* Start Service Worker (for browser)
|
||||
*/
|
||||
async startServiceWorker() {
|
||||
// Service Worker implementation would go here
|
||||
// Intercepts fetch() calls and handles them locally
|
||||
this.log('Service Worker API not yet implemented', 'warn');
|
||||
}
|
||||
/**
|
||||
* Shutdown the server
|
||||
*/
|
||||
async onShutdown() {
|
||||
// Close all WebSocket connections
|
||||
for (const client of this.clients.values()) {
|
||||
if (client.socket) {
|
||||
try {
|
||||
client.socket.close();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
this.clients.clear();
|
||||
// Close servers
|
||||
if (this.wsServer) {
|
||||
this.wsServer.close();
|
||||
}
|
||||
if (this.httpServer) {
|
||||
await new Promise(resolve => {
|
||||
this.httpServer.close(() => resolve());
|
||||
});
|
||||
}
|
||||
this.log('API Server shut down');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper function to create and configure API server
|
||||
*/
|
||||
export function createAPIServer(config) {
|
||||
return new APIServerAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=apiServerAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/auditLogAugmentation.d.ts
vendored
Normal file
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/auditLogAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Audit Logging Augmentation
|
||||
* Provides comprehensive audit trail for all Brainy operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { AugmentationManifest } from './manifest.js';
|
||||
export interface AuditLogConfig {
|
||||
enabled?: boolean;
|
||||
logLevel?: 'minimal' | 'standard' | 'detailed';
|
||||
includeData?: boolean;
|
||||
includeMetadata?: boolean;
|
||||
retention?: number;
|
||||
storage?: 'memory' | 'file' | 'database';
|
||||
filePath?: string;
|
||||
maxMemoryLogs?: number;
|
||||
}
|
||||
export interface AuditLogEntry {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
operation: string;
|
||||
params: any;
|
||||
result?: any;
|
||||
error?: any;
|
||||
duration: number;
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
/**
|
||||
* Audit Log Augmentation
|
||||
*/
|
||||
export declare class AuditLogAugmentation extends BaseAugmentation {
|
||||
readonly name = "auditLogger";
|
||||
readonly timing: "around";
|
||||
readonly metadata: "readonly";
|
||||
operations: any;
|
||||
readonly priority = 90;
|
||||
readonly category: "core";
|
||||
readonly description = "Comprehensive audit logging for compliance and debugging";
|
||||
private logs;
|
||||
private sessionId;
|
||||
constructor(config?: AuditLogConfig);
|
||||
getManifest(): AugmentationManifest;
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute augmentation - log operations
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Sanitize parameters to remove sensitive data
|
||||
*/
|
||||
private sanitizeParams;
|
||||
/**
|
||||
* Sanitize result data
|
||||
*/
|
||||
private sanitizeResult;
|
||||
/**
|
||||
* Sanitize error information
|
||||
*/
|
||||
private sanitizeError;
|
||||
/**
|
||||
* Write log entry
|
||||
*/
|
||||
private writeLog;
|
||||
/**
|
||||
* Flush logs to persistent storage
|
||||
*/
|
||||
private flushLogs;
|
||||
/**
|
||||
* Clean up old logs based on retention
|
||||
*/
|
||||
private cleanupOldLogs;
|
||||
/**
|
||||
* Generate unique ID
|
||||
*/
|
||||
private generateId;
|
||||
/**
|
||||
* Query audit logs
|
||||
*/
|
||||
queryLogs(filter?: {
|
||||
operation?: string;
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
sessionId?: string;
|
||||
hasError?: boolean;
|
||||
}): AuditLogEntry[];
|
||||
/**
|
||||
* Get audit statistics
|
||||
*/
|
||||
getStats(): {
|
||||
totalLogs: number;
|
||||
operations: Record<string, number>;
|
||||
averageDuration: number;
|
||||
errorRate: number;
|
||||
};
|
||||
/**
|
||||
* Export logs for analysis
|
||||
*/
|
||||
exportLogs(): AuditLogEntry[];
|
||||
/**
|
||||
* Clear all logs
|
||||
*/
|
||||
clearLogs(): void;
|
||||
}
|
||||
/**
|
||||
* Create audit log augmentation
|
||||
*/
|
||||
export declare function createAuditLogAugmentation(config?: AuditLogConfig): AuditLogAugmentation;
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
/**
|
||||
* Audit Logging Augmentation
|
||||
* Provides comprehensive audit trail for all Brainy operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { createHash } from 'crypto';
|
||||
/**
|
||||
* Audit Log Augmentation
|
||||
*/
|
||||
export class AuditLogAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super(config);
|
||||
this.name = 'auditLogger';
|
||||
this.timing = 'around';
|
||||
this.metadata = 'readonly'; // Read metadata for context
|
||||
this.operations = ['all']; // Audit all operations
|
||||
this.priority = 90; // Low priority, runs last
|
||||
// Augmentation metadata
|
||||
this.category = 'core';
|
||||
this.description = 'Comprehensive audit logging for compliance and debugging';
|
||||
this.logs = [];
|
||||
// Merge with defaults
|
||||
this.config = {
|
||||
enabled: config.enabled ?? true,
|
||||
logLevel: config.logLevel ?? 'standard',
|
||||
includeData: config.includeData ?? false,
|
||||
includeMetadata: config.includeMetadata ?? true,
|
||||
retention: config.retention ?? 90, // 90 days default
|
||||
storage: config.storage ?? 'memory',
|
||||
filePath: config.filePath,
|
||||
maxMemoryLogs: config.maxMemoryLogs ?? 10000
|
||||
};
|
||||
// Generate session ID
|
||||
this.sessionId = this.generateId();
|
||||
}
|
||||
getManifest() {
|
||||
return {
|
||||
id: 'audit-logger',
|
||||
name: 'Audit Logger',
|
||||
version: '1.0.0',
|
||||
description: 'Comprehensive audit trail for all operations',
|
||||
longDescription: 'Records detailed audit logs of all Brainy operations for compliance, debugging, and analytics purposes.',
|
||||
category: 'analytics',
|
||||
configSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Enable audit logging'
|
||||
},
|
||||
logLevel: {
|
||||
type: 'string',
|
||||
enum: ['minimal', 'standard', 'detailed'],
|
||||
default: 'standard',
|
||||
description: 'Level of detail to log'
|
||||
},
|
||||
includeData: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Include actual data in logs (privacy concern)'
|
||||
},
|
||||
includeMetadata: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Include metadata in logs'
|
||||
},
|
||||
retention: {
|
||||
type: 'number',
|
||||
default: 90,
|
||||
minimum: 1,
|
||||
maximum: 365,
|
||||
description: 'Days to retain logs'
|
||||
},
|
||||
storage: {
|
||||
type: 'string',
|
||||
enum: ['memory', 'file', 'database'],
|
||||
default: 'memory',
|
||||
description: 'Where to store audit logs'
|
||||
},
|
||||
maxMemoryLogs: {
|
||||
type: 'number',
|
||||
default: 10000,
|
||||
description: 'Maximum logs to keep in memory'
|
||||
}
|
||||
}
|
||||
},
|
||||
configDefaults: {
|
||||
enabled: true,
|
||||
logLevel: 'standard',
|
||||
includeData: false,
|
||||
includeMetadata: true,
|
||||
retention: 90,
|
||||
storage: 'memory',
|
||||
maxMemoryLogs: 10000
|
||||
},
|
||||
minBrainyVersion: '3.0.0',
|
||||
keywords: ['audit', 'logging', 'compliance', 'analytics'],
|
||||
documentation: 'https://docs.brainy.dev/augmentations/audit-log',
|
||||
status: 'stable',
|
||||
performance: {
|
||||
memoryUsage: 'medium',
|
||||
cpuUsage: 'low',
|
||||
networkUsage: 'none'
|
||||
},
|
||||
features: ['operation-logging', 'configurable-detail', 'retention-management'],
|
||||
enhancedOperations: ['all'],
|
||||
metrics: [
|
||||
{
|
||||
name: 'audit_logs_created',
|
||||
type: 'counter',
|
||||
description: 'Total audit logs created'
|
||||
},
|
||||
{
|
||||
name: 'audit_log_size',
|
||||
type: 'gauge',
|
||||
description: 'Current audit log size'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('Audit logger disabled by configuration');
|
||||
return;
|
||||
}
|
||||
this.log(`Audit logger initialized (level: ${this.config.logLevel}, storage: ${this.config.storage})`);
|
||||
// Start retention cleanup if using memory storage
|
||||
if (this.config.storage === 'memory') {
|
||||
setInterval(() => {
|
||||
this.cleanupOldLogs();
|
||||
}, 3600000); // Every hour
|
||||
}
|
||||
}
|
||||
async onShutdown() {
|
||||
// Save any pending logs if using file storage
|
||||
if (this.config.storage === 'file' && this.logs.length > 0) {
|
||||
await this.flushLogs();
|
||||
}
|
||||
this.log('Audit logger shut down');
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - log operations
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// If audit logging is disabled, just pass through
|
||||
if (!this.config.enabled) {
|
||||
return next();
|
||||
}
|
||||
const startTime = Date.now();
|
||||
const logEntry = {
|
||||
id: this.generateId(),
|
||||
timestamp: startTime,
|
||||
operation,
|
||||
sessionId: this.sessionId
|
||||
};
|
||||
// Add params based on log level
|
||||
if (this.config.logLevel !== 'minimal') {
|
||||
logEntry.params = this.sanitizeParams(params);
|
||||
}
|
||||
try {
|
||||
const result = await next();
|
||||
// Log successful operation
|
||||
logEntry.duration = Date.now() - startTime;
|
||||
// Add result based on log level and config
|
||||
if (this.config.logLevel === 'detailed' && this.config.includeData) {
|
||||
logEntry.result = this.sanitizeResult(result);
|
||||
}
|
||||
await this.writeLog(logEntry);
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
// Log failed operation
|
||||
logEntry.duration = Date.now() - startTime;
|
||||
logEntry.error = this.sanitizeError(error);
|
||||
await this.writeLog(logEntry);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sanitize parameters to remove sensitive data
|
||||
*/
|
||||
sanitizeParams(params) {
|
||||
if (!params)
|
||||
return params;
|
||||
// Don't include actual data unless configured
|
||||
if (!this.config.includeData && params.data) {
|
||||
return {
|
||||
...params,
|
||||
data: '[REDACTED]'
|
||||
};
|
||||
}
|
||||
// Redact common sensitive fields
|
||||
const sanitized = { ...params };
|
||||
const sensitiveFields = ['password', 'token', 'apiKey', 'secret'];
|
||||
for (const field of sensitiveFields) {
|
||||
if (sanitized[field]) {
|
||||
sanitized[field] = '[REDACTED]';
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
/**
|
||||
* Sanitize result data
|
||||
*/
|
||||
sanitizeResult(result) {
|
||||
if (!result)
|
||||
return result;
|
||||
// For arrays, just log count
|
||||
if (Array.isArray(result)) {
|
||||
return { count: result.length, type: 'array' };
|
||||
}
|
||||
// For objects, remove sensitive fields
|
||||
if (typeof result === 'object') {
|
||||
const sanitized = {};
|
||||
for (const key in result) {
|
||||
if (!['password', 'token', 'apiKey', 'secret'].includes(key)) {
|
||||
sanitized[key] = result[key];
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Sanitize error information
|
||||
*/
|
||||
sanitizeError(error) {
|
||||
if (!error)
|
||||
return error;
|
||||
return {
|
||||
message: error.message || 'Unknown error',
|
||||
code: error.code,
|
||||
statusCode: error.statusCode,
|
||||
stack: this.config.logLevel === 'detailed' ? error.stack : undefined
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Write log entry
|
||||
*/
|
||||
async writeLog(entry) {
|
||||
switch (this.config.storage) {
|
||||
case 'memory':
|
||||
this.logs.push(entry);
|
||||
// Enforce max memory logs
|
||||
if (this.logs.length > this.config.maxMemoryLogs) {
|
||||
this.logs.shift(); // Remove oldest
|
||||
}
|
||||
break;
|
||||
case 'file':
|
||||
// In production, would write to file
|
||||
// For now, just add to memory
|
||||
this.logs.push(entry);
|
||||
break;
|
||||
case 'database':
|
||||
// In production, would write to database
|
||||
// For now, just add to memory
|
||||
this.logs.push(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Flush logs to persistent storage
|
||||
*/
|
||||
async flushLogs() {
|
||||
// In production, would write to file/database
|
||||
// For now, just clear old logs
|
||||
if (this.logs.length > this.config.maxMemoryLogs) {
|
||||
this.logs = this.logs.slice(-this.config.maxMemoryLogs);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clean up old logs based on retention
|
||||
*/
|
||||
cleanupOldLogs() {
|
||||
const cutoffTime = Date.now() - (this.config.retention * 24 * 60 * 60 * 1000);
|
||||
this.logs = this.logs.filter(log => log.timestamp >= cutoffTime);
|
||||
}
|
||||
/**
|
||||
* Generate unique ID
|
||||
*/
|
||||
generateId() {
|
||||
return createHash('sha256')
|
||||
.update(`${Date.now()}-${Math.random()}`)
|
||||
.digest('hex')
|
||||
.substring(0, 16);
|
||||
}
|
||||
/**
|
||||
* Query audit logs
|
||||
*/
|
||||
queryLogs(filter) {
|
||||
let results = [...this.logs];
|
||||
if (filter) {
|
||||
if (filter.operation) {
|
||||
results = results.filter(log => log.operation === filter.operation);
|
||||
}
|
||||
if (filter.startTime) {
|
||||
results = results.filter(log => log.timestamp >= filter.startTime);
|
||||
}
|
||||
if (filter.endTime) {
|
||||
results = results.filter(log => log.timestamp <= filter.endTime);
|
||||
}
|
||||
if (filter.sessionId) {
|
||||
results = results.filter(log => log.sessionId === filter.sessionId);
|
||||
}
|
||||
if (filter.hasError !== undefined) {
|
||||
results = results.filter(log => (log.error !== undefined) === filter.hasError);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
/**
|
||||
* Get audit statistics
|
||||
*/
|
||||
getStats() {
|
||||
const stats = {
|
||||
totalLogs: this.logs.length,
|
||||
operations: {},
|
||||
averageDuration: 0,
|
||||
errorRate: 0
|
||||
};
|
||||
let totalDuration = 0;
|
||||
let errorCount = 0;
|
||||
for (const log of this.logs) {
|
||||
// Count by operation
|
||||
stats.operations[log.operation] = (stats.operations[log.operation] || 0) + 1;
|
||||
// Sum duration
|
||||
totalDuration += log.duration;
|
||||
// Count errors
|
||||
if (log.error)
|
||||
errorCount++;
|
||||
}
|
||||
if (this.logs.length > 0) {
|
||||
stats.averageDuration = totalDuration / this.logs.length;
|
||||
stats.errorRate = errorCount / this.logs.length;
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
/**
|
||||
* Export logs for analysis
|
||||
*/
|
||||
exportLogs() {
|
||||
return [...this.logs];
|
||||
}
|
||||
/**
|
||||
* Clear all logs
|
||||
*/
|
||||
clearLogs() {
|
||||
this.logs = [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create audit log augmentation
|
||||
*/
|
||||
export function createAuditLogAugmentation(config) {
|
||||
return new AuditLogAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=auditLogAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
97
.recovery-workspace/dist-backup-20250910-141917/augmentations/batchProcessingAugmentation.d.ts
vendored
Normal file
97
.recovery-workspace/dist-backup-20250910-141917/augmentations/batchProcessingAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Batch Processing Augmentation
|
||||
*
|
||||
* Critical for enterprise-scale performance: 500,000+ operations/second
|
||||
* Automatically batches operations for maximum throughput
|
||||
* Handles streaming data, bulk imports, and high-frequency operations
|
||||
*
|
||||
* Performance Impact: 10-50x improvement for bulk operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { AugmentationManifest } from './manifest.js';
|
||||
interface BatchConfig {
|
||||
enabled?: boolean;
|
||||
adaptiveMode?: boolean;
|
||||
immediateThreshold?: number;
|
||||
batchThreshold?: number;
|
||||
maxBatchSize?: number;
|
||||
maxWaitTime?: number;
|
||||
adaptiveBatching?: boolean;
|
||||
priorityLanes?: number;
|
||||
memoryLimit?: number;
|
||||
}
|
||||
interface BatchMetrics {
|
||||
totalOperations: number;
|
||||
batchesProcessed: number;
|
||||
averageBatchSize: number;
|
||||
averageLatency: number;
|
||||
throughputPerSecond: number;
|
||||
memoryUsage: number;
|
||||
adaptiveAdjustments: number;
|
||||
}
|
||||
export declare class BatchProcessingAugmentation extends BaseAugmentation {
|
||||
readonly metadata: "readonly";
|
||||
name: string;
|
||||
timing: "around";
|
||||
operations: ("add" | "addNoun" | "addVerb" | "saveNoun" | "saveVerb" | "storage")[];
|
||||
priority: number;
|
||||
protected config: Required<BatchConfig>;
|
||||
private batches;
|
||||
private flushTimers;
|
||||
private metrics;
|
||||
private currentMemoryUsage;
|
||||
private performanceHistory;
|
||||
constructor(config?: BatchConfig);
|
||||
getManifest(): AugmentationManifest;
|
||||
protected onInitialize(): Promise<void>;
|
||||
shouldExecute(operation: string, params: any): boolean;
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
private shouldBatch;
|
||||
/**
|
||||
* SMART WORKFLOW DETECTION METHODS
|
||||
* These methods detect critical patterns that must not be batched
|
||||
*/
|
||||
private isEntityRegistryWorkflow;
|
||||
private isDependencyChainStart;
|
||||
private isWriteOnlyMode;
|
||||
private hasEntityRegistryMetadata;
|
||||
private getOperationContext;
|
||||
private getCurrentLoad;
|
||||
private addToBatch;
|
||||
private getOperationPriority;
|
||||
private getBatchKey;
|
||||
private getOperationType;
|
||||
private estimateOperationSize;
|
||||
private shouldFlushBatch;
|
||||
private extractPriorityFromKey;
|
||||
private setFlushTimer;
|
||||
private getAdaptiveWaitTime;
|
||||
private getPerformanceMultiplier;
|
||||
private flushBatch;
|
||||
private processBatch;
|
||||
private processBatchByType;
|
||||
private processBatchSave;
|
||||
private processBatchUpdate;
|
||||
private processBatchDelete;
|
||||
private processIndividually;
|
||||
private processWithConcurrency;
|
||||
private flushOldestBatch;
|
||||
private updateMetrics;
|
||||
private adjustBatchSize;
|
||||
private startMetricsCollection;
|
||||
/**
|
||||
* Get batch processing statistics
|
||||
*/
|
||||
getStats(): BatchMetrics & {
|
||||
pendingBatches: number;
|
||||
pendingOperations: number;
|
||||
currentBatchSize: number;
|
||||
memoryUtilization: string;
|
||||
};
|
||||
/**
|
||||
* Force flush all pending batches
|
||||
*/
|
||||
flushAll(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,669 @@
|
|||
/**
|
||||
* Batch Processing Augmentation
|
||||
*
|
||||
* Critical for enterprise-scale performance: 500,000+ operations/second
|
||||
* Automatically batches operations for maximum throughput
|
||||
* Handles streaming data, bulk imports, and high-frequency operations
|
||||
*
|
||||
* Performance Impact: 10-50x improvement for bulk operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export class BatchProcessingAugmentation extends BaseAugmentation {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
this.metadata = 'readonly'; // Reads metadata for batching decisions
|
||||
this.name = 'BatchProcessing';
|
||||
this.timing = 'around';
|
||||
this.operations = ['add', 'addNoun', 'addVerb', 'saveNoun', 'saveVerb', 'storage'];
|
||||
this.priority = 80; // High priority for performance
|
||||
this.config = {
|
||||
enabled: true,
|
||||
adaptiveMode: true,
|
||||
immediateThreshold: 1,
|
||||
batchThreshold: 5,
|
||||
maxBatchSize: 100,
|
||||
maxWaitTime: 1000,
|
||||
adaptiveBatching: true,
|
||||
priorityLanes: 2,
|
||||
memoryLimit: 100 * 1024 * 1024 // 100MB
|
||||
};
|
||||
this.batches = new Map();
|
||||
this.flushTimers = new Map();
|
||||
this.metrics = {
|
||||
totalOperations: 0,
|
||||
batchesProcessed: 0,
|
||||
averageBatchSize: 0,
|
||||
averageLatency: 0,
|
||||
throughputPerSecond: 0,
|
||||
memoryUsage: 0,
|
||||
adaptiveAdjustments: 0
|
||||
};
|
||||
this.currentMemoryUsage = 0;
|
||||
this.performanceHistory = [];
|
||||
}
|
||||
getManifest() {
|
||||
return {
|
||||
id: 'batch-processing',
|
||||
name: 'Batch Processing',
|
||||
version: '2.0.0',
|
||||
description: 'High-performance batching for bulk operations',
|
||||
longDescription: 'Automatically batches operations for maximum throughput. Essential for enterprise-scale workloads, achieving 500,000+ operations/second. Provides 10-50x performance improvement for bulk operations.',
|
||||
category: 'performance',
|
||||
configSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Enable batch processing'
|
||||
},
|
||||
adaptiveMode: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Automatically decide when to batch operations'
|
||||
},
|
||||
immediateThreshold: {
|
||||
type: 'number',
|
||||
default: 1,
|
||||
minimum: 1,
|
||||
maximum: 10,
|
||||
description: 'Operations count below which to execute immediately'
|
||||
},
|
||||
batchThreshold: {
|
||||
type: 'number',
|
||||
default: 5,
|
||||
minimum: 2,
|
||||
maximum: 100,
|
||||
description: 'Queue size at which to start batching'
|
||||
},
|
||||
maxBatchSize: {
|
||||
type: 'number',
|
||||
default: 1000,
|
||||
minimum: 10,
|
||||
maximum: 10000,
|
||||
description: 'Maximum items per batch'
|
||||
},
|
||||
maxWaitTime: {
|
||||
type: 'number',
|
||||
default: 100,
|
||||
minimum: 1,
|
||||
maximum: 5000,
|
||||
description: 'Maximum wait time before flushing batch (ms)'
|
||||
},
|
||||
adaptiveBatching: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Dynamically adjust batch size based on performance'
|
||||
},
|
||||
priorityLanes: {
|
||||
type: 'number',
|
||||
default: 3,
|
||||
minimum: 1,
|
||||
maximum: 10,
|
||||
description: 'Number of priority processing lanes'
|
||||
},
|
||||
memoryLimit: {
|
||||
type: 'number',
|
||||
default: 104857600, // 100MB
|
||||
minimum: 10485760, // 10MB
|
||||
maximum: 1073741824, // 1GB
|
||||
description: 'Maximum memory for batching in bytes'
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
},
|
||||
configDefaults: {
|
||||
enabled: true,
|
||||
adaptiveMode: true,
|
||||
immediateThreshold: 1,
|
||||
batchThreshold: 5,
|
||||
maxBatchSize: 1000,
|
||||
maxWaitTime: 100,
|
||||
adaptiveBatching: true,
|
||||
priorityLanes: 3,
|
||||
memoryLimit: 104857600
|
||||
},
|
||||
minBrainyVersion: '2.0.0',
|
||||
keywords: ['batch', 'performance', 'bulk', 'streaming', 'throughput'],
|
||||
documentation: 'https://docs.brainy.dev/augmentations/batch-processing',
|
||||
status: 'stable',
|
||||
performance: {
|
||||
memoryUsage: 'high',
|
||||
cpuUsage: 'medium',
|
||||
networkUsage: 'none'
|
||||
},
|
||||
features: ['auto-batching', 'adaptive-sizing', 'priority-lanes', 'streaming-support'],
|
||||
enhancedOperations: ['add', 'addNoun', 'addVerb', 'saveNoun', 'saveVerb'],
|
||||
ui: {
|
||||
icon: '📦',
|
||||
color: '#9C27B0'
|
||||
}
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (this.config.enabled) {
|
||||
this.startMetricsCollection();
|
||||
this.log(`Batch processing initialized: ${this.config.maxBatchSize} batch size, ${this.config.maxWaitTime}ms max wait`);
|
||||
if (this.config.adaptiveBatching) {
|
||||
this.log('Adaptive batching enabled - will optimize batch size dynamically');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.log('Batch processing disabled');
|
||||
}
|
||||
}
|
||||
shouldExecute(operation, params) {
|
||||
if (!this.config.enabled)
|
||||
return false;
|
||||
// Skip batching for single operations or already-batched operations
|
||||
if (params?.batch === false || params?.streaming === false)
|
||||
return false;
|
||||
// Enable for high-volume operations
|
||||
return operation.includes('add') ||
|
||||
operation.includes('save') ||
|
||||
operation.includes('storage');
|
||||
}
|
||||
async execute(operation, params, next) {
|
||||
if (!this.shouldExecute(operation, params)) {
|
||||
return next();
|
||||
}
|
||||
// Check if this should be batched based on system load
|
||||
if (this.shouldBatch(operation, params)) {
|
||||
return this.addToBatch(operation, params, next);
|
||||
}
|
||||
// Execute immediately for low-latency requirements
|
||||
return next();
|
||||
}
|
||||
shouldBatch(operation, params) {
|
||||
// ZERO-CONFIG INTELLIGENT ADAPTATION:
|
||||
if (this.config.adaptiveMode) {
|
||||
// CRITICAL WORKFLOW DETECTION: Never batch operations that break critical patterns
|
||||
// 1. ENTITY REGISTRY PATTERN: Never batch when immediate lookup is expected
|
||||
if (this.isEntityRegistryWorkflow(operation, params)) {
|
||||
return false; // Must be immediate for registry lookups to work
|
||||
}
|
||||
// 2. DEPENDENCY CHAIN PATTERN: Never batch when next operation depends on this one
|
||||
if (this.isDependencyChainStart(operation, params)) {
|
||||
return false; // Must be immediate for noun → verb workflows
|
||||
}
|
||||
// Count pending operations in the current operation's batch (needed for write-only mode)
|
||||
const batchKey = this.getBatchKey(operation, params);
|
||||
const currentBatch = this.batches.get(batchKey) || [];
|
||||
const pendingCount = currentBatch.length;
|
||||
// 3. WRITE-ONLY MODE: Special handling for high-speed streaming
|
||||
if (this.isWriteOnlyMode(params)) {
|
||||
// In write-only mode, batch aggressively but ensure entity registry updates immediately
|
||||
if (this.hasEntityRegistryMetadata(params)) {
|
||||
return false; // Entity registry updates must be immediate even in write-only mode
|
||||
}
|
||||
return pendingCount >= 3; // Lower threshold for write-only mode batching
|
||||
}
|
||||
// Apply intelligent thresholds:
|
||||
// 4. Single operations are immediate (responsive user experience)
|
||||
if (pendingCount < this.config.immediateThreshold) {
|
||||
return false; // Execute immediately
|
||||
}
|
||||
// 5. Start batching when multiple operations are queued
|
||||
if (pendingCount >= this.config.batchThreshold) {
|
||||
return true; // Batch for efficiency
|
||||
}
|
||||
// 6. For in-between cases, use smart heuristics
|
||||
const currentLoad = this.getCurrentLoad();
|
||||
if (currentLoad > 0.5)
|
||||
return true; // Higher load = more batching
|
||||
// 7. Batch operations that naturally benefit from grouping
|
||||
if (operation.includes('save') || operation.includes('add')) {
|
||||
return pendingCount > 1; // Batch if others are already waiting
|
||||
}
|
||||
return false; // Default to immediate for best responsiveness
|
||||
}
|
||||
// TRADITIONAL MODE: (for explicit configuration scenarios)
|
||||
// Always batch if explicitly requested
|
||||
if (params?.batch === true || params?.streaming === true)
|
||||
return true;
|
||||
// Batch based on current system load
|
||||
const currentLoad = this.getCurrentLoad();
|
||||
if (currentLoad > 0.7)
|
||||
return true; // High load - batch everything
|
||||
// Batch operations that benefit from grouping
|
||||
return operation.includes('save') ||
|
||||
operation.includes('add') ||
|
||||
operation.includes('update');
|
||||
}
|
||||
/**
|
||||
* SMART WORKFLOW DETECTION METHODS
|
||||
* These methods detect critical patterns that must not be batched
|
||||
*/
|
||||
isEntityRegistryWorkflow(operation, params) {
|
||||
// Detect operations that will likely be followed by immediate entity registry lookups
|
||||
if (operation === 'addNoun' || operation === 'add') {
|
||||
// Check if metadata contains external identifiers (DID, handle, etc.)
|
||||
const metadata = params?.metadata || params?.data || {};
|
||||
return !!(metadata.did || // Bluesky DID
|
||||
metadata.handle || // Social media handle
|
||||
metadata.uri || // Resource URI
|
||||
metadata.external_id || // External system ID
|
||||
metadata.user_id || // User ID
|
||||
metadata.profile_id || // Profile ID
|
||||
metadata.account_id // Account ID
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
isDependencyChainStart(operation, params) {
|
||||
// Detect operations that are likely to be followed by dependent operations
|
||||
if (operation === 'addNoun' || operation === 'add') {
|
||||
// In interactive workflows, noun creation is often followed by verb creation
|
||||
// Use heuristics to detect this pattern
|
||||
const context = this.getOperationContext();
|
||||
// If we've seen recent addVerb operations, this noun might be for a relationship
|
||||
if (context.recentVerbOperations > 0) {
|
||||
return true;
|
||||
}
|
||||
// If this is part of a rapid sequence of operations, it might be a dependency chain
|
||||
if (context.operationsInLastSecond > 3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
isWriteOnlyMode(params) {
|
||||
// Detect write-only mode from context or parameters
|
||||
return !!(params?.writeOnlyMode ||
|
||||
params?.streaming ||
|
||||
params?.highThroughput ||
|
||||
this.context?.brain?.writeOnly);
|
||||
}
|
||||
hasEntityRegistryMetadata(params) {
|
||||
// Check if this operation has metadata that needs immediate entity registry updates
|
||||
const metadata = params?.metadata || params?.data || {};
|
||||
return !!(metadata.did ||
|
||||
metadata.handle ||
|
||||
metadata.uri ||
|
||||
metadata.external_id ||
|
||||
// Also check for auto-registration hints
|
||||
params?.autoCreateMissingNouns ||
|
||||
params?.entityRegistry);
|
||||
}
|
||||
getOperationContext() {
|
||||
const now = Date.now();
|
||||
const oneSecondAgo = now - 1000;
|
||||
let recentVerbOperations = 0;
|
||||
let operationsInLastSecond = 0;
|
||||
// Analyze recent operations across all batches
|
||||
for (const batch of this.batches.values()) {
|
||||
for (const op of batch) {
|
||||
if (op.timestamp > oneSecondAgo) {
|
||||
operationsInLastSecond++;
|
||||
if (op.operation.includes('Verb') || op.operation.includes('verb')) {
|
||||
recentVerbOperations++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { recentVerbOperations, operationsInLastSecond };
|
||||
}
|
||||
getCurrentLoad() {
|
||||
// Simple load calculation based on pending operations
|
||||
let totalPending = 0;
|
||||
for (const batch of this.batches.values()) {
|
||||
totalPending += batch.length;
|
||||
}
|
||||
return Math.min(totalPending / 10000, 1.0); // Normalize to 0-1
|
||||
}
|
||||
async addToBatch(operation, params, executor) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const priority = this.getOperationPriority(operation, params);
|
||||
const batchKey = this.getBatchKey(operation, priority);
|
||||
const operationSize = this.estimateOperationSize(params);
|
||||
// Check memory limit
|
||||
if (this.currentMemoryUsage + operationSize > this.config.memoryLimit) {
|
||||
// Memory limit reached - flush oldest batch
|
||||
this.flushOldestBatch();
|
||||
}
|
||||
const batchedOp = {
|
||||
id: `op_${Date.now()}_${Math.random()}`,
|
||||
operation,
|
||||
params,
|
||||
executor, // Store the actual executor
|
||||
resolver: resolve,
|
||||
rejector: reject,
|
||||
timestamp: Date.now(),
|
||||
priority,
|
||||
size: operationSize
|
||||
};
|
||||
// Add to appropriate batch
|
||||
if (!this.batches.has(batchKey)) {
|
||||
this.batches.set(batchKey, []);
|
||||
}
|
||||
const batch = this.batches.get(batchKey);
|
||||
batch.push(batchedOp);
|
||||
this.currentMemoryUsage += operationSize;
|
||||
this.metrics.totalOperations++;
|
||||
// Check if batch should be flushed immediately
|
||||
if (this.shouldFlushBatch(batch, batchKey)) {
|
||||
this.flushBatch(batchKey);
|
||||
}
|
||||
else if (!this.flushTimers.has(batchKey)) {
|
||||
// Set flush timer if not already set
|
||||
this.setFlushTimer(batchKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
getOperationPriority(operation, params) {
|
||||
// Explicit priority
|
||||
if (params?.priority !== undefined)
|
||||
return params.priority;
|
||||
// Operation-based priority
|
||||
if (operation.includes('delete'))
|
||||
return 10; // Highest
|
||||
if (operation.includes('update'))
|
||||
return 8;
|
||||
if (operation.includes('save'))
|
||||
return 6;
|
||||
if (operation.includes('add'))
|
||||
return 4;
|
||||
return 1; // Lowest
|
||||
}
|
||||
getBatchKey(operation, priority) {
|
||||
// Group by operation type and priority for optimal batching
|
||||
const opType = this.getOperationType(operation);
|
||||
const priorityLane = Math.min(priority, this.config.priorityLanes - 1);
|
||||
return `${opType}_p${priorityLane}`;
|
||||
}
|
||||
getOperationType(operation) {
|
||||
if (operation.includes('add'))
|
||||
return 'add';
|
||||
if (operation.includes('save'))
|
||||
return 'save';
|
||||
if (operation.includes('update'))
|
||||
return 'update';
|
||||
if (operation.includes('delete'))
|
||||
return 'delete';
|
||||
return 'other';
|
||||
}
|
||||
estimateOperationSize(params) {
|
||||
// Rough estimation of memory usage
|
||||
if (!params)
|
||||
return 100;
|
||||
let size = 0;
|
||||
if (params.vector && Array.isArray(params.vector)) {
|
||||
size += params.vector.length * 8; // 8 bytes per float64
|
||||
}
|
||||
if (params.data) {
|
||||
size += JSON.stringify(params.data).length * 2; // Rough UTF-16 estimate
|
||||
}
|
||||
if (params.metadata) {
|
||||
size += JSON.stringify(params.metadata).length * 2;
|
||||
}
|
||||
return Math.max(size, 100); // Minimum 100 bytes
|
||||
}
|
||||
shouldFlushBatch(batch, batchKey) {
|
||||
// Flush if batch is full
|
||||
if (batch.length >= this.config.maxBatchSize)
|
||||
return true;
|
||||
// Flush if memory limit approaching
|
||||
if (this.currentMemoryUsage > this.config.memoryLimit * 0.9)
|
||||
return true;
|
||||
// Flush high-priority batches more aggressively
|
||||
const priority = this.extractPriorityFromKey(batchKey);
|
||||
if (priority >= 8 && batch.length >= 100)
|
||||
return true;
|
||||
if (priority >= 6 && batch.length >= 500)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
extractPriorityFromKey(batchKey) {
|
||||
const match = batchKey.match(/_p(\d+)$/);
|
||||
return match ? parseInt(match[1]) : 0;
|
||||
}
|
||||
setFlushTimer(batchKey) {
|
||||
const priority = this.extractPriorityFromKey(batchKey);
|
||||
const waitTime = this.getAdaptiveWaitTime(priority);
|
||||
const timer = setTimeout(() => {
|
||||
this.flushBatch(batchKey);
|
||||
}, waitTime);
|
||||
this.flushTimers.set(batchKey, timer);
|
||||
}
|
||||
getAdaptiveWaitTime(priority) {
|
||||
if (!this.config.adaptiveBatching) {
|
||||
return this.config.maxWaitTime;
|
||||
}
|
||||
// Adaptive wait time based on performance and priority
|
||||
const baseWaitTime = this.config.maxWaitTime;
|
||||
const performanceMultiplier = this.getPerformanceMultiplier();
|
||||
const priorityMultiplier = priority >= 8 ? 0.5 : priority >= 6 ? 0.7 : 1.0;
|
||||
return Math.max(baseWaitTime * performanceMultiplier * priorityMultiplier, 10);
|
||||
}
|
||||
getPerformanceMultiplier() {
|
||||
if (this.performanceHistory.length < 10)
|
||||
return 1.0;
|
||||
// Calculate average latency trend
|
||||
const recent = this.performanceHistory.slice(-10);
|
||||
const average = recent.reduce((a, b) => a + b, 0) / recent.length;
|
||||
// If performance is degrading, reduce wait time
|
||||
if (average > this.metrics.averageLatency * 1.2)
|
||||
return 0.7;
|
||||
if (average < this.metrics.averageLatency * 0.8)
|
||||
return 1.3;
|
||||
return 1.0;
|
||||
}
|
||||
async flushBatch(batchKey) {
|
||||
const batch = this.batches.get(batchKey);
|
||||
if (!batch || batch.length === 0)
|
||||
return;
|
||||
// Clear timer
|
||||
const timer = this.flushTimers.get(batchKey);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
this.flushTimers.delete(batchKey);
|
||||
}
|
||||
// Remove batch from queue
|
||||
this.batches.delete(batchKey);
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
await this.processBatch(batch);
|
||||
// Update metrics
|
||||
const latency = Date.now() - startTime;
|
||||
this.updateMetrics(batch.length, latency);
|
||||
// Adaptive adjustment
|
||||
if (this.config.adaptiveBatching) {
|
||||
this.adjustBatchSize(latency, batch.length);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Batch processing failed for ${batchKey}: ${error}`, 'error');
|
||||
// Reject all operations in batch
|
||||
batch.forEach(op => {
|
||||
op.rejector(error);
|
||||
this.currentMemoryUsage -= op.size;
|
||||
});
|
||||
}
|
||||
}
|
||||
async processBatch(batch) {
|
||||
// Group by operation type for efficient processing
|
||||
const operationGroups = new Map();
|
||||
for (const op of batch) {
|
||||
const opType = this.getOperationType(op.operation);
|
||||
if (!operationGroups.has(opType)) {
|
||||
operationGroups.set(opType, []);
|
||||
}
|
||||
operationGroups.get(opType).push(op);
|
||||
}
|
||||
// Process each operation type
|
||||
for (const [opType, operations] of operationGroups) {
|
||||
await this.processBatchByType(opType, operations);
|
||||
}
|
||||
}
|
||||
async processBatchByType(opType, operations) {
|
||||
// Execute batch operation based on type
|
||||
try {
|
||||
if (opType === 'add' || opType === 'save') {
|
||||
await this.processBatchSave(operations);
|
||||
}
|
||||
else if (opType === 'update') {
|
||||
await this.processBatchUpdate(operations);
|
||||
}
|
||||
else if (opType === 'delete') {
|
||||
await this.processBatchDelete(operations);
|
||||
}
|
||||
else {
|
||||
// Fallback: execute individually
|
||||
await this.processIndividually(operations);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async processBatchSave(operations) {
|
||||
// Try to use storage's bulk save if available
|
||||
const storage = this.context?.storage;
|
||||
if (storage && typeof storage.saveBatch === 'function') {
|
||||
// Use bulk save operation
|
||||
const items = operations.map(op => ({
|
||||
...op.params,
|
||||
_batchId: op.id
|
||||
}));
|
||||
try {
|
||||
const results = await storage.saveBatch(items);
|
||||
// Resolve all operations with actual results
|
||||
operations.forEach((op, index) => {
|
||||
op.resolver(results[index] || op.params.id);
|
||||
this.currentMemoryUsage -= op.size;
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
// Reject all operations on batch error
|
||||
operations.forEach(op => {
|
||||
op.rejector(error);
|
||||
this.currentMemoryUsage -= op.size;
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Execute using stored executors with concurrency control
|
||||
await this.processWithConcurrency(operations, 10);
|
||||
}
|
||||
}
|
||||
async processBatchUpdate(operations) {
|
||||
await this.processWithConcurrency(operations, 5); // Lower concurrency for updates
|
||||
}
|
||||
async processBatchDelete(operations) {
|
||||
await this.processWithConcurrency(operations, 5); // Lower concurrency for deletes
|
||||
}
|
||||
async processIndividually(operations) {
|
||||
await this.processWithConcurrency(operations, 3); // Conservative concurrency
|
||||
}
|
||||
async processWithConcurrency(operations, concurrency) {
|
||||
const promises = [];
|
||||
for (let i = 0; i < operations.length; i += concurrency) {
|
||||
const chunk = operations.slice(i, i + concurrency);
|
||||
const chunkPromise = Promise.all(chunk.map(async (op) => {
|
||||
try {
|
||||
// Execute using the stored executor function - REAL EXECUTION!
|
||||
const result = await op.executor();
|
||||
op.resolver(result);
|
||||
this.currentMemoryUsage -= op.size;
|
||||
}
|
||||
catch (error) {
|
||||
op.rejector(error);
|
||||
this.currentMemoryUsage -= op.size;
|
||||
}
|
||||
})).then(() => { }); // Convert to void promise
|
||||
promises.push(chunkPromise);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
// REMOVED executeOperation - no longer needed since we use stored executors
|
||||
flushOldestBatch() {
|
||||
if (this.batches.size === 0)
|
||||
return;
|
||||
// Find oldest batch
|
||||
let oldestKey = '';
|
||||
let oldestTime = Infinity;
|
||||
for (const [key, batch] of this.batches) {
|
||||
if (batch.length > 0) {
|
||||
const batchAge = Math.min(...batch.map(op => op.timestamp));
|
||||
if (batchAge < oldestTime) {
|
||||
oldestTime = batchAge;
|
||||
oldestKey = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldestKey) {
|
||||
this.flushBatch(oldestKey);
|
||||
}
|
||||
}
|
||||
updateMetrics(batchSize, latency) {
|
||||
this.metrics.batchesProcessed++;
|
||||
this.metrics.averageBatchSize =
|
||||
(this.metrics.averageBatchSize * (this.metrics.batchesProcessed - 1) + batchSize) /
|
||||
this.metrics.batchesProcessed;
|
||||
// Update latency with exponential moving average
|
||||
this.metrics.averageLatency = this.metrics.averageLatency * 0.9 + latency * 0.1;
|
||||
// Add to performance history
|
||||
this.performanceHistory.push(latency);
|
||||
if (this.performanceHistory.length > 100) {
|
||||
this.performanceHistory.shift();
|
||||
}
|
||||
}
|
||||
adjustBatchSize(latency, batchSize) {
|
||||
const targetLatency = this.config.maxWaitTime * 5; // Target: 5x wait time
|
||||
if (latency > targetLatency && batchSize > 100) {
|
||||
// Reduce batch size if latency too high
|
||||
this.config.maxBatchSize = Math.max(this.config.maxBatchSize * 0.9, 100);
|
||||
this.metrics.adaptiveAdjustments++;
|
||||
}
|
||||
else if (latency < targetLatency * 0.5 && batchSize === this.config.maxBatchSize) {
|
||||
// Increase batch size if latency very low
|
||||
this.config.maxBatchSize = Math.min(this.config.maxBatchSize * 1.1, 10000);
|
||||
this.metrics.adaptiveAdjustments++;
|
||||
}
|
||||
}
|
||||
startMetricsCollection() {
|
||||
setInterval(() => {
|
||||
// Calculate throughput
|
||||
this.metrics.throughputPerSecond = this.metrics.totalOperations;
|
||||
this.metrics.totalOperations = 0; // Reset for next measurement
|
||||
// Update memory usage
|
||||
this.metrics.memoryUsage = this.currentMemoryUsage;
|
||||
}, 1000);
|
||||
}
|
||||
/**
|
||||
* Get batch processing statistics
|
||||
*/
|
||||
getStats() {
|
||||
let pendingOperations = 0;
|
||||
for (const batch of this.batches.values()) {
|
||||
pendingOperations += batch.length;
|
||||
}
|
||||
return {
|
||||
...this.metrics,
|
||||
pendingBatches: this.batches.size,
|
||||
pendingOperations,
|
||||
currentBatchSize: this.config.maxBatchSize,
|
||||
memoryUtilization: `${Math.round((this.currentMemoryUsage / this.config.memoryLimit) * 100)}%`
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Force flush all pending batches
|
||||
*/
|
||||
async flushAll() {
|
||||
const batchKeys = Array.from(this.batches.keys());
|
||||
await Promise.all(batchKeys.map(key => this.flushBatch(key)));
|
||||
}
|
||||
async onShutdown() {
|
||||
// Clear all timers
|
||||
for (const timer of this.flushTimers.values()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
this.flushTimers.clear();
|
||||
// Flush all pending batches
|
||||
await this.flushAll();
|
||||
const stats = this.getStats();
|
||||
this.log(`Batch processing shutdown: ${this.metrics.batchesProcessed} batches processed, ${stats.memoryUtilization} peak memory usage`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=batchProcessingAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
304
.recovery-workspace/dist-backup-20250910-141917/augmentations/brainyAugmentation.d.ts
vendored
Normal file
304
.recovery-workspace/dist-backup-20250910-141917/augmentations/brainyAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
/**
|
||||
* Single BrainyAugmentation Interface
|
||||
*
|
||||
* This replaces the 7 complex interfaces with one elegant, purpose-driven design.
|
||||
* Each augmentation knows its place and when to execute automatically.
|
||||
*
|
||||
* The Vision: Components that enhance Brainy's capabilities seamlessly
|
||||
* - WAL: Adds durability to storage operations
|
||||
* - RequestDeduplicator: Prevents duplicate concurrent requests
|
||||
* - ConnectionPool: Optimizes cloud storage throughput
|
||||
* - IntelligentVerbScoring: Enhances relationship analysis
|
||||
* - StreamingPipeline: Enables unlimited data processing
|
||||
*/
|
||||
import { AugmentationManifest } from './manifest.js';
|
||||
/**
|
||||
* Metadata access declaration for augmentations
|
||||
*/
|
||||
export interface MetadataAccess {
|
||||
reads?: string[] | '*';
|
||||
writes?: string[] | '*';
|
||||
namespace?: string;
|
||||
}
|
||||
export interface BrainyAugmentation {
|
||||
/**
|
||||
* Unique identifier for the augmentation
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* When this augmentation should execute
|
||||
* - 'before': Execute before the main operation
|
||||
* - 'after': Execute after the main operation
|
||||
* - 'around': Wrap the main operation (like middleware)
|
||||
* - 'replace': Replace the main operation entirely
|
||||
*/
|
||||
timing: 'before' | 'after' | 'around' | 'replace';
|
||||
/**
|
||||
* Metadata access contract - REQUIRED
|
||||
* - 'none': No metadata access at all
|
||||
* - 'readonly': Can read any metadata but cannot write
|
||||
* - MetadataAccess: Specific fields to read/write
|
||||
*/
|
||||
metadata: 'none' | 'readonly' | MetadataAccess;
|
||||
/**
|
||||
* Which operations this augmentation applies to
|
||||
* Granular operation matching for precise augmentation targeting
|
||||
*/
|
||||
operations: ('add' | 'addNoun' | 'addVerb' | 'saveNoun' | 'saveVerb' | 'updateMetadata' | 'update' | 'delete' | 'deleteVerb' | 'clear' | 'get' | 'search' | 'searchText' | 'searchByNounTypes' | 'find' | 'findSimilar' | 'searchWithCursor' | 'similar' | 'relate' | 'unrelate' | 'getConnections' | 'getRelations' | 'storage' | 'backup' | 'restore' | 'all')[];
|
||||
/**
|
||||
* Priority for execution order (higher numbers execute first)
|
||||
* - 100: Critical system operations (WAL, ConnectionPool)
|
||||
* - 50: Performance optimizations (RequestDeduplicator, Caching)
|
||||
* - 10: Enhancement features (IntelligentVerbScoring)
|
||||
* - 1: Optional features (Logging, Analytics)
|
||||
*/
|
||||
priority: number;
|
||||
/**
|
||||
* Initialize the augmentation
|
||||
* Called once during Brainy initialization
|
||||
*
|
||||
* @param context - The Brainy instance and storage
|
||||
*/
|
||||
initialize(context: AugmentationContext): Promise<void>;
|
||||
/**
|
||||
* Execute the augmentation
|
||||
*
|
||||
* @param operation - The operation being performed
|
||||
* @param params - Parameters for the operation
|
||||
* @param next - Function to call the next augmentation or main operation
|
||||
* @returns Result of the operation
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Optional: Check if this augmentation should run for the given operation
|
||||
* Return false to skip execution
|
||||
*/
|
||||
shouldExecute?(operation: string, params: any): boolean;
|
||||
/**
|
||||
* Optional: Cleanup when Brainy is destroyed
|
||||
*/
|
||||
shutdown?(): Promise<void>;
|
||||
/**
|
||||
* Optional: Computed fields this augmentation provides
|
||||
* Used for discovery, TypeScript support, and API documentation
|
||||
*/
|
||||
computedFields?: {
|
||||
[namespace: string]: {
|
||||
[field: string]: {
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
||||
description: string;
|
||||
confidence?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Optional: Compute fields for a result entity
|
||||
* Called when user accesses getDisplay(), getSchema(), etc.
|
||||
*
|
||||
* @param result - The result entity (VectorDocument, GraphVerb, etc.)
|
||||
* @param namespace - The namespace being requested ('display', 'schema', etc.)
|
||||
* @returns Computed fields for the namespace
|
||||
*/
|
||||
computeFields?(result: any, namespace: string): Promise<Record<string, any>> | Record<string, any>;
|
||||
}
|
||||
/**
|
||||
* Context provided to augmentations
|
||||
*/
|
||||
export interface AugmentationContext {
|
||||
/**
|
||||
* The Brainy instance (for accessing methods and config)
|
||||
*/
|
||||
brain: any;
|
||||
/**
|
||||
* The storage adapter
|
||||
*/
|
||||
storage: any;
|
||||
/**
|
||||
* Configuration for this augmentation
|
||||
*/
|
||||
config: any;
|
||||
/**
|
||||
* Logging function
|
||||
*/
|
||||
log: (message: string, level?: 'info' | 'warn' | 'error') => void;
|
||||
}
|
||||
/**
|
||||
* Base class for augmentations with common functionality
|
||||
*
|
||||
* This is the unified base class that combines the features of both
|
||||
* BaseAugmentation and ConfigurableAugmentation. All augmentations
|
||||
* should extend this class for consistent configuration support.
|
||||
*/
|
||||
export declare abstract class BaseAugmentation implements BrainyAugmentation {
|
||||
abstract name: string;
|
||||
abstract timing: 'before' | 'after' | 'around' | 'replace';
|
||||
abstract metadata: 'none' | 'readonly' | MetadataAccess;
|
||||
abstract operations: ('add' | 'addNoun' | 'addVerb' | 'saveNoun' | 'saveVerb' | 'updateMetadata' | 'update' | 'delete' | 'deleteVerb' | 'clear' | 'get' | 'search' | 'searchText' | 'searchByNounTypes' | 'find' | 'findSimilar' | 'searchWithCursor' | 'similar' | 'relate' | 'unrelate' | 'getConnections' | 'getRelations' | 'storage' | 'backup' | 'restore' | 'all')[];
|
||||
abstract priority: number;
|
||||
category: 'internal' | 'core' | 'premium' | 'community' | 'external';
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
protected context?: AugmentationContext;
|
||||
protected isInitialized: boolean;
|
||||
protected config: any;
|
||||
private configResolver?;
|
||||
/**
|
||||
* Constructor with optional configuration
|
||||
* @param config Optional configuration to override defaults
|
||||
*/
|
||||
constructor(config?: any);
|
||||
/**
|
||||
* Get the augmentation manifest for discovery
|
||||
* Override this to enable configuration support
|
||||
* CRITICAL: This enables tools to discover parameters and configuration
|
||||
*/
|
||||
getManifest?(): AugmentationManifest;
|
||||
/**
|
||||
* Get parameter schema for operations
|
||||
* Enables tools to know what parameters each operation needs
|
||||
*/
|
||||
getParameterSchema?(operation: string): any;
|
||||
/**
|
||||
* Get operation descriptions
|
||||
* Enables tools to show what each operation does
|
||||
*/
|
||||
getOperationInfo?(): Record<string, {
|
||||
description: string;
|
||||
parameters?: any;
|
||||
returns?: any;
|
||||
examples?: any[];
|
||||
}>;
|
||||
/**
|
||||
* Get current configuration
|
||||
*/
|
||||
getConfig(): any;
|
||||
/**
|
||||
* Update configuration at runtime
|
||||
* @param partial Partial configuration to merge
|
||||
*/
|
||||
updateConfig(partial: any): Promise<void>;
|
||||
/**
|
||||
* Optional: Handle configuration changes
|
||||
* Override this to react to runtime configuration updates
|
||||
*/
|
||||
protected onConfigChange?(newConfig: any, oldConfig: any): Promise<void>;
|
||||
/**
|
||||
* Resolve configuration from all sources
|
||||
* Priority: constructor > env > files > defaults
|
||||
*/
|
||||
private resolveConfiguration;
|
||||
initialize(context: AugmentationContext): Promise<void>;
|
||||
/**
|
||||
* Override this in subclasses for initialization logic
|
||||
*/
|
||||
protected onInitialize(): Promise<void>;
|
||||
abstract execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
shouldExecute(operation: string, params: any): boolean;
|
||||
shutdown(): Promise<void>;
|
||||
/**
|
||||
* Override this in subclasses for cleanup logic
|
||||
*/
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Optional computed fields declaration (override in subclasses)
|
||||
*/
|
||||
computedFields?: {
|
||||
[namespace: string]: {
|
||||
[field: string]: {
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
||||
description: string;
|
||||
confidence?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Optional computed fields implementation (override in subclasses)
|
||||
* @param result The result entity
|
||||
* @param namespace The requested namespace
|
||||
* @returns Computed fields for the namespace
|
||||
*/
|
||||
computeFields?(result: any, namespace: string): Promise<Record<string, any>> | Record<string, any>;
|
||||
/**
|
||||
* Log a message with the augmentation name
|
||||
*/
|
||||
protected log(message: string, level?: 'info' | 'warn' | 'error'): void;
|
||||
}
|
||||
/**
|
||||
* Alias for backward compatibility
|
||||
* ConfigurableAugmentation is now merged into BaseAugmentation
|
||||
* @deprecated Use BaseAugmentation instead
|
||||
*/
|
||||
export declare const ConfigurableAugmentation: typeof BaseAugmentation;
|
||||
/**
|
||||
* Registry for managing augmentations
|
||||
*/
|
||||
export declare class AugmentationRegistry {
|
||||
private augmentations;
|
||||
private context?;
|
||||
/**
|
||||
* Register an augmentation
|
||||
*/
|
||||
register(augmentation: BrainyAugmentation): void;
|
||||
/**
|
||||
* Find augmentations by operation (before initialization)
|
||||
* Used for two-phase initialization to find storage augmentations
|
||||
*/
|
||||
findByOperation(operation: string): BrainyAugmentation | null;
|
||||
/**
|
||||
* Initialize all augmentations
|
||||
*/
|
||||
initialize(context: AugmentationContext): Promise<void>;
|
||||
/**
|
||||
* Initialize all augmentations (alias for consistency)
|
||||
*/
|
||||
initializeAll(context: AugmentationContext): Promise<void>;
|
||||
/**
|
||||
* Execute augmentations for an operation
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, mainOperation: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Get all registered augmentations
|
||||
*/
|
||||
getAll(): BrainyAugmentation[];
|
||||
/**
|
||||
* Get augmentation info for listing
|
||||
*/
|
||||
getInfo(): Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
enabled: boolean;
|
||||
description: string;
|
||||
category: string;
|
||||
priority: number;
|
||||
}>;
|
||||
/**
|
||||
* Get augmentations by name
|
||||
*/
|
||||
get(name: string): BrainyAugmentation | undefined;
|
||||
/**
|
||||
* Discover augmentation parameters and schemas
|
||||
* Critical for tools like brain-cloud to generate UIs
|
||||
*/
|
||||
discover(name?: string): any;
|
||||
/**
|
||||
* Get configuration schema for an augmentation
|
||||
* Enables UI generation for configuration
|
||||
*/
|
||||
getConfigSchema(name: string): any;
|
||||
/**
|
||||
* Configure an augmentation at runtime
|
||||
*/
|
||||
configure(name: string, config: any): Promise<void>;
|
||||
/**
|
||||
* Get metrics for an augmentation
|
||||
*/
|
||||
metrics(name?: string): any;
|
||||
/**
|
||||
* Get health status
|
||||
*/
|
||||
health(): any;
|
||||
/**
|
||||
* Shutdown all augmentations
|
||||
*/
|
||||
shutdown(): Promise<void>;
|
||||
}
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
/**
|
||||
* Single BrainyAugmentation Interface
|
||||
*
|
||||
* This replaces the 7 complex interfaces with one elegant, purpose-driven design.
|
||||
* Each augmentation knows its place and when to execute automatically.
|
||||
*
|
||||
* The Vision: Components that enhance Brainy's capabilities seamlessly
|
||||
* - WAL: Adds durability to storage operations
|
||||
* - RequestDeduplicator: Prevents duplicate concurrent requests
|
||||
* - ConnectionPool: Optimizes cloud storage throughput
|
||||
* - IntelligentVerbScoring: Enhances relationship analysis
|
||||
* - StreamingPipeline: Enables unlimited data processing
|
||||
*/
|
||||
import { AugmentationConfigResolver } from './configResolver.js';
|
||||
/**
|
||||
* Base class for augmentations with common functionality
|
||||
*
|
||||
* This is the unified base class that combines the features of both
|
||||
* BaseAugmentation and ConfigurableAugmentation. All augmentations
|
||||
* should extend this class for consistent configuration support.
|
||||
*/
|
||||
export class BaseAugmentation {
|
||||
/**
|
||||
* Constructor with optional configuration
|
||||
* @param config Optional configuration to override defaults
|
||||
*/
|
||||
constructor(config) {
|
||||
// Metadata for augmentation listing and management
|
||||
this.category = 'core';
|
||||
this.description = '';
|
||||
this.enabled = true;
|
||||
this.isInitialized = false;
|
||||
this.config = {};
|
||||
// Only resolve configuration if getManifest is implemented
|
||||
if (this.getManifest) {
|
||||
this.config = this.resolveConfiguration(config);
|
||||
}
|
||||
else if (config) {
|
||||
// Legacy support: direct config assignment for augmentations without manifests
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get current configuration
|
||||
*/
|
||||
getConfig() {
|
||||
return { ...this.config };
|
||||
}
|
||||
/**
|
||||
* Update configuration at runtime
|
||||
* @param partial Partial configuration to merge
|
||||
*/
|
||||
async updateConfig(partial) {
|
||||
if (!this.configResolver) {
|
||||
// For legacy augmentations without manifest, just merge config
|
||||
const oldConfig = this.config;
|
||||
this.config = { ...this.config, ...partial };
|
||||
if (this.onConfigChange) {
|
||||
await this.onConfigChange(this.config, oldConfig);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const oldConfig = this.config;
|
||||
try {
|
||||
// Use resolver to update and validate
|
||||
this.config = this.configResolver.updateRuntime(partial);
|
||||
// Call config change handler if implemented
|
||||
if (this.onConfigChange) {
|
||||
await this.onConfigChange(this.config, oldConfig);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Revert on error
|
||||
this.config = oldConfig;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resolve configuration from all sources
|
||||
* Priority: constructor > env > files > defaults
|
||||
*/
|
||||
resolveConfiguration(constructorConfig) {
|
||||
const manifest = this.getManifest();
|
||||
// Create config resolver
|
||||
this.configResolver = new AugmentationConfigResolver({
|
||||
augmentationId: manifest.id,
|
||||
schema: manifest.configSchema,
|
||||
defaults: manifest.configDefaults
|
||||
});
|
||||
// Resolve configuration from all sources
|
||||
return this.configResolver.resolve(constructorConfig);
|
||||
}
|
||||
async initialize(context) {
|
||||
this.context = context;
|
||||
this.isInitialized = true;
|
||||
await this.onInitialize();
|
||||
}
|
||||
/**
|
||||
* Override this in subclasses for initialization logic
|
||||
*/
|
||||
async onInitialize() {
|
||||
// Default: no-op
|
||||
}
|
||||
shouldExecute(operation, params) {
|
||||
// Default: execute if operations match exactly or includes 'all'
|
||||
return this.operations.includes('all') ||
|
||||
this.operations.includes(operation) ||
|
||||
this.operations.some(op => operation.includes(op));
|
||||
}
|
||||
async shutdown() {
|
||||
await this.onShutdown();
|
||||
this.isInitialized = false;
|
||||
}
|
||||
/**
|
||||
* Override this in subclasses for cleanup logic
|
||||
*/
|
||||
async onShutdown() {
|
||||
// Default: no-op
|
||||
}
|
||||
/**
|
||||
* Log a message with the augmentation name
|
||||
*/
|
||||
log(message, level = 'info') {
|
||||
if (this.context) {
|
||||
this.context.log(`[${this.name}] ${message}`, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Alias for backward compatibility
|
||||
* ConfigurableAugmentation is now merged into BaseAugmentation
|
||||
* @deprecated Use BaseAugmentation instead
|
||||
*/
|
||||
export const ConfigurableAugmentation = BaseAugmentation;
|
||||
/**
|
||||
* Registry for managing augmentations
|
||||
*/
|
||||
export class AugmentationRegistry {
|
||||
constructor() {
|
||||
this.augmentations = [];
|
||||
}
|
||||
/**
|
||||
* Register an augmentation
|
||||
*/
|
||||
register(augmentation) {
|
||||
this.augmentations.push(augmentation);
|
||||
// Sort by priority (highest first)
|
||||
this.augmentations.sort((a, b) => b.priority - a.priority);
|
||||
}
|
||||
/**
|
||||
* Find augmentations by operation (before initialization)
|
||||
* Used for two-phase initialization to find storage augmentations
|
||||
*/
|
||||
findByOperation(operation) {
|
||||
return this.augmentations.find(aug => aug.operations.includes(operation) ||
|
||||
aug.operations.includes('all')) || null;
|
||||
}
|
||||
/**
|
||||
* Initialize all augmentations
|
||||
*/
|
||||
async initialize(context) {
|
||||
this.context = context;
|
||||
for (const augmentation of this.augmentations) {
|
||||
await augmentation.initialize(context);
|
||||
}
|
||||
context.log(`Initialized ${this.augmentations.length} augmentations`);
|
||||
}
|
||||
/**
|
||||
* Initialize all augmentations (alias for consistency)
|
||||
*/
|
||||
async initializeAll(context) {
|
||||
return this.initialize(context);
|
||||
}
|
||||
/**
|
||||
* Execute augmentations for an operation
|
||||
*/
|
||||
async execute(operation, params, mainOperation) {
|
||||
// Filter augmentations that should execute for this operation
|
||||
const applicable = this.augmentations.filter(aug => aug.shouldExecute ? aug.shouldExecute(operation, params) :
|
||||
aug.operations.includes('all') ||
|
||||
aug.operations.includes(operation) ||
|
||||
aug.operations.some(op => operation.includes(op)));
|
||||
if (applicable.length === 0) {
|
||||
// No augmentations, execute main operation directly
|
||||
return mainOperation();
|
||||
}
|
||||
// Create a chain of augmentations
|
||||
let index = 0;
|
||||
const executeNext = async () => {
|
||||
if (index >= applicable.length) {
|
||||
// All augmentations processed, execute main operation
|
||||
return mainOperation();
|
||||
}
|
||||
const augmentation = applicable[index++];
|
||||
return augmentation.execute(operation, params, executeNext);
|
||||
};
|
||||
return executeNext();
|
||||
}
|
||||
/**
|
||||
* Get all registered augmentations
|
||||
*/
|
||||
getAll() {
|
||||
return [...this.augmentations];
|
||||
}
|
||||
/**
|
||||
* Get augmentation info for listing
|
||||
*/
|
||||
getInfo() {
|
||||
return this.augmentations.map(aug => {
|
||||
const baseAug = aug;
|
||||
return {
|
||||
name: aug.name,
|
||||
type: baseAug.category || 'core',
|
||||
enabled: baseAug.enabled !== false,
|
||||
description: baseAug.description || `${aug.name} augmentation`,
|
||||
category: baseAug.category || 'core',
|
||||
priority: aug.priority
|
||||
};
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get augmentations by name
|
||||
*/
|
||||
get(name) {
|
||||
return this.augmentations.find(aug => aug.name === name);
|
||||
}
|
||||
/**
|
||||
* Discover augmentation parameters and schemas
|
||||
* Critical for tools like brain-cloud to generate UIs
|
||||
*/
|
||||
discover(name) {
|
||||
if (name) {
|
||||
const aug = this.get(name);
|
||||
if (!aug)
|
||||
return null;
|
||||
const baseAug = aug;
|
||||
return {
|
||||
name: aug.name,
|
||||
operations: aug.operations,
|
||||
priority: aug.priority,
|
||||
timing: aug.timing,
|
||||
metadata: aug.metadata,
|
||||
manifest: baseAug.getManifest ? baseAug.getManifest() : undefined,
|
||||
parameters: baseAug.getParameterSchema ?
|
||||
aug.operations.reduce((acc, op) => {
|
||||
acc[op] = baseAug.getParameterSchema(op);
|
||||
return acc;
|
||||
}, {}) : undefined,
|
||||
operationInfo: baseAug.getOperationInfo ? baseAug.getOperationInfo() : undefined,
|
||||
config: baseAug.getConfig ? baseAug.getConfig() : undefined
|
||||
};
|
||||
}
|
||||
// Return all augmentations discovery info
|
||||
return this.augmentations.map(aug => this.discover(aug.name));
|
||||
}
|
||||
/**
|
||||
* Get configuration schema for an augmentation
|
||||
* Enables UI generation for configuration
|
||||
*/
|
||||
getConfigSchema(name) {
|
||||
const aug = this.get(name);
|
||||
if (!aug || !aug.getManifest)
|
||||
return null;
|
||||
const manifest = aug.getManifest();
|
||||
return manifest?.configSchema;
|
||||
}
|
||||
/**
|
||||
* Configure an augmentation at runtime
|
||||
*/
|
||||
async configure(name, config) {
|
||||
const aug = this.get(name);
|
||||
if (!aug || !aug.updateConfig) {
|
||||
throw new Error(`Augmentation ${name} does not support configuration`);
|
||||
}
|
||||
await aug.updateConfig(config);
|
||||
}
|
||||
/**
|
||||
* Get metrics for an augmentation
|
||||
*/
|
||||
metrics(name) {
|
||||
if (name) {
|
||||
const aug = this.get(name);
|
||||
if (!aug || !aug.metrics)
|
||||
return null;
|
||||
return aug.metrics();
|
||||
}
|
||||
// Return all metrics
|
||||
const allMetrics = {};
|
||||
for (const aug of this.augmentations) {
|
||||
const a = aug;
|
||||
if (a.metrics) {
|
||||
allMetrics[aug.name] = a.metrics();
|
||||
}
|
||||
}
|
||||
return allMetrics;
|
||||
}
|
||||
/**
|
||||
* Get health status
|
||||
*/
|
||||
health() {
|
||||
const health = {
|
||||
overall: 'healthy',
|
||||
augmentations: {}
|
||||
};
|
||||
for (const aug of this.augmentations) {
|
||||
const a = aug;
|
||||
health.augmentations[aug.name] = a.health ? a.health() : 'unknown';
|
||||
}
|
||||
return health;
|
||||
}
|
||||
/**
|
||||
* Shutdown all augmentations
|
||||
*/
|
||||
async shutdown() {
|
||||
for (const augmentation of this.augmentations) {
|
||||
if (augmentation.shutdown) {
|
||||
await augmentation.shutdown();
|
||||
}
|
||||
}
|
||||
this.augmentations = [];
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=brainyAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/cacheAugmentation.d.ts
vendored
Normal file
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/cacheAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Cache Augmentation - Optional Search Result Caching
|
||||
*
|
||||
* Replaces the hardcoded SearchCache in Brainy with an optional augmentation.
|
||||
* This reduces core size and allows custom cache implementations.
|
||||
*
|
||||
* Zero-config: Automatically enabled with sensible defaults
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { AugmentationManifest } from './manifest.js';
|
||||
import { SearchCache } from '../utils/searchCache.js';
|
||||
import type { GraphNoun } from '../types/graphTypes.js';
|
||||
export interface CacheConfig {
|
||||
maxSize?: number;
|
||||
ttl?: number;
|
||||
enabled?: boolean;
|
||||
invalidateOnWrite?: boolean;
|
||||
}
|
||||
/**
|
||||
* CacheAugmentation - Makes search caching optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - Transparent search result caching
|
||||
* - Automatic invalidation on data changes
|
||||
* - Memory-aware cache management
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export declare class CacheAugmentation extends BaseAugmentation {
|
||||
readonly name = "cache";
|
||||
readonly timing: "around";
|
||||
readonly metadata: "none";
|
||||
operations: ("search" | "find" | "similar" | "add" | "update" | "delete" | "clear" | "all")[];
|
||||
readonly priority = 50;
|
||||
readonly category: "core";
|
||||
readonly description = "Transparent search result caching with automatic invalidation";
|
||||
private searchCache;
|
||||
constructor(config?: CacheConfig);
|
||||
getManifest(): AugmentationManifest;
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute augmentation - wrap operations with caching logic
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Handle search operation with caching
|
||||
*/
|
||||
private handleSearch;
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats(): {
|
||||
enabled: boolean;
|
||||
hits: number;
|
||||
misses: number;
|
||||
size: number;
|
||||
memoryUsage: number;
|
||||
} | {
|
||||
memoryUsage: number;
|
||||
hits: number;
|
||||
misses: number;
|
||||
evictions: number;
|
||||
hitRate: number;
|
||||
size: number;
|
||||
maxSize: number;
|
||||
enabled: boolean;
|
||||
};
|
||||
/**
|
||||
* Clear the cache manually
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Handle runtime configuration changes
|
||||
*/
|
||||
protected onConfigChange(newConfig: CacheConfig, oldConfig: CacheConfig): Promise<void>;
|
||||
/**
|
||||
* Clean up expired entries
|
||||
*/
|
||||
cleanupExpiredEntries(): number;
|
||||
/**
|
||||
* Invalidate cache when data changes
|
||||
*/
|
||||
invalidateOnDataChange(operation: 'add' | 'update' | 'delete'): void;
|
||||
/**
|
||||
* Get cache key for a query
|
||||
*/
|
||||
getCacheKey(query: any, options?: any): string;
|
||||
/**
|
||||
* Direct cache get
|
||||
*/
|
||||
get(key: string): any;
|
||||
/**
|
||||
* Direct cache set
|
||||
*/
|
||||
set(key: string, value: any): void;
|
||||
/**
|
||||
* Get the underlying SearchCache instance (for compatibility)
|
||||
*/
|
||||
getSearchCache(): SearchCache<GraphNoun> | null;
|
||||
/**
|
||||
* Get memory usage
|
||||
*/
|
||||
getMemoryUsage(): number;
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config cache augmentation
|
||||
*/
|
||||
export declare function createCacheAugmentation(config?: CacheConfig): CacheAugmentation;
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
/**
|
||||
* Cache Augmentation - Optional Search Result Caching
|
||||
*
|
||||
* Replaces the hardcoded SearchCache in Brainy with an optional augmentation.
|
||||
* This reduces core size and allows custom cache implementations.
|
||||
*
|
||||
* Zero-config: Automatically enabled with sensible defaults
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { SearchCache } from '../utils/searchCache.js';
|
||||
/**
|
||||
* CacheAugmentation - Makes search caching optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - Transparent search result caching
|
||||
* - Automatic invalidation on data changes
|
||||
* - Memory-aware cache management
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export class CacheAugmentation extends BaseAugmentation {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
this.name = 'cache';
|
||||
this.timing = 'around';
|
||||
this.metadata = 'none'; // Cache doesn't access metadata
|
||||
this.operations = ['search', 'find', 'similar', 'add', 'update', 'delete', 'clear', 'all'];
|
||||
this.priority = 50; // Mid-priority, runs after data operations
|
||||
// Augmentation metadata
|
||||
this.category = 'core';
|
||||
this.description = 'Transparent search result caching with automatic invalidation';
|
||||
this.searchCache = null;
|
||||
}
|
||||
getManifest() {
|
||||
return {
|
||||
id: 'cache',
|
||||
name: 'Cache',
|
||||
version: '2.0.0',
|
||||
description: 'Intelligent caching for search and query operations',
|
||||
longDescription: 'Provides transparent caching for search results with automatic invalidation on data changes. Significantly improves performance for repeated queries while maintaining data consistency.',
|
||||
category: 'performance',
|
||||
configSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Enable or disable caching'
|
||||
},
|
||||
maxSize: {
|
||||
type: 'number',
|
||||
default: 1000,
|
||||
minimum: 10,
|
||||
maximum: 100000,
|
||||
description: 'Maximum number of cached entries'
|
||||
},
|
||||
ttl: {
|
||||
type: 'number',
|
||||
default: 300000, // 5 minutes
|
||||
minimum: 1000, // 1 second
|
||||
maximum: 3600000, // 1 hour
|
||||
description: 'Time to live for cache entries in milliseconds'
|
||||
},
|
||||
invalidateOnWrite: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Automatically invalidate cache on data modifications'
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
},
|
||||
configDefaults: {
|
||||
enabled: true,
|
||||
maxSize: 1000,
|
||||
ttl: 300000,
|
||||
invalidateOnWrite: true
|
||||
},
|
||||
configExamples: [
|
||||
{
|
||||
name: 'High Performance',
|
||||
description: 'Large cache with longer TTL for read-heavy workloads',
|
||||
config: {
|
||||
enabled: true,
|
||||
maxSize: 10000,
|
||||
ttl: 1800000, // 30 minutes
|
||||
invalidateOnWrite: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Conservative',
|
||||
description: 'Small cache with short TTL for frequently changing data',
|
||||
config: {
|
||||
enabled: true,
|
||||
maxSize: 100,
|
||||
ttl: 60000, // 1 minute
|
||||
invalidateOnWrite: true
|
||||
}
|
||||
}
|
||||
],
|
||||
minBrainyVersion: '2.0.0',
|
||||
keywords: ['cache', 'performance', 'search', 'optimization'],
|
||||
documentation: 'https://docs.brainy.dev/augmentations/cache',
|
||||
status: 'stable',
|
||||
performance: {
|
||||
memoryUsage: 'medium',
|
||||
cpuUsage: 'low',
|
||||
networkUsage: 'none'
|
||||
},
|
||||
features: ['search-caching', 'auto-invalidation', 'ttl-support', 'memory-management'],
|
||||
enhancedOperations: ['search', 'searchText', 'findSimilar'],
|
||||
metrics: [
|
||||
{
|
||||
name: 'cache_hits',
|
||||
type: 'counter',
|
||||
description: 'Number of cache hits'
|
||||
},
|
||||
{
|
||||
name: 'cache_misses',
|
||||
type: 'counter',
|
||||
description: 'Number of cache misses'
|
||||
},
|
||||
{
|
||||
name: 'cache_size',
|
||||
type: 'gauge',
|
||||
description: 'Current cache size'
|
||||
}
|
||||
],
|
||||
ui: {
|
||||
icon: '⚡',
|
||||
color: '#FFC107'
|
||||
}
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('Cache augmentation disabled by configuration');
|
||||
return;
|
||||
}
|
||||
// Initialize search cache with config
|
||||
this.searchCache = new SearchCache({
|
||||
maxSize: this.config.maxSize,
|
||||
maxAge: this.config.ttl, // SearchCache uses maxAge, not ttl
|
||||
enabled: true
|
||||
});
|
||||
this.log(`Cache augmentation initialized (maxSize: ${this.config.maxSize}, ttl: ${this.config.ttl}ms)`);
|
||||
}
|
||||
async onShutdown() {
|
||||
if (this.searchCache) {
|
||||
this.searchCache.clear();
|
||||
this.searchCache = null;
|
||||
}
|
||||
this.log('Cache augmentation shut down');
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - wrap operations with caching logic
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// If cache is disabled, just pass through
|
||||
if (!this.searchCache || !this.config.enabled) {
|
||||
return next();
|
||||
}
|
||||
switch (operation) {
|
||||
case 'search':
|
||||
return this.handleSearch(params, next);
|
||||
case 'add':
|
||||
case 'update':
|
||||
case 'delete':
|
||||
// Invalidate cache on data changes
|
||||
if (this.config.invalidateOnWrite) {
|
||||
const result = await next();
|
||||
this.searchCache.invalidateOnDataChange(operation);
|
||||
this.log(`Cache invalidated due to ${operation} operation`);
|
||||
return result;
|
||||
}
|
||||
return next();
|
||||
case 'clear':
|
||||
// Clear cache when all data is cleared
|
||||
const result = await next();
|
||||
this.searchCache.clear();
|
||||
this.log('Cache cleared due to clear operation');
|
||||
return result;
|
||||
default:
|
||||
return next();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle search operation with caching
|
||||
*/
|
||||
async handleSearch(params, next) {
|
||||
if (!this.searchCache)
|
||||
return next();
|
||||
// Extract search parameters
|
||||
const { query, k, options = {} } = params;
|
||||
// Skip cache if explicitly disabled or has complex filters
|
||||
if (options.skipCache || options.metadata) {
|
||||
return next();
|
||||
}
|
||||
// Generate cache key
|
||||
const cacheKey = this.searchCache.getCacheKey(query, k, options);
|
||||
// Check cache
|
||||
const cachedResult = this.searchCache.get(cacheKey);
|
||||
if (cachedResult) {
|
||||
this.log('Cache hit for search query');
|
||||
// Update metrics if available
|
||||
if (this.context?.brain) {
|
||||
const metrics = this.context.brain.augmentations?.get('metrics');
|
||||
if (metrics) {
|
||||
metrics.recordCacheHit?.();
|
||||
}
|
||||
}
|
||||
return cachedResult;
|
||||
}
|
||||
// Execute search
|
||||
const result = await next();
|
||||
// Cache the result
|
||||
this.searchCache.set(cacheKey, result);
|
||||
this.log('Search result cached');
|
||||
// Update metrics if available
|
||||
if (this.context?.brain) {
|
||||
const metrics = this.context.brain.augmentations?.get('metrics');
|
||||
if (metrics) {
|
||||
metrics.recordCacheMiss?.();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats() {
|
||||
if (!this.searchCache) {
|
||||
return {
|
||||
enabled: false,
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
size: 0,
|
||||
memoryUsage: 0
|
||||
};
|
||||
}
|
||||
const stats = this.searchCache.getStats();
|
||||
return {
|
||||
...stats,
|
||||
memoryUsage: this.searchCache.getMemoryUsage()
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Clear the cache manually
|
||||
*/
|
||||
clear() {
|
||||
if (this.searchCache) {
|
||||
this.searchCache.clear();
|
||||
this.log('Cache manually cleared');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle runtime configuration changes
|
||||
*/
|
||||
async onConfigChange(newConfig, oldConfig) {
|
||||
if (this.searchCache && newConfig.enabled) {
|
||||
this.searchCache.updateConfig({
|
||||
maxSize: newConfig.maxSize,
|
||||
maxAge: newConfig.ttl, // SearchCache uses maxAge
|
||||
enabled: newConfig.enabled
|
||||
});
|
||||
this.log('Cache configuration updated');
|
||||
}
|
||||
else if (!newConfig.enabled && this.searchCache) {
|
||||
this.searchCache.clear();
|
||||
this.log('Cache disabled and cleared');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clean up expired entries
|
||||
*/
|
||||
cleanupExpiredEntries() {
|
||||
if (!this.searchCache)
|
||||
return 0;
|
||||
const cleaned = this.searchCache.cleanupExpiredEntries();
|
||||
if (cleaned > 0) {
|
||||
this.log(`Cleaned ${cleaned} expired cache entries`);
|
||||
}
|
||||
return cleaned;
|
||||
}
|
||||
/**
|
||||
* Invalidate cache when data changes
|
||||
*/
|
||||
invalidateOnDataChange(operation) {
|
||||
if (!this.searchCache)
|
||||
return;
|
||||
this.searchCache.invalidateOnDataChange(operation);
|
||||
this.log(`Cache invalidated due to ${operation} operation`);
|
||||
}
|
||||
/**
|
||||
* Get cache key for a query
|
||||
*/
|
||||
getCacheKey(query, options) {
|
||||
if (!this.searchCache)
|
||||
return '';
|
||||
return this.searchCache.getCacheKey(query, options);
|
||||
}
|
||||
/**
|
||||
* Direct cache get
|
||||
*/
|
||||
get(key) {
|
||||
if (!this.searchCache)
|
||||
return null;
|
||||
return this.searchCache.get(key);
|
||||
}
|
||||
/**
|
||||
* Direct cache set
|
||||
*/
|
||||
set(key, value) {
|
||||
if (!this.searchCache)
|
||||
return;
|
||||
this.searchCache.set(key, value);
|
||||
}
|
||||
/**
|
||||
* Get the underlying SearchCache instance (for compatibility)
|
||||
*/
|
||||
getSearchCache() {
|
||||
return this.searchCache;
|
||||
}
|
||||
/**
|
||||
* Get memory usage
|
||||
*/
|
||||
getMemoryUsage() {
|
||||
if (!this.searchCache)
|
||||
return 0;
|
||||
return this.searchCache.getMemoryUsage();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config cache augmentation
|
||||
*/
|
||||
export function createCacheAugmentation(config) {
|
||||
return new CacheAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=cacheAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
71
.recovery-workspace/dist-backup-20250910-141917/augmentations/conduitAugmentations.d.ts
vendored
Normal file
71
.recovery-workspace/dist-backup-20250910-141917/augmentations/conduitAugmentations.d.ts
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Conduit Augmentations - Data Synchronization Bridges
|
||||
*
|
||||
* These augmentations connect and synchronize data between multiple Brainy instances.
|
||||
* Now using the unified BrainyAugmentation interface.
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export interface WebSocketConnection {
|
||||
connectionId: string;
|
||||
url: string;
|
||||
readyState: number;
|
||||
socket?: any;
|
||||
}
|
||||
/**
|
||||
* Base class for conduit augmentations that sync between Brainy instances
|
||||
* Converted to use the unified BrainyAugmentation interface
|
||||
*/
|
||||
declare abstract class BaseConduitAugmentation extends BaseAugmentation {
|
||||
readonly timing: "after";
|
||||
readonly metadata: "readonly";
|
||||
readonly operations: ("addNoun" | "delete" | "addVerb")[];
|
||||
readonly priority = 20;
|
||||
protected connections: Map<string, any>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
abstract establishConnection(targetSystemId: string, config?: Record<string, unknown>): Promise<WebSocketConnection | null>;
|
||||
}
|
||||
/**
|
||||
* WebSocket Conduit Augmentation
|
||||
* Syncs data between Brainy instances using WebSockets
|
||||
*/
|
||||
export declare class WebSocketConduitAugmentation extends BaseConduitAugmentation {
|
||||
readonly name = "websocket-conduit";
|
||||
private webSocketConnections;
|
||||
private messageCallbacks;
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
private shouldSync;
|
||||
private syncOperation;
|
||||
establishConnection(url: string, config?: Record<string, unknown>): Promise<WebSocketConnection | null>;
|
||||
private handleMessage;
|
||||
private applySyncOperation;
|
||||
/**
|
||||
* Subscribe to messages from a specific connection
|
||||
*/
|
||||
onMessage(connectionId: string, callback: (data: any) => void): void;
|
||||
/**
|
||||
* Send a message to a specific connection
|
||||
*/
|
||||
sendMessage(connectionId: string, data: any): boolean;
|
||||
}
|
||||
export {};
|
||||
/**
|
||||
* Example usage:
|
||||
*
|
||||
* // Server instance
|
||||
* const serverBrain = new Brainy()
|
||||
* serverBrain.augmentations.register(new APIServerAugmentation())
|
||||
* await serverBrain.init()
|
||||
*
|
||||
* // Client instance
|
||||
* const clientBrain = new Brainy()
|
||||
* const conduit = new WebSocketConduitAugmentation()
|
||||
* clientBrain.augmentations.register(conduit)
|
||||
* await clientBrain.init()
|
||||
*
|
||||
* // Connect client to server
|
||||
* await conduit.establishConnection('ws://localhost:3000/ws')
|
||||
*
|
||||
* // Now operations sync automatically!
|
||||
* await clientBrain.addNoun('synced data', { source: 'client' })
|
||||
* // This will automatically sync to the server
|
||||
*/
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
/**
|
||||
* Conduit Augmentations - Data Synchronization Bridges
|
||||
*
|
||||
* These augmentations connect and synchronize data between multiple Brainy instances.
|
||||
* Now using the unified BrainyAugmentation interface.
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { v4 as uuidv4 } from '../universal/uuid.js';
|
||||
/**
|
||||
* Base class for conduit augmentations that sync between Brainy instances
|
||||
* Converted to use the unified BrainyAugmentation interface
|
||||
*/
|
||||
class BaseConduitAugmentation extends BaseAugmentation {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.timing = 'after'; // Conduits run after operations to sync
|
||||
this.metadata = 'readonly'; // Conduits read metadata to pass to external systems
|
||||
this.operations = ['addNoun', 'delete', 'addVerb'];
|
||||
this.priority = 20; // Medium-low priority
|
||||
this.connections = new Map();
|
||||
}
|
||||
async onShutdown() {
|
||||
// Close all connections
|
||||
for (const [connectionId, connection] of this.connections.entries()) {
|
||||
try {
|
||||
if (connection.close) {
|
||||
await connection.close();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to close connection ${connectionId}: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
this.connections.clear();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* WebSocket Conduit Augmentation
|
||||
* Syncs data between Brainy instances using WebSockets
|
||||
*/
|
||||
export class WebSocketConduitAugmentation extends BaseConduitAugmentation {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.name = 'websocket-conduit';
|
||||
this.webSocketConnections = new Map();
|
||||
this.messageCallbacks = new Map();
|
||||
}
|
||||
async execute(operation, params, next) {
|
||||
// Execute the operation first
|
||||
const result = await next();
|
||||
// Then sync to connected instances
|
||||
if (this.shouldSync(operation)) {
|
||||
await this.syncOperation(operation, params, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
shouldSync(operation) {
|
||||
return ['addNoun', 'deleteNoun', 'addVerb'].includes(operation);
|
||||
}
|
||||
async syncOperation(operation, params, result) {
|
||||
// Broadcast to all connected WebSocket instances
|
||||
for (const [id, connection] of this.webSocketConnections) {
|
||||
if (connection.socket && connection.readyState === 1) { // OPEN state
|
||||
try {
|
||||
const message = JSON.stringify({
|
||||
type: 'sync',
|
||||
operation,
|
||||
params,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
if (typeof connection.socket.send === 'function') {
|
||||
connection.socket.send(message);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to sync to ${id}: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async establishConnection(url, config) {
|
||||
try {
|
||||
const connectionId = uuidv4();
|
||||
const protocols = config?.protocols;
|
||||
// Create WebSocket based on environment
|
||||
let socket;
|
||||
if (typeof WebSocket !== 'undefined') {
|
||||
// Browser environment
|
||||
socket = new WebSocket(url, protocols);
|
||||
}
|
||||
else {
|
||||
// Node.js environment - dynamic import
|
||||
try {
|
||||
const ws = await import('ws');
|
||||
socket = new ws.WebSocket(url, protocols);
|
||||
}
|
||||
catch {
|
||||
this.log('WebSocket not available in this environment', 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Setup event handlers
|
||||
socket.onopen = () => {
|
||||
this.log(`Connected to ${url}`);
|
||||
};
|
||||
socket.onmessage = (event) => {
|
||||
this.handleMessage(connectionId, event.data);
|
||||
};
|
||||
socket.onerror = (error) => {
|
||||
this.log(`WebSocket error: ${error}`, 'error');
|
||||
};
|
||||
socket.onclose = () => {
|
||||
this.log(`Disconnected from ${url}`);
|
||||
this.webSocketConnections.delete(connectionId);
|
||||
};
|
||||
// Wait for connection to open
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Connection timeout'));
|
||||
}, 5000);
|
||||
socket.onopen = () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
};
|
||||
socket.onerror = (error) => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
};
|
||||
});
|
||||
const connection = {
|
||||
connectionId,
|
||||
url,
|
||||
readyState: socket.readyState,
|
||||
socket
|
||||
};
|
||||
this.webSocketConnections.set(connectionId, connection);
|
||||
this.connections.set(connectionId, connection);
|
||||
return connection;
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to establish connection to ${url}: ${error}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
handleMessage(connectionId, data) {
|
||||
try {
|
||||
const message = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
// Handle sync messages from remote instances
|
||||
if (message.type === 'sync') {
|
||||
// Apply the operation to our local instance
|
||||
this.applySyncOperation(message).catch(error => {
|
||||
this.log(`Failed to apply sync operation: ${error}`, 'error');
|
||||
});
|
||||
}
|
||||
// Notify any registered callbacks
|
||||
const callbacks = this.messageCallbacks.get(connectionId);
|
||||
if (callbacks) {
|
||||
callbacks.forEach(callback => callback(message));
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to handle message: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
async applySyncOperation(message) {
|
||||
// Apply the synced operation to our local Brainy instance
|
||||
const { operation, params } = message;
|
||||
try {
|
||||
switch (operation) {
|
||||
case 'addNoun':
|
||||
await this.context?.brain.addNoun(params.content, params.metadata);
|
||||
break;
|
||||
case 'deleteNoun':
|
||||
await this.context?.brain.deleteNoun(params.id);
|
||||
break;
|
||||
case 'addVerb':
|
||||
await this.context?.brain.addVerb(params.source, params.target, params.verb, params.metadata);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to apply ${operation}: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Subscribe to messages from a specific connection
|
||||
*/
|
||||
onMessage(connectionId, callback) {
|
||||
if (!this.messageCallbacks.has(connectionId)) {
|
||||
this.messageCallbacks.set(connectionId, new Set());
|
||||
}
|
||||
this.messageCallbacks.get(connectionId).add(callback);
|
||||
}
|
||||
/**
|
||||
* Send a message to a specific connection
|
||||
*/
|
||||
sendMessage(connectionId, data) {
|
||||
const connection = this.webSocketConnections.get(connectionId);
|
||||
if (connection?.socket && connection.readyState === 1) {
|
||||
try {
|
||||
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
connection.socket.send(message);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to send message: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Example usage:
|
||||
*
|
||||
* // Server instance
|
||||
* const serverBrain = new Brainy()
|
||||
* serverBrain.augmentations.register(new APIServerAugmentation())
|
||||
* await serverBrain.init()
|
||||
*
|
||||
* // Client instance
|
||||
* const clientBrain = new Brainy()
|
||||
* const conduit = new WebSocketConduitAugmentation()
|
||||
* clientBrain.augmentations.register(conduit)
|
||||
* await clientBrain.init()
|
||||
*
|
||||
* // Connect client to server
|
||||
* await conduit.establishConnection('ws://localhost:3000/ws')
|
||||
*
|
||||
* // Now operations sync automatically!
|
||||
* await clientBrain.addNoun('synced data', { source: 'client' })
|
||||
* // This will automatically sync to the server
|
||||
*/
|
||||
//# sourceMappingURL=conduitAugmentations.js.map
|
||||
File diff suppressed because one or more lines are too long
122
.recovery-workspace/dist-backup-20250910-141917/augmentations/configResolver.d.ts
vendored
Normal file
122
.recovery-workspace/dist-backup-20250910-141917/augmentations/configResolver.d.ts
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* Configuration Resolver for Augmentations
|
||||
*
|
||||
* Handles loading and resolving configuration from multiple sources:
|
||||
* - Environment variables
|
||||
* - Configuration files
|
||||
* - Runtime updates
|
||||
* - Default values from schema
|
||||
*/
|
||||
import { JSONSchema } from './manifest.js';
|
||||
/**
|
||||
* Configuration source priority (highest to lowest)
|
||||
*/
|
||||
export declare enum ConfigPriority {
|
||||
RUNTIME = 4,// Runtime updates (highest priority)
|
||||
CONSTRUCTOR = 3,// Constructor parameters
|
||||
ENVIRONMENT = 2,// Environment variables
|
||||
FILE = 1,// Configuration files
|
||||
DEFAULT = 0
|
||||
}
|
||||
/**
|
||||
* Configuration source information
|
||||
*/
|
||||
export interface ConfigSource {
|
||||
priority: ConfigPriority;
|
||||
source: string;
|
||||
config: any;
|
||||
}
|
||||
/**
|
||||
* Configuration resolution options
|
||||
*/
|
||||
export interface ConfigResolverOptions {
|
||||
augmentationId: string;
|
||||
schema?: JSONSchema;
|
||||
defaults?: Record<string, any>;
|
||||
configPaths?: string[];
|
||||
envPrefix?: string;
|
||||
allowUndefined?: boolean;
|
||||
}
|
||||
/**
|
||||
* Augmentation Configuration Resolver
|
||||
*/
|
||||
export declare class AugmentationConfigResolver {
|
||||
private options;
|
||||
private sources;
|
||||
private resolved;
|
||||
constructor(options: ConfigResolverOptions);
|
||||
/**
|
||||
* Resolve configuration from all sources
|
||||
* @param constructorConfig Optional constructor configuration
|
||||
* @returns Resolved configuration
|
||||
*/
|
||||
resolve(constructorConfig?: any): any;
|
||||
/**
|
||||
* Load default values from schema and defaults
|
||||
*/
|
||||
private loadDefaults;
|
||||
/**
|
||||
* Load configuration from files
|
||||
*/
|
||||
private loadFromFiles;
|
||||
/**
|
||||
* Parse configuration file based on extension
|
||||
*/
|
||||
private parseConfigFile;
|
||||
/**
|
||||
* Extract augmentation-specific configuration from a config object
|
||||
*/
|
||||
private extractAugmentationConfig;
|
||||
/**
|
||||
* Load configuration from environment variables
|
||||
*/
|
||||
private loadFromEnvironment;
|
||||
/**
|
||||
* Convert environment variable key to config key
|
||||
* ENABLED -> enabled
|
||||
* MAX_SIZE -> maxSize
|
||||
*/
|
||||
private envKeyToConfigKey;
|
||||
/**
|
||||
* Parse environment variable value
|
||||
*/
|
||||
private parseEnvValue;
|
||||
/**
|
||||
* Merge configurations by priority
|
||||
*/
|
||||
private mergeConfigurations;
|
||||
/**
|
||||
* Deep merge two objects
|
||||
*/
|
||||
private deepMerge;
|
||||
/**
|
||||
* Validate configuration against schema
|
||||
*/
|
||||
private validateConfiguration;
|
||||
/**
|
||||
* Validate a single property against its schema
|
||||
*/
|
||||
private validateProperty;
|
||||
/**
|
||||
* Get configuration sources for debugging
|
||||
*/
|
||||
getSources(): ConfigSource[];
|
||||
/**
|
||||
* Get resolved configuration
|
||||
*/
|
||||
getResolved(): any;
|
||||
/**
|
||||
* Update configuration at runtime
|
||||
*/
|
||||
updateRuntime(config: any): any;
|
||||
/**
|
||||
* Save configuration to file
|
||||
* @param filepath Path to save configuration
|
||||
* @param format Format to save as (json, etc.)
|
||||
*/
|
||||
saveToFile(filepath?: string, format?: 'json'): Promise<void>;
|
||||
/**
|
||||
* Get environment variable names for this augmentation
|
||||
*/
|
||||
getEnvironmentVariables(): Record<string, any>;
|
||||
}
|
||||
|
|
@ -0,0 +1,440 @@
|
|||
/**
|
||||
* Configuration Resolver for Augmentations
|
||||
*
|
||||
* Handles loading and resolving configuration from multiple sources:
|
||||
* - Environment variables
|
||||
* - Configuration files
|
||||
* - Runtime updates
|
||||
* - Default values from schema
|
||||
*/
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
/**
|
||||
* Configuration source priority (highest to lowest)
|
||||
*/
|
||||
export var ConfigPriority;
|
||||
(function (ConfigPriority) {
|
||||
ConfigPriority[ConfigPriority["RUNTIME"] = 4] = "RUNTIME";
|
||||
ConfigPriority[ConfigPriority["CONSTRUCTOR"] = 3] = "CONSTRUCTOR";
|
||||
ConfigPriority[ConfigPriority["ENVIRONMENT"] = 2] = "ENVIRONMENT";
|
||||
ConfigPriority[ConfigPriority["FILE"] = 1] = "FILE";
|
||||
ConfigPriority[ConfigPriority["DEFAULT"] = 0] = "DEFAULT"; // Schema defaults (lowest priority)
|
||||
})(ConfigPriority || (ConfigPriority = {}));
|
||||
/**
|
||||
* Augmentation Configuration Resolver
|
||||
*/
|
||||
export class AugmentationConfigResolver {
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
this.sources = [];
|
||||
this.resolved = {};
|
||||
this.options = {
|
||||
configPaths: [
|
||||
'.brainyrc',
|
||||
'.brainyrc.json',
|
||||
'brainy.config.json',
|
||||
join(homedir(), '.brainy', 'config.json'),
|
||||
join(homedir(), '.brainyrc')
|
||||
],
|
||||
envPrefix: `BRAINY_AUG_${options.augmentationId.toUpperCase()}_`,
|
||||
allowUndefined: true,
|
||||
...options
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Resolve configuration from all sources
|
||||
* @param constructorConfig Optional constructor configuration
|
||||
* @returns Resolved configuration
|
||||
*/
|
||||
resolve(constructorConfig) {
|
||||
this.sources = [];
|
||||
// Load from all sources in priority order
|
||||
this.loadDefaults();
|
||||
this.loadFromFiles();
|
||||
this.loadFromEnvironment();
|
||||
if (constructorConfig) {
|
||||
this.sources.push({
|
||||
priority: ConfigPriority.CONSTRUCTOR,
|
||||
source: 'constructor',
|
||||
config: constructorConfig
|
||||
});
|
||||
}
|
||||
// Merge configurations by priority
|
||||
this.resolved = this.mergeConfigurations();
|
||||
// Validate against schema if provided
|
||||
if (this.options.schema) {
|
||||
this.validateConfiguration(this.resolved);
|
||||
}
|
||||
return this.resolved;
|
||||
}
|
||||
/**
|
||||
* Load default values from schema and defaults
|
||||
*/
|
||||
loadDefaults() {
|
||||
let defaults = {};
|
||||
// Load from provided defaults
|
||||
if (this.options.defaults) {
|
||||
defaults = { ...defaults, ...this.options.defaults };
|
||||
}
|
||||
// Load from schema defaults
|
||||
if (this.options.schema?.properties) {
|
||||
for (const [key, prop] of Object.entries(this.options.schema.properties)) {
|
||||
if (prop.default !== undefined && defaults[key] === undefined) {
|
||||
defaults[key] = prop.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(defaults).length > 0) {
|
||||
this.sources.push({
|
||||
priority: ConfigPriority.DEFAULT,
|
||||
source: 'defaults',
|
||||
config: defaults
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load configuration from files
|
||||
*/
|
||||
loadFromFiles() {
|
||||
// Skip in browser environment
|
||||
if (typeof process === 'undefined' || typeof window !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
for (const configPath of this.options.configPaths || []) {
|
||||
try {
|
||||
if (existsSync(configPath)) {
|
||||
const content = readFileSync(configPath, 'utf8');
|
||||
const config = this.parseConfigFile(content, configPath);
|
||||
// Extract augmentation-specific configuration
|
||||
const augConfig = this.extractAugmentationConfig(config);
|
||||
if (augConfig && Object.keys(augConfig).length > 0) {
|
||||
this.sources.push({
|
||||
priority: ConfigPriority.FILE,
|
||||
source: `file:${configPath}`,
|
||||
config: augConfig
|
||||
});
|
||||
break; // Use first found config file
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Silently ignore file errors
|
||||
console.debug(`Failed to load config from ${configPath}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse configuration file based on extension
|
||||
*/
|
||||
parseConfigFile(content, filepath) {
|
||||
try {
|
||||
// Try JSON first
|
||||
return JSON.parse(content);
|
||||
}
|
||||
catch {
|
||||
// Try other formats in the future (YAML, TOML, etc.)
|
||||
throw new Error(`Unable to parse config file: ${filepath}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Extract augmentation-specific configuration from a config object
|
||||
*/
|
||||
extractAugmentationConfig(config) {
|
||||
const augId = this.options.augmentationId;
|
||||
// Check for augmentations section
|
||||
if (config.augmentations && config.augmentations[augId]) {
|
||||
return config.augmentations[augId];
|
||||
}
|
||||
// Check for direct augmentation config (prefixed keys)
|
||||
const prefix = `${augId}.`;
|
||||
const augConfig = {};
|
||||
for (const [key, value] of Object.entries(config)) {
|
||||
if (key.startsWith(prefix)) {
|
||||
const configKey = key.slice(prefix.length);
|
||||
augConfig[configKey] = value;
|
||||
}
|
||||
}
|
||||
return Object.keys(augConfig).length > 0 ? augConfig : null;
|
||||
}
|
||||
/**
|
||||
* Load configuration from environment variables
|
||||
*/
|
||||
loadFromEnvironment() {
|
||||
// Skip in browser environment
|
||||
if (typeof process === 'undefined' || !process.env) {
|
||||
return;
|
||||
}
|
||||
const prefix = this.options.envPrefix;
|
||||
const envConfig = {};
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (key.startsWith(prefix)) {
|
||||
const configKey = this.envKeyToConfigKey(key.slice(prefix.length));
|
||||
envConfig[configKey] = this.parseEnvValue(value);
|
||||
}
|
||||
}
|
||||
if (Object.keys(envConfig).length > 0) {
|
||||
this.sources.push({
|
||||
priority: ConfigPriority.ENVIRONMENT,
|
||||
source: 'environment',
|
||||
config: envConfig
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Convert environment variable key to config key
|
||||
* ENABLED -> enabled
|
||||
* MAX_SIZE -> maxSize
|
||||
*/
|
||||
envKeyToConfigKey(envKey) {
|
||||
return envKey
|
||||
.toLowerCase()
|
||||
.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
}
|
||||
/**
|
||||
* Parse environment variable value
|
||||
*/
|
||||
parseEnvValue(value) {
|
||||
// Handle empty strings
|
||||
if (value === '')
|
||||
return value;
|
||||
// Try to parse as JSON
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch {
|
||||
// Check for boolean strings
|
||||
if (value.toLowerCase() === 'true')
|
||||
return true;
|
||||
if (value.toLowerCase() === 'false')
|
||||
return false;
|
||||
// Check for number strings
|
||||
const num = Number(value);
|
||||
if (!isNaN(num) && value.trim() !== '')
|
||||
return num;
|
||||
// Return as string
|
||||
return value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Merge configurations by priority
|
||||
*/
|
||||
mergeConfigurations() {
|
||||
// Sort by priority (lowest to highest)
|
||||
const sorted = [...this.sources].sort((a, b) => a.priority - b.priority);
|
||||
// Merge configurations
|
||||
let merged = {};
|
||||
for (const source of sorted) {
|
||||
merged = this.deepMerge(merged, source.config);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
/**
|
||||
* Deep merge two objects
|
||||
*/
|
||||
deepMerge(target, source) {
|
||||
const output = { ...target };
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
||||
if (target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
|
||||
output[key] = this.deepMerge(target[key], source[key]);
|
||||
}
|
||||
else {
|
||||
output[key] = source[key];
|
||||
}
|
||||
}
|
||||
else {
|
||||
output[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
/**
|
||||
* Validate configuration against schema
|
||||
*/
|
||||
validateConfiguration(config) {
|
||||
if (!this.options.schema)
|
||||
return;
|
||||
const schema = this.options.schema;
|
||||
const errors = [];
|
||||
// Check required fields
|
||||
if (schema.required) {
|
||||
for (const field of schema.required) {
|
||||
if (config[field] === undefined) {
|
||||
errors.push(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Validate properties
|
||||
if (schema.properties) {
|
||||
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
||||
const value = config[key];
|
||||
if (value !== undefined) {
|
||||
this.validateProperty(key, value, propSchema, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for additional properties
|
||||
if (schema.additionalProperties === false) {
|
||||
const allowedKeys = Object.keys(schema.properties || {});
|
||||
for (const key of Object.keys(config)) {
|
||||
if (!allowedKeys.includes(key)) {
|
||||
errors.push(`Unknown configuration property: ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Configuration validation failed for ${this.options.augmentationId}:\n${errors.join('\n')}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Validate a single property against its schema
|
||||
*/
|
||||
validateProperty(key, value, schema, errors) {
|
||||
// Type validation
|
||||
if (schema.type) {
|
||||
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
||||
if (actualType !== schema.type) {
|
||||
errors.push(`${key}: expected ${schema.type}, got ${actualType}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Number validations
|
||||
if (schema.type === 'number') {
|
||||
if (schema.minimum !== undefined && value < schema.minimum) {
|
||||
errors.push(`${key}: value ${value} is less than minimum ${schema.minimum}`);
|
||||
}
|
||||
if (schema.maximum !== undefined && value > schema.maximum) {
|
||||
errors.push(`${key}: value ${value} is greater than maximum ${schema.maximum}`);
|
||||
}
|
||||
}
|
||||
// String validations
|
||||
if (schema.type === 'string') {
|
||||
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
||||
errors.push(`${key}: length ${value.length} is less than minimum ${schema.minLength}`);
|
||||
}
|
||||
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
||||
errors.push(`${key}: length ${value.length} is greater than maximum ${schema.maxLength}`);
|
||||
}
|
||||
if (schema.pattern) {
|
||||
const regex = new RegExp(schema.pattern);
|
||||
if (!regex.test(value)) {
|
||||
errors.push(`${key}: value does not match pattern ${schema.pattern}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Enum validation
|
||||
if (schema.enum && !schema.enum.includes(value)) {
|
||||
errors.push(`${key}: value ${value} is not one of allowed values: ${schema.enum.join(', ')}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get configuration sources for debugging
|
||||
*/
|
||||
getSources() {
|
||||
return [...this.sources];
|
||||
}
|
||||
/**
|
||||
* Get resolved configuration
|
||||
*/
|
||||
getResolved() {
|
||||
return { ...this.resolved };
|
||||
}
|
||||
/**
|
||||
* Update configuration at runtime
|
||||
*/
|
||||
updateRuntime(config) {
|
||||
// Add or update runtime source
|
||||
const runtimeIndex = this.sources.findIndex(s => s.priority === ConfigPriority.RUNTIME);
|
||||
if (runtimeIndex >= 0) {
|
||||
this.sources[runtimeIndex].config = {
|
||||
...this.sources[runtimeIndex].config,
|
||||
...config
|
||||
};
|
||||
}
|
||||
else {
|
||||
this.sources.push({
|
||||
priority: ConfigPriority.RUNTIME,
|
||||
source: 'runtime',
|
||||
config
|
||||
});
|
||||
}
|
||||
// Re-merge configurations
|
||||
this.resolved = this.mergeConfigurations();
|
||||
// Validate
|
||||
if (this.options.schema) {
|
||||
this.validateConfiguration(this.resolved);
|
||||
}
|
||||
return this.resolved;
|
||||
}
|
||||
/**
|
||||
* Save configuration to file
|
||||
* @param filepath Path to save configuration
|
||||
* @param format Format to save as (json, etc.)
|
||||
*/
|
||||
async saveToFile(filepath, format = 'json') {
|
||||
// Skip in browser environment
|
||||
if (typeof process === 'undefined' || typeof window !== 'undefined') {
|
||||
throw new Error('Cannot save configuration files in browser environment');
|
||||
}
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const configPath = filepath || this.options.configPaths?.[0] || '.brainyrc';
|
||||
const augId = this.options.augmentationId;
|
||||
// Load existing config if it exists
|
||||
let fullConfig = {};
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const content = fs.readFileSync(configPath, 'utf8');
|
||||
fullConfig = JSON.parse(content);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Start with empty config
|
||||
}
|
||||
// Ensure augmentations section exists
|
||||
if (!fullConfig.augmentations) {
|
||||
fullConfig.augmentations = {};
|
||||
}
|
||||
// Update augmentation config
|
||||
fullConfig.augmentations[augId] = this.resolved;
|
||||
// Save based on format
|
||||
let content;
|
||||
if (format === 'json') {
|
||||
content = JSON.stringify(fullConfig, null, 2);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unsupported format: ${format}`);
|
||||
}
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(configPath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
// Write file
|
||||
fs.writeFileSync(configPath, content, 'utf8');
|
||||
}
|
||||
/**
|
||||
* Get environment variable names for this augmentation
|
||||
*/
|
||||
getEnvironmentVariables() {
|
||||
const schema = this.options.schema;
|
||||
const prefix = this.options.envPrefix;
|
||||
const vars = {};
|
||||
if (schema?.properties) {
|
||||
for (const [key, prop] of Object.entries(schema.properties)) {
|
||||
const envKey = prefix + key.replace(/([A-Z])/g, '_$1').toUpperCase();
|
||||
vars[envKey] = {
|
||||
description: prop.description,
|
||||
type: prop.type,
|
||||
default: prop.default,
|
||||
currentValue: process.env?.[envKey]
|
||||
};
|
||||
}
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=configResolver.js.map
|
||||
File diff suppressed because one or more lines are too long
65
.recovery-workspace/dist-backup-20250910-141917/augmentations/connectionPoolAugmentation.d.ts
vendored
Normal file
65
.recovery-workspace/dist-backup-20250910-141917/augmentations/connectionPoolAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Connection Pool Augmentation
|
||||
*
|
||||
* Provides 10-20x throughput improvement for cloud storage (S3, R2, GCS)
|
||||
* Manages connection pooling, request queuing, and parallel processing
|
||||
* Critical for enterprise-scale operations with millions of entries
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
interface ConnectionPoolConfig {
|
||||
enabled?: boolean;
|
||||
maxConnections?: number;
|
||||
minConnections?: number;
|
||||
acquireTimeout?: number;
|
||||
idleTimeout?: number;
|
||||
maxQueueSize?: number;
|
||||
retryAttempts?: number;
|
||||
healthCheckInterval?: number;
|
||||
}
|
||||
export declare class ConnectionPoolAugmentation extends BaseAugmentation {
|
||||
name: string;
|
||||
timing: "around";
|
||||
metadata: "none";
|
||||
operations: ("storage")[];
|
||||
priority: number;
|
||||
protected config: Required<ConnectionPoolConfig>;
|
||||
private connections;
|
||||
private requestQueue;
|
||||
private healthCheckInterval?;
|
||||
private storageType;
|
||||
private stats;
|
||||
constructor(config?: ConnectionPoolConfig);
|
||||
protected onInitialize(): Promise<void>;
|
||||
shouldExecute(operation: string, params: any): boolean;
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
private detectStorageType;
|
||||
private isCloudStorage;
|
||||
private isStorageOperation;
|
||||
private getOperationPriority;
|
||||
private executeWithPool;
|
||||
private getAvailableConnection;
|
||||
private executeWithConnection;
|
||||
private queueRequest;
|
||||
private processQueue;
|
||||
private initializeConnectionPool;
|
||||
private createConnection;
|
||||
private createStorageConnection;
|
||||
private getOrCreateConnection;
|
||||
private startHealthChecks;
|
||||
private performHealthChecks;
|
||||
private startMetricsCollection;
|
||||
private updateLatencyMetrics;
|
||||
private updateThroughputMetrics;
|
||||
/**
|
||||
* Get connection pool statistics
|
||||
*/
|
||||
getStats(): typeof this.stats & {
|
||||
queueSize: number;
|
||||
activeConnections: number;
|
||||
totalConnections: number;
|
||||
poolUtilization: string;
|
||||
storageType: string;
|
||||
};
|
||||
protected onShutdown(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
/**
|
||||
* Connection Pool Augmentation
|
||||
*
|
||||
* Provides 10-20x throughput improvement for cloud storage (S3, R2, GCS)
|
||||
* Manages connection pooling, request queuing, and parallel processing
|
||||
* Critical for enterprise-scale operations with millions of entries
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export class ConnectionPoolAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.name = 'ConnectionPool';
|
||||
this.timing = 'around';
|
||||
this.metadata = 'none'; // Connection pooling doesn't access metadata
|
||||
this.operations = ['storage'];
|
||||
this.priority = 95; // Very high priority for storage operations
|
||||
this.connections = new Map();
|
||||
this.requestQueue = [];
|
||||
this.storageType = 'unknown';
|
||||
this.stats = {
|
||||
totalRequests: 0,
|
||||
queuedRequests: 0,
|
||||
activeConnections: 0,
|
||||
totalConnections: 0,
|
||||
averageLatency: 0,
|
||||
throughputPerSecond: 0
|
||||
};
|
||||
this.config = {
|
||||
enabled: config.enabled ?? true,
|
||||
maxConnections: config.maxConnections ?? 50,
|
||||
minConnections: config.minConnections ?? 5,
|
||||
acquireTimeout: config.acquireTimeout ?? 30000, // 30s
|
||||
idleTimeout: config.idleTimeout ?? 300000, // 5 minutes
|
||||
maxQueueSize: config.maxQueueSize ?? 10000,
|
||||
retryAttempts: config.retryAttempts ?? 3,
|
||||
healthCheckInterval: config.healthCheckInterval ?? 60000 // 1 minute
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('Connection pooling disabled');
|
||||
return;
|
||||
}
|
||||
// Detect storage type
|
||||
this.storageType = this.detectStorageType();
|
||||
if (this.isCloudStorage()) {
|
||||
await this.initializeConnectionPool();
|
||||
this.startHealthChecks();
|
||||
this.startMetricsCollection();
|
||||
this.log(`Connection pool initialized for ${this.storageType}: ${this.config.minConnections}-${this.config.maxConnections} connections`);
|
||||
}
|
||||
else {
|
||||
this.log(`Connection pooling skipped for ${this.storageType} (local storage)`);
|
||||
}
|
||||
}
|
||||
shouldExecute(operation, params) {
|
||||
return this.config.enabled &&
|
||||
this.isCloudStorage() &&
|
||||
this.isStorageOperation(operation);
|
||||
}
|
||||
async execute(operation, params, next) {
|
||||
if (!this.shouldExecute(operation, params)) {
|
||||
return next();
|
||||
}
|
||||
const startTime = Date.now();
|
||||
this.stats.totalRequests++;
|
||||
try {
|
||||
// High priority for critical operations
|
||||
const priority = this.getOperationPriority(operation);
|
||||
// Execute with pooled connection
|
||||
const result = await this.executeWithPool(operation, params, next, priority);
|
||||
// Update metrics
|
||||
const latency = Date.now() - startTime;
|
||||
this.updateLatencyMetrics(latency);
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Connection pool error for ${operation}: ${error}`, 'error');
|
||||
// Fallback to direct execution for reliability
|
||||
return next();
|
||||
}
|
||||
}
|
||||
detectStorageType() {
|
||||
const storage = this.context?.storage;
|
||||
if (!storage)
|
||||
return 'unknown';
|
||||
const className = storage.constructor.name.toLowerCase();
|
||||
if (className.includes('s3'))
|
||||
return 's3';
|
||||
if (className.includes('r2'))
|
||||
return 'r2';
|
||||
if (className.includes('gcs') || className.includes('google'))
|
||||
return 'gcs';
|
||||
if (className.includes('azure'))
|
||||
return 'azure';
|
||||
if (className.includes('filesystem'))
|
||||
return 'filesystem';
|
||||
if (className.includes('memory'))
|
||||
return 'memory';
|
||||
return 'unknown';
|
||||
}
|
||||
isCloudStorage() {
|
||||
return ['s3', 'r2', 'gcs', 'azure'].includes(this.storageType);
|
||||
}
|
||||
isStorageOperation(operation) {
|
||||
return operation.includes('save') ||
|
||||
operation.includes('get') ||
|
||||
operation.includes('delete') ||
|
||||
operation.includes('list') ||
|
||||
operation.includes('backup') ||
|
||||
operation.includes('restore');
|
||||
}
|
||||
getOperationPriority(operation) {
|
||||
// Critical operations get highest priority
|
||||
if (operation.includes('save') || operation.includes('update'))
|
||||
return 10;
|
||||
if (operation.includes('delete'))
|
||||
return 9;
|
||||
if (operation.includes('get'))
|
||||
return 7;
|
||||
if (operation.includes('list'))
|
||||
return 5;
|
||||
if (operation.includes('backup'))
|
||||
return 3;
|
||||
return 1;
|
||||
}
|
||||
async executeWithPool(operation, params, executor, priority) {
|
||||
// Check queue size
|
||||
if (this.requestQueue.length >= this.config.maxQueueSize) {
|
||||
throw new Error('Connection pool queue full - system overloaded');
|
||||
}
|
||||
// Try to get available connection immediately
|
||||
const connection = await this.getOrCreateConnection();
|
||||
if (connection && connection.isIdle) {
|
||||
return this.executeWithConnection(connection, operation, executor);
|
||||
}
|
||||
// Queue the request with the actual executor
|
||||
return this.queueRequest(operation, params, executor, priority);
|
||||
}
|
||||
getAvailableConnection() {
|
||||
// Find idle connection with best health score
|
||||
let bestConnection = null;
|
||||
let bestScore = -1;
|
||||
for (const connection of this.connections.values()) {
|
||||
if (connection.isIdle && connection.healthScore > bestScore) {
|
||||
bestConnection = connection;
|
||||
bestScore = connection.healthScore;
|
||||
}
|
||||
}
|
||||
return bestConnection;
|
||||
}
|
||||
async executeWithConnection(connection, operation, executor) {
|
||||
// Mark connection as active
|
||||
connection.isIdle = false;
|
||||
connection.activeRequests++;
|
||||
connection.lastUsed = Date.now();
|
||||
this.stats.activeConnections++;
|
||||
try {
|
||||
const result = await executor();
|
||||
// Update connection health on success
|
||||
connection.healthScore = Math.min(connection.healthScore + 1, 100);
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
// Decrease health on failure
|
||||
connection.healthScore = Math.max(connection.healthScore - 5, 0);
|
||||
throw error;
|
||||
}
|
||||
finally {
|
||||
// Release connection
|
||||
connection.isIdle = true;
|
||||
connection.activeRequests--;
|
||||
this.stats.activeConnections--;
|
||||
// Process next queued request
|
||||
this.processQueue();
|
||||
}
|
||||
}
|
||||
async queueRequest(operation, params, executor, priority) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = {
|
||||
id: `req_${Date.now()}_${Math.random()}`,
|
||||
operation,
|
||||
params,
|
||||
executor, // Store the actual executor function
|
||||
resolver: resolve,
|
||||
rejector: reject,
|
||||
timestamp: Date.now(),
|
||||
priority
|
||||
};
|
||||
// Insert by priority (higher priority first)
|
||||
const insertIndex = this.requestQueue.findIndex(r => r.priority < priority);
|
||||
if (insertIndex === -1) {
|
||||
this.requestQueue.push(request);
|
||||
}
|
||||
else {
|
||||
this.requestQueue.splice(insertIndex, 0, request);
|
||||
}
|
||||
this.stats.queuedRequests++;
|
||||
// Set timeout
|
||||
setTimeout(() => {
|
||||
const index = this.requestQueue.findIndex(r => r.id === request.id);
|
||||
if (index !== -1) {
|
||||
this.requestQueue.splice(index, 1);
|
||||
this.stats.queuedRequests--;
|
||||
reject(new Error(`Connection pool timeout: ${this.config.acquireTimeout}ms`));
|
||||
}
|
||||
}, this.config.acquireTimeout);
|
||||
});
|
||||
}
|
||||
processQueue() {
|
||||
if (this.requestQueue.length === 0)
|
||||
return;
|
||||
const connection = this.getAvailableConnection();
|
||||
if (!connection)
|
||||
return;
|
||||
const request = this.requestQueue.shift();
|
||||
this.stats.queuedRequests--;
|
||||
// Execute queued request with the REAL executor
|
||||
this.executeWithConnection(connection, request.operation, request.executor)
|
||||
.then(request.resolver)
|
||||
.catch(request.rejector);
|
||||
}
|
||||
async initializeConnectionPool() {
|
||||
// Create minimum connections
|
||||
for (let i = 0; i < this.config.minConnections; i++) {
|
||||
await this.createConnection();
|
||||
}
|
||||
}
|
||||
async createConnection() {
|
||||
const connectionId = `conn_${Date.now()}_${Math.random()}`;
|
||||
// Create actual connection based on storage type
|
||||
const actualConnection = await this.createStorageConnection();
|
||||
const connection = {
|
||||
id: connectionId,
|
||||
connection: actualConnection,
|
||||
isIdle: true,
|
||||
lastUsed: Date.now(),
|
||||
healthScore: 100,
|
||||
activeRequests: 0,
|
||||
requestCount: 0
|
||||
};
|
||||
this.connections.set(connectionId, connection);
|
||||
this.stats.totalConnections++;
|
||||
return connection;
|
||||
}
|
||||
async createStorageConnection() {
|
||||
// For cloud storage, reuse the existing storage instance
|
||||
// Connection pooling in this context means managing concurrent requests
|
||||
// not creating multiple storage instances (which would be wasteful)
|
||||
const storage = this.context?.storage;
|
||||
if (!storage) {
|
||||
throw new Error('Storage not available for connection pooling');
|
||||
}
|
||||
// Return a connection wrapper that tracks usage
|
||||
return {
|
||||
storage,
|
||||
created: Date.now(),
|
||||
requestCount: 0
|
||||
};
|
||||
}
|
||||
async getOrCreateConnection() {
|
||||
// Try to get an available connection
|
||||
let connection = this.getAvailableConnection();
|
||||
// If no connection available and under max, create new one
|
||||
if (!connection && this.connections.size < this.config.maxConnections) {
|
||||
connection = await this.createConnection();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
startHealthChecks() {
|
||||
this.healthCheckInterval = setInterval(() => {
|
||||
this.performHealthChecks();
|
||||
}, this.config.healthCheckInterval);
|
||||
}
|
||||
performHealthChecks() {
|
||||
const now = Date.now();
|
||||
const toRemove = [];
|
||||
for (const [id, connection] of this.connections) {
|
||||
// Remove idle connections that are too old
|
||||
if (connection.isIdle &&
|
||||
now - connection.lastUsed > this.config.idleTimeout &&
|
||||
this.connections.size > this.config.minConnections) {
|
||||
toRemove.push(id);
|
||||
}
|
||||
// Remove unhealthy connections
|
||||
if (connection.healthScore < 20) {
|
||||
toRemove.push(id);
|
||||
}
|
||||
}
|
||||
// Remove unhealthy/old connections
|
||||
for (const id of toRemove) {
|
||||
this.connections.delete(id);
|
||||
this.stats.totalConnections--;
|
||||
}
|
||||
// Ensure minimum connections
|
||||
while (this.connections.size < this.config.minConnections) {
|
||||
this.createConnection();
|
||||
}
|
||||
}
|
||||
startMetricsCollection() {
|
||||
setInterval(() => {
|
||||
this.updateThroughputMetrics();
|
||||
}, 1000); // Update every second
|
||||
}
|
||||
updateLatencyMetrics(latency) {
|
||||
// Simple moving average
|
||||
this.stats.averageLatency = (this.stats.averageLatency * 0.9) + (latency * 0.1);
|
||||
}
|
||||
updateThroughputMetrics() {
|
||||
// Reset counter for next second
|
||||
this.stats.throughputPerSecond = this.stats.totalRequests;
|
||||
// Reset total for next measurement (in practice, use sliding window)
|
||||
}
|
||||
/**
|
||||
* Get connection pool statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
...this.stats,
|
||||
queueSize: this.requestQueue.length,
|
||||
activeConnections: this.stats.activeConnections,
|
||||
totalConnections: this.connections.size,
|
||||
poolUtilization: `${Math.round((this.stats.activeConnections / this.connections.size) * 100)}%`,
|
||||
storageType: this.storageType
|
||||
};
|
||||
}
|
||||
async onShutdown() {
|
||||
if (this.healthCheckInterval) {
|
||||
clearInterval(this.healthCheckInterval);
|
||||
}
|
||||
// Close all connections
|
||||
this.connections.clear();
|
||||
// Reject all queued requests
|
||||
this.requestQueue.forEach(request => {
|
||||
request.rejector(new Error('Connection pool shutting down'));
|
||||
});
|
||||
this.requestQueue = [];
|
||||
const stats = this.getStats();
|
||||
this.log(`Connection pool shutdown: ${stats.totalRequests} requests processed, ${stats.poolUtilization} avg utilization`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=connectionPoolAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
59
.recovery-workspace/dist-backup-20250910-141917/augmentations/defaultAugmentations.d.ts
vendored
Normal file
59
.recovery-workspace/dist-backup-20250910-141917/augmentations/defaultAugmentations.d.ts
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Default Augmentations Registration
|
||||
*
|
||||
* Maintains zero-config philosophy by automatically registering
|
||||
* core augmentations that were previously hardcoded in Brainy.
|
||||
*
|
||||
* These augmentations are optional but enabled by default for
|
||||
* backward compatibility and optimal performance.
|
||||
*/
|
||||
import { Brainy } from '../brainy.js';
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { CacheAugmentation } from './cacheAugmentation.js';
|
||||
import { IndexAugmentation } from './indexAugmentation.js';
|
||||
import { MetricsAugmentation } from './metricsAugmentation.js';
|
||||
import { MonitoringAugmentation } from './monitoringAugmentation.js';
|
||||
import { UniversalDisplayAugmentation } from './universalDisplayAugmentation.js';
|
||||
/**
|
||||
* Create default augmentations for zero-config operation
|
||||
* Returns an array of augmentations to be registered
|
||||
*
|
||||
* @param config - Configuration options
|
||||
* @returns Array of augmentations to register
|
||||
*/
|
||||
export declare function createDefaultAugmentations(config?: {
|
||||
cache?: boolean | Record<string, any>;
|
||||
index?: boolean | Record<string, any>;
|
||||
metrics?: boolean | Record<string, any>;
|
||||
monitoring?: boolean | Record<string, any>;
|
||||
display?: boolean | Record<string, any>;
|
||||
}): BaseAugmentation[];
|
||||
/**
|
||||
* Get augmentation by name with type safety
|
||||
*/
|
||||
export declare function getAugmentation<T>(brain: Brainy, name: string): T | null;
|
||||
/**
|
||||
* Compatibility helpers for migrating from hardcoded features
|
||||
*/
|
||||
export declare const AugmentationHelpers: {
|
||||
/**
|
||||
* Get cache augmentation
|
||||
*/
|
||||
getCache(brain: Brainy): CacheAugmentation | null;
|
||||
/**
|
||||
* Get index augmentation
|
||||
*/
|
||||
getIndex(brain: Brainy): IndexAugmentation | null;
|
||||
/**
|
||||
* Get metrics augmentation
|
||||
*/
|
||||
getMetrics(brain: Brainy): MetricsAugmentation | null;
|
||||
/**
|
||||
* Get monitoring augmentation
|
||||
*/
|
||||
getMonitoring(brain: Brainy): MonitoringAugmentation | null;
|
||||
/**
|
||||
* Get display augmentation
|
||||
*/
|
||||
getDisplay(brain: Brainy): UniversalDisplayAugmentation | null;
|
||||
};
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* Default Augmentations Registration
|
||||
*
|
||||
* Maintains zero-config philosophy by automatically registering
|
||||
* core augmentations that were previously hardcoded in Brainy.
|
||||
*
|
||||
* These augmentations are optional but enabled by default for
|
||||
* backward compatibility and optimal performance.
|
||||
*/
|
||||
import { CacheAugmentation } from './cacheAugmentation.js';
|
||||
import { IndexAugmentation } from './indexAugmentation.js';
|
||||
import { MetricsAugmentation } from './metricsAugmentation.js';
|
||||
import { MonitoringAugmentation } from './monitoringAugmentation.js';
|
||||
import { UniversalDisplayAugmentation } from './universalDisplayAugmentation.js';
|
||||
/**
|
||||
* Create default augmentations for zero-config operation
|
||||
* Returns an array of augmentations to be registered
|
||||
*
|
||||
* @param config - Configuration options
|
||||
* @returns Array of augmentations to register
|
||||
*/
|
||||
export function createDefaultAugmentations(config = {}) {
|
||||
const augmentations = [];
|
||||
// Cache augmentation (was SearchCache)
|
||||
if (config.cache !== false) {
|
||||
const cacheConfig = typeof config.cache === 'object' ? config.cache : {};
|
||||
augmentations.push(new CacheAugmentation(cacheConfig));
|
||||
}
|
||||
// Index augmentation (was MetadataIndex)
|
||||
if (config.index !== false) {
|
||||
const indexConfig = typeof config.index === 'object' ? config.index : {};
|
||||
augmentations.push(new IndexAugmentation(indexConfig));
|
||||
}
|
||||
// Metrics augmentation (was StatisticsCollector)
|
||||
if (config.metrics !== false) {
|
||||
const metricsConfig = typeof config.metrics === 'object' ? config.metrics : {};
|
||||
augmentations.push(new MetricsAugmentation(metricsConfig));
|
||||
}
|
||||
// Display augmentation (AI-powered intelligent display fields)
|
||||
if (config.display !== false) {
|
||||
const displayConfig = typeof config.display === 'object' ? config.display : {};
|
||||
augmentations.push(new UniversalDisplayAugmentation(displayConfig));
|
||||
}
|
||||
// Monitoring augmentation (was HealthMonitor)
|
||||
// Only enable by default in distributed mode
|
||||
const isDistributed = process.env.BRAINY_MODE === 'distributed' ||
|
||||
process.env.BRAINY_DISTRIBUTED === 'true';
|
||||
if (config.monitoring !== false && (config.monitoring || isDistributed)) {
|
||||
const monitoringConfig = typeof config.monitoring === 'object' ? config.monitoring : {};
|
||||
augmentations.push(new MonitoringAugmentation(monitoringConfig));
|
||||
}
|
||||
return augmentations;
|
||||
}
|
||||
/**
|
||||
* Get augmentation by name with type safety
|
||||
*/
|
||||
export function getAugmentation(brain, name) {
|
||||
// Access augmentations through a public method or property
|
||||
const augmentations = brain.augmentations;
|
||||
if (!augmentations)
|
||||
return null;
|
||||
const aug = augmentations.get(name);
|
||||
return aug;
|
||||
}
|
||||
/**
|
||||
* Compatibility helpers for migrating from hardcoded features
|
||||
*/
|
||||
export const AugmentationHelpers = {
|
||||
/**
|
||||
* Get cache augmentation
|
||||
*/
|
||||
getCache(brain) {
|
||||
return getAugmentation(brain, 'cache');
|
||||
},
|
||||
/**
|
||||
* Get index augmentation
|
||||
*/
|
||||
getIndex(brain) {
|
||||
return getAugmentation(brain, 'index');
|
||||
},
|
||||
/**
|
||||
* Get metrics augmentation
|
||||
*/
|
||||
getMetrics(brain) {
|
||||
return getAugmentation(brain, 'metrics');
|
||||
},
|
||||
/**
|
||||
* Get monitoring augmentation
|
||||
*/
|
||||
getMonitoring(brain) {
|
||||
return getAugmentation(brain, 'monitoring');
|
||||
},
|
||||
/**
|
||||
* Get display augmentation
|
||||
*/
|
||||
getDisplay(brain) {
|
||||
return getAugmentation(brain, 'display');
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=defaultAugmentations.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"defaultAugmentations.js","sourceRoot":"","sources":["../../src/augmentations/defaultAugmentations.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACpE,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAA;AAEhF;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,SAMI,EAAE;IAEN,MAAM,aAAa,GAAuB,EAAE,CAAA;IAE5C,uCAAuC;IACvC,IAAI,MAAM,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3B,MAAM,WAAW,GAAG,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QACxE,aAAa,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAA;IACxD,CAAC;IAED,yCAAyC;IACzC,IAAI,MAAM,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3B,MAAM,WAAW,GAAG,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QACxE,aAAa,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAA;IACxD,CAAC;IAED,iDAAiD;IACjD,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,aAAa,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;QAC9E,aAAa,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED,+DAA+D;IAC/D,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,aAAa,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;QAC9E,aAAa,CAAC,IAAI,CAAC,IAAI,4BAA4B,CAAC,aAAa,CAAC,CAAC,CAAA;IACrE,CAAC;IAED,8CAA8C;IAC9C,6CAA6C;IAC7C,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,aAAa;QACzC,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,MAAM,CAAA;IAE/D,IAAI,MAAM,CAAC,UAAU,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,aAAa,CAAC,EAAE,CAAC;QACxE,MAAM,gBAAgB,GAAG,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;QACvF,aAAa,CAAC,IAAI,CAAC,IAAI,sBAAsB,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAClE,CAAC;IAED,OAAO,aAAa,CAAA;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAI,KAAa,EAAE,IAAY;IAC5D,2DAA2D;IAC3D,MAAM,aAAa,GAAI,KAAa,CAAC,aAAa,CAAA;IAClD,IAAI,CAAC,aAAa;QAAE,OAAO,IAAI,CAAA;IAC/B,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACnC,OAAO,GAAe,CAAA;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC;;OAEG;IACH,QAAQ,CAAC,KAAa;QACpB,OAAO,eAAe,CAAoB,KAAK,EAAE,OAAO,CAAC,CAAA;IAC3D,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,KAAa;QACpB,OAAO,eAAe,CAAoB,KAAK,EAAE,OAAO,CAAC,CAAA;IAC3D,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,KAAa;QACtB,OAAO,eAAe,CAAsB,KAAK,EAAE,SAAS,CAAC,CAAA;IAC/D,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,KAAa;QACzB,OAAO,eAAe,CAAyB,KAAK,EAAE,YAAY,CAAC,CAAA;IACrE,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,KAAa;QACtB,OAAO,eAAe,CAA+B,KAAK,EAAE,SAAS,CAAC,CAAA;IACxE,CAAC;CACF,CAAA"}
|
||||
152
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery.d.ts
vendored
Normal file
152
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery.d.ts
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* Augmentation Discovery API
|
||||
*
|
||||
* Provides discovery and configuration capabilities for augmentations
|
||||
* Enables tools like brain-cloud to dynamically discover, configure, and manage augmentations
|
||||
*/
|
||||
import { AugmentationRegistry } from './brainyAugmentation.js';
|
||||
import { AugmentationManifest, JSONSchema } from './manifest.js';
|
||||
/**
|
||||
* Augmentation listing with manifest and status
|
||||
*/
|
||||
export interface AugmentationListing {
|
||||
id: string;
|
||||
name: string;
|
||||
manifest: AugmentationManifest;
|
||||
status: {
|
||||
enabled: boolean;
|
||||
initialized: boolean;
|
||||
category: string;
|
||||
priority: number;
|
||||
};
|
||||
config?: {
|
||||
current: any;
|
||||
schema?: JSONSchema;
|
||||
sources?: any[];
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Configuration validation result
|
||||
*/
|
||||
export interface ConfigValidationResult {
|
||||
valid: boolean;
|
||||
errors?: string[];
|
||||
warnings?: string[];
|
||||
suggestions?: string[];
|
||||
}
|
||||
/**
|
||||
* Discovery API options
|
||||
*/
|
||||
export interface DiscoveryOptions {
|
||||
includeConfig?: boolean;
|
||||
includeSchema?: boolean;
|
||||
includeSources?: boolean;
|
||||
category?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
/**
|
||||
* Augmentation Discovery API
|
||||
*
|
||||
* Provides a unified interface for discovering and managing augmentations
|
||||
*/
|
||||
export declare class AugmentationDiscovery {
|
||||
private registry;
|
||||
constructor(registry: AugmentationRegistry);
|
||||
/**
|
||||
* Discover all registered augmentations with manifests
|
||||
* @param options Discovery options
|
||||
* @returns List of augmentation listings
|
||||
*/
|
||||
discover(options?: DiscoveryOptions): Promise<AugmentationListing[]>;
|
||||
/**
|
||||
* Get a specific augmentation's manifest
|
||||
* @param augId Augmentation ID
|
||||
* @returns Augmentation manifest or null if not found
|
||||
*/
|
||||
getManifest(augId: string): Promise<AugmentationManifest | null>;
|
||||
/**
|
||||
* Get configuration schema for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Configuration schema or null
|
||||
*/
|
||||
getConfigSchema(augId: string): Promise<JSONSchema | null>;
|
||||
/**
|
||||
* Get current configuration for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Current configuration or null
|
||||
*/
|
||||
getConfig(augId: string): Promise<any | null>;
|
||||
/**
|
||||
* Update configuration for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @param config New configuration
|
||||
* @returns Updated configuration or null on failure
|
||||
*/
|
||||
updateConfig(augId: string, config: any): Promise<any | null>;
|
||||
/**
|
||||
* Validate configuration against schema
|
||||
* @param augId Augmentation ID
|
||||
* @param config Configuration to validate
|
||||
* @returns Validation result
|
||||
*/
|
||||
validateConfig(augId: string, config: any): Promise<ConfigValidationResult>;
|
||||
/**
|
||||
* Validate a property value against its schema
|
||||
*/
|
||||
private validatePropertyValue;
|
||||
/**
|
||||
* Get environment variables for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Map of environment variable names to descriptions
|
||||
*/
|
||||
getEnvironmentVariables(augId: string): Promise<Record<string, any> | null>;
|
||||
/**
|
||||
* Get configuration examples for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Configuration examples or empty array
|
||||
*/
|
||||
getConfigExamples(augId: string): Promise<any[]>;
|
||||
/**
|
||||
* Check if an augmentation supports configuration
|
||||
* @param augId Augmentation ID
|
||||
* @returns True if augmentation supports configuration
|
||||
*/
|
||||
supportsConfiguration(augId: string): Promise<boolean>;
|
||||
/**
|
||||
* Get augmentations by category
|
||||
* @param category Category to filter by
|
||||
* @returns List of augmentations in the category
|
||||
*/
|
||||
getByCategory(category: string): Promise<AugmentationListing[]>;
|
||||
/**
|
||||
* Get enabled augmentations
|
||||
* @returns List of enabled augmentations
|
||||
*/
|
||||
getEnabled(): Promise<AugmentationListing[]>;
|
||||
/**
|
||||
* Search augmentations by keyword
|
||||
* @param query Search query
|
||||
* @returns Matching augmentations
|
||||
*/
|
||||
search(query: string): Promise<AugmentationListing[]>;
|
||||
/**
|
||||
* Export configuration for all augmentations
|
||||
* @returns Map of augmentation IDs to configurations
|
||||
*/
|
||||
exportConfigurations(): Promise<Record<string, any>>;
|
||||
/**
|
||||
* Import configurations for multiple augmentations
|
||||
* @param configs Map of augmentation IDs to configurations
|
||||
* @returns Results of import operation
|
||||
*/
|
||||
importConfigurations(configs: Record<string, any>): Promise<Record<string, {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}>>;
|
||||
/**
|
||||
* Generate configuration documentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Markdown documentation
|
||||
*/
|
||||
generateConfigDocs(augId: string): Promise<string | null>;
|
||||
}
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
/**
|
||||
* Augmentation Discovery API
|
||||
*
|
||||
* Provides discovery and configuration capabilities for augmentations
|
||||
* Enables tools like brain-cloud to dynamically discover, configure, and manage augmentations
|
||||
*/
|
||||
/**
|
||||
* Augmentation Discovery API
|
||||
*
|
||||
* Provides a unified interface for discovering and managing augmentations
|
||||
*/
|
||||
export class AugmentationDiscovery {
|
||||
constructor(registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
/**
|
||||
* Discover all registered augmentations with manifests
|
||||
* @param options Discovery options
|
||||
* @returns List of augmentation listings
|
||||
*/
|
||||
async discover(options = {}) {
|
||||
const augmentations = this.registry.getAll();
|
||||
const listings = [];
|
||||
for (const aug of augmentations) {
|
||||
// Check if augmentation has manifest support
|
||||
const hasManifest = 'getManifest' in aug && typeof aug.getManifest === 'function';
|
||||
if (!hasManifest) {
|
||||
// Skip augmentations without manifest support (legacy)
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// Check if augmentation has manifest method
|
||||
if (!('getManifest' in aug) || typeof aug.getManifest !== 'function') {
|
||||
continue;
|
||||
}
|
||||
const getManifestFn = aug.getManifest;
|
||||
const manifest = getManifestFn();
|
||||
// Apply filters
|
||||
if (options.category && manifest.category !== options.category) {
|
||||
continue;
|
||||
}
|
||||
if (options.enabled !== undefined) {
|
||||
const isEnabled = aug.enabled !== false;
|
||||
if (isEnabled !== options.enabled) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Build listing
|
||||
const listing = {
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
manifest,
|
||||
status: {
|
||||
enabled: aug.enabled !== false,
|
||||
initialized: aug.isInitialized || false,
|
||||
category: aug.category || manifest.category,
|
||||
priority: aug.priority
|
||||
}
|
||||
};
|
||||
// Include configuration if requested
|
||||
if (options.includeConfig && 'getConfig' in aug) {
|
||||
const getConfigFn = aug.getConfig;
|
||||
listing.config = {
|
||||
current: getConfigFn()
|
||||
};
|
||||
if (options.includeSchema) {
|
||||
listing.config.schema = manifest.configSchema;
|
||||
}
|
||||
}
|
||||
listings.push(listing);
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Failed to get manifest for augmentation ${aug.name}:`, error);
|
||||
}
|
||||
}
|
||||
// Sort by priority (highest first) then by name
|
||||
listings.sort((a, b) => {
|
||||
const priorityDiff = b.status.priority - a.status.priority;
|
||||
if (priorityDiff !== 0)
|
||||
return priorityDiff;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
return listings;
|
||||
}
|
||||
/**
|
||||
* Get a specific augmentation's manifest
|
||||
* @param augId Augmentation ID
|
||||
* @returns Augmentation manifest or null if not found
|
||||
*/
|
||||
async getManifest(augId) {
|
||||
const aug = this.registry.get(augId);
|
||||
if (!aug || !('getManifest' in aug)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const getManifestFn = aug.getManifest;
|
||||
return getManifestFn();
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to get manifest for ${augId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get configuration schema for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Configuration schema or null
|
||||
*/
|
||||
async getConfigSchema(augId) {
|
||||
const manifest = await this.getManifest(augId);
|
||||
return manifest?.configSchema || null;
|
||||
}
|
||||
/**
|
||||
* Get current configuration for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Current configuration or null
|
||||
*/
|
||||
async getConfig(augId) {
|
||||
const aug = this.registry.get(augId);
|
||||
if (!aug || !('getConfig' in aug)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const getConfigFn = aug.getConfig;
|
||||
return getConfigFn();
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to get config for ${augId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update configuration for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @param config New configuration
|
||||
* @returns Updated configuration or null on failure
|
||||
*/
|
||||
async updateConfig(augId, config) {
|
||||
const aug = this.registry.get(augId);
|
||||
if (!aug || !('updateConfig' in aug) || !('getConfig' in aug)) {
|
||||
throw new Error(`Augmentation ${augId} does not support configuration updates`);
|
||||
}
|
||||
try {
|
||||
const updateConfigFn = aug.updateConfig;
|
||||
await updateConfigFn(config);
|
||||
const getConfigFn = aug.getConfig;
|
||||
return getConfigFn();
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to update config for ${augId}: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Validate configuration against schema
|
||||
* @param augId Augmentation ID
|
||||
* @param config Configuration to validate
|
||||
* @returns Validation result
|
||||
*/
|
||||
async validateConfig(augId, config) {
|
||||
const schema = await this.getConfigSchema(augId);
|
||||
if (!schema) {
|
||||
return {
|
||||
valid: true,
|
||||
warnings: ['No schema available for validation']
|
||||
};
|
||||
}
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const suggestions = [];
|
||||
// Check required fields
|
||||
if (schema.required) {
|
||||
for (const field of schema.required) {
|
||||
if (config[field] === undefined) {
|
||||
errors.push(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Validate properties
|
||||
if (schema.properties) {
|
||||
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
||||
const value = config[key];
|
||||
if (value === undefined) {
|
||||
// Check if there's a default
|
||||
if (propSchema.default !== undefined) {
|
||||
suggestions.push(`Field '${key}' not provided, will use default: ${JSON.stringify(propSchema.default)}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Type validation
|
||||
if (propSchema.type) {
|
||||
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
||||
if (actualType !== propSchema.type) {
|
||||
errors.push(`${key}: expected ${propSchema.type}, got ${actualType}`);
|
||||
}
|
||||
}
|
||||
// Additional validations for specific types
|
||||
this.validatePropertyValue(key, value, propSchema, errors, warnings);
|
||||
}
|
||||
}
|
||||
// Check for unknown properties
|
||||
if (schema.additionalProperties === false && schema.properties) {
|
||||
const allowedKeys = Object.keys(schema.properties);
|
||||
for (const key of Object.keys(config)) {
|
||||
if (!allowedKeys.includes(key)) {
|
||||
warnings.push(`Unknown property: ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors: errors.length > 0 ? errors : undefined,
|
||||
warnings: warnings.length > 0 ? warnings : undefined,
|
||||
suggestions: suggestions.length > 0 ? suggestions : undefined
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Validate a property value against its schema
|
||||
*/
|
||||
validatePropertyValue(key, value, schema, errors, warnings) {
|
||||
// Number validations
|
||||
if (schema.type === 'number') {
|
||||
if (schema.minimum !== undefined && value < schema.minimum) {
|
||||
errors.push(`${key}: value ${value} is less than minimum ${schema.minimum}`);
|
||||
}
|
||||
if (schema.maximum !== undefined && value > schema.maximum) {
|
||||
errors.push(`${key}: value ${value} is greater than maximum ${schema.maximum}`);
|
||||
}
|
||||
}
|
||||
// String validations
|
||||
if (schema.type === 'string') {
|
||||
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
||||
errors.push(`${key}: length ${value.length} is less than minimum ${schema.minLength}`);
|
||||
}
|
||||
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
||||
errors.push(`${key}: length ${value.length} is greater than maximum ${schema.maxLength}`);
|
||||
}
|
||||
if (schema.pattern) {
|
||||
const regex = new RegExp(schema.pattern);
|
||||
if (!regex.test(value)) {
|
||||
errors.push(`${key}: value does not match pattern ${schema.pattern}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Enum validation
|
||||
if (schema.enum && !schema.enum.includes(value)) {
|
||||
errors.push(`${key}: value '${value}' is not one of allowed values: ${schema.enum.join(', ')}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get environment variables for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Map of environment variable names to descriptions
|
||||
*/
|
||||
async getEnvironmentVariables(augId) {
|
||||
const manifest = await this.getManifest(augId);
|
||||
if (!manifest?.configSchema?.properties) {
|
||||
return null;
|
||||
}
|
||||
const prefix = `BRAINY_AUG_${augId.toUpperCase()}_`;
|
||||
const vars = {};
|
||||
for (const [key, prop] of Object.entries(manifest.configSchema.properties)) {
|
||||
const envKey = prefix + key.replace(/([A-Z])/g, '_$1').toUpperCase();
|
||||
vars[envKey] = {
|
||||
configKey: key,
|
||||
description: prop.description,
|
||||
type: prop.type,
|
||||
default: prop.default,
|
||||
required: manifest.configSchema.required?.includes(key),
|
||||
currentValue: typeof process !== 'undefined' ? process.env?.[envKey] : undefined
|
||||
};
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
/**
|
||||
* Get configuration examples for an augmentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Configuration examples or empty array
|
||||
*/
|
||||
async getConfigExamples(augId) {
|
||||
const manifest = await this.getManifest(augId);
|
||||
return manifest?.configExamples || [];
|
||||
}
|
||||
/**
|
||||
* Check if an augmentation supports configuration
|
||||
* @param augId Augmentation ID
|
||||
* @returns True if augmentation supports configuration
|
||||
*/
|
||||
async supportsConfiguration(augId) {
|
||||
const aug = this.registry.get(augId);
|
||||
return !!(aug && 'getConfig' in aug && 'updateConfig' in aug);
|
||||
}
|
||||
/**
|
||||
* Get augmentations by category
|
||||
* @param category Category to filter by
|
||||
* @returns List of augmentations in the category
|
||||
*/
|
||||
async getByCategory(category) {
|
||||
return this.discover({ category });
|
||||
}
|
||||
/**
|
||||
* Get enabled augmentations
|
||||
* @returns List of enabled augmentations
|
||||
*/
|
||||
async getEnabled() {
|
||||
return this.discover({ enabled: true });
|
||||
}
|
||||
/**
|
||||
* Search augmentations by keyword
|
||||
* @param query Search query
|
||||
* @returns Matching augmentations
|
||||
*/
|
||||
async search(query) {
|
||||
const all = await this.discover();
|
||||
const queryLower = query.toLowerCase();
|
||||
return all.filter(listing => {
|
||||
const manifest = listing.manifest;
|
||||
// Search in various fields
|
||||
const searchFields = [
|
||||
manifest.name,
|
||||
manifest.description,
|
||||
manifest.longDescription,
|
||||
...(manifest.keywords || []),
|
||||
manifest.category
|
||||
].filter(Boolean).map(s => s.toLowerCase());
|
||||
return searchFields.some(field => field.includes(queryLower));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Export configuration for all augmentations
|
||||
* @returns Map of augmentation IDs to configurations
|
||||
*/
|
||||
async exportConfigurations() {
|
||||
const configs = {};
|
||||
const listings = await this.discover({ includeConfig: true });
|
||||
for (const listing of listings) {
|
||||
if (listing.config?.current) {
|
||||
configs[listing.id] = listing.config.current;
|
||||
}
|
||||
}
|
||||
return configs;
|
||||
}
|
||||
/**
|
||||
* Import configurations for multiple augmentations
|
||||
* @param configs Map of augmentation IDs to configurations
|
||||
* @returns Results of import operation
|
||||
*/
|
||||
async importConfigurations(configs) {
|
||||
const results = {};
|
||||
for (const [augId, config] of Object.entries(configs)) {
|
||||
try {
|
||||
// Validate before applying
|
||||
const validation = await this.validateConfig(augId, config);
|
||||
if (!validation.valid) {
|
||||
results[augId] = {
|
||||
success: false,
|
||||
error: `Validation failed: ${validation.errors?.join(', ')}`
|
||||
};
|
||||
continue;
|
||||
}
|
||||
// Apply configuration
|
||||
await this.updateConfig(augId, config);
|
||||
results[augId] = { success: true };
|
||||
}
|
||||
catch (error) {
|
||||
results[augId] = {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
/**
|
||||
* Generate configuration documentation
|
||||
* @param augId Augmentation ID
|
||||
* @returns Markdown documentation
|
||||
*/
|
||||
async generateConfigDocs(augId) {
|
||||
const manifest = await this.getManifest(augId);
|
||||
if (!manifest)
|
||||
return null;
|
||||
const schema = manifest.configSchema;
|
||||
const examples = manifest.configExamples || [];
|
||||
const envVars = await this.getEnvironmentVariables(augId);
|
||||
let docs = `# ${manifest.name} Configuration\n\n`;
|
||||
docs += `${manifest.description}\n\n`;
|
||||
if (manifest.longDescription) {
|
||||
docs += `## Overview\n\n${manifest.longDescription}\n\n`;
|
||||
}
|
||||
// Configuration options
|
||||
if (schema?.properties) {
|
||||
docs += `## Configuration Options\n\n`;
|
||||
for (const [key, prop] of Object.entries(schema.properties)) {
|
||||
const required = schema.required?.includes(key) ? ' *(required)*' : '';
|
||||
docs += `### \`${key}\`${required}\n\n`;
|
||||
if (prop.description) {
|
||||
docs += `${prop.description}\n\n`;
|
||||
}
|
||||
docs += `- **Type**: ${prop.type}\n`;
|
||||
if (prop.default !== undefined) {
|
||||
docs += `- **Default**: \`${JSON.stringify(prop.default)}\`\n`;
|
||||
}
|
||||
if (prop.minimum !== undefined) {
|
||||
docs += `- **Minimum**: ${prop.minimum}\n`;
|
||||
}
|
||||
if (prop.maximum !== undefined) {
|
||||
docs += `- **Maximum**: ${prop.maximum}\n`;
|
||||
}
|
||||
if (prop.enum) {
|
||||
docs += `- **Allowed values**: ${prop.enum.map(v => `\`${v}\``).join(', ')}\n`;
|
||||
}
|
||||
docs += '\n';
|
||||
}
|
||||
}
|
||||
// Environment variables
|
||||
if (envVars && Object.keys(envVars).length > 0) {
|
||||
docs += `## Environment Variables\n\n`;
|
||||
docs += `| Variable | Config Key | Type | Required | Default |\n`;
|
||||
docs += `|----------|------------|------|----------|----------|\n`;
|
||||
for (const [envKey, info] of Object.entries(envVars)) {
|
||||
docs += `| \`${envKey}\` | ${info.configKey} | ${info.type} | ${info.required ? 'Yes' : 'No'} | ${info.default !== undefined ? `\`${info.default}\`` : '-'} |\n`;
|
||||
}
|
||||
docs += '\n';
|
||||
}
|
||||
// Examples
|
||||
if (examples.length > 0) {
|
||||
docs += `## Examples\n\n`;
|
||||
for (const example of examples) {
|
||||
docs += `### ${example.name}\n\n`;
|
||||
if (example.description) {
|
||||
docs += `${example.description}\n\n`;
|
||||
}
|
||||
docs += '```json\n';
|
||||
docs += JSON.stringify(example.config, null, 2);
|
||||
docs += '\n```\n\n';
|
||||
}
|
||||
}
|
||||
return docs;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=discovery.js.map
|
||||
File diff suppressed because one or more lines are too long
142
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery/catalogDiscovery.d.ts
vendored
Normal file
142
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery/catalogDiscovery.d.ts
vendored
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* Brain-Cloud Catalog Discovery
|
||||
*
|
||||
* Discovers augmentations available in the brain-cloud catalog
|
||||
* Handles free, premium, and community augmentations
|
||||
*/
|
||||
import { AugmentationManifest } from '../manifest.js';
|
||||
export interface CatalogAugmentation {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
longDescription?: string;
|
||||
category: string;
|
||||
status: 'available' | 'coming_soon' | 'deprecated';
|
||||
tier: 'free' | 'premium' | 'enterprise';
|
||||
price?: {
|
||||
monthly?: number;
|
||||
yearly?: number;
|
||||
oneTime?: number;
|
||||
};
|
||||
manifest?: AugmentationManifest;
|
||||
source: 'catalog';
|
||||
cdnUrl?: string;
|
||||
npmPackage?: string;
|
||||
githubRepo?: string;
|
||||
author?: {
|
||||
name: string;
|
||||
url?: string;
|
||||
};
|
||||
metrics?: {
|
||||
installations: number;
|
||||
rating: number;
|
||||
reviews: number;
|
||||
};
|
||||
requirements?: {
|
||||
minBrainyVersion?: string;
|
||||
maxBrainyVersion?: string;
|
||||
dependencies?: string[];
|
||||
};
|
||||
}
|
||||
export interface CatalogOptions {
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
cache?: boolean;
|
||||
cacheTimeout?: number;
|
||||
}
|
||||
export interface CatalogFilters {
|
||||
category?: string;
|
||||
tier?: 'free' | 'premium' | 'enterprise';
|
||||
status?: 'available' | 'coming_soon' | 'deprecated';
|
||||
search?: string;
|
||||
installed?: boolean;
|
||||
minRating?: number;
|
||||
}
|
||||
/**
|
||||
* Brain-Cloud Catalog Discovery
|
||||
*/
|
||||
export declare class CatalogDiscovery {
|
||||
private apiUrl;
|
||||
private apiKey?;
|
||||
private cache;
|
||||
private cacheTimeout;
|
||||
constructor(options?: CatalogOptions);
|
||||
/**
|
||||
* Discover augmentations from catalog
|
||||
*/
|
||||
discover(filters?: CatalogFilters): Promise<CatalogAugmentation[]>;
|
||||
/**
|
||||
* Get specific augmentation details
|
||||
*/
|
||||
getAugmentation(id: string): Promise<CatalogAugmentation | null>;
|
||||
/**
|
||||
* Get augmentation manifest
|
||||
*/
|
||||
getManifest(id: string): Promise<AugmentationManifest | null>;
|
||||
/**
|
||||
* Get CDN URL for dynamic loading
|
||||
*/
|
||||
getCDNUrl(id: string): Promise<string | null>;
|
||||
/**
|
||||
* Check if user has access to augmentation
|
||||
*/
|
||||
checkAccess(id: string): Promise<{
|
||||
hasAccess: boolean;
|
||||
requiresPurchase?: boolean;
|
||||
requiredTier?: string;
|
||||
}>;
|
||||
/**
|
||||
* Purchase/activate augmentation
|
||||
*/
|
||||
purchase(id: string, licenseKey?: string): Promise<{
|
||||
success: boolean;
|
||||
cdnUrl?: string;
|
||||
npmPackage?: string;
|
||||
licenseKey?: string;
|
||||
}>;
|
||||
/**
|
||||
* Get user's purchased augmentations
|
||||
*/
|
||||
getPurchased(): Promise<CatalogAugmentation[]>;
|
||||
/**
|
||||
* Get categories
|
||||
*/
|
||||
getCategories(): Promise<Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon?: string;
|
||||
}>>;
|
||||
/**
|
||||
* Search augmentations
|
||||
*/
|
||||
search(query: string): Promise<CatalogAugmentation[]>;
|
||||
/**
|
||||
* Get trending augmentations
|
||||
*/
|
||||
getTrending(limit?: number): Promise<CatalogAugmentation[]>;
|
||||
/**
|
||||
* Get recommended augmentations
|
||||
*/
|
||||
getRecommended(): Promise<CatalogAugmentation[]>;
|
||||
/**
|
||||
* Transform catalog data
|
||||
*/
|
||||
private transformCatalogData;
|
||||
/**
|
||||
* Transform single augmentation
|
||||
*/
|
||||
private transformAugmentation;
|
||||
/**
|
||||
* Get request headers
|
||||
*/
|
||||
private getHeaders;
|
||||
/**
|
||||
* Clear cache
|
||||
*/
|
||||
clearCache(): void;
|
||||
/**
|
||||
* Set API key
|
||||
*/
|
||||
setApiKey(apiKey: string): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
/**
|
||||
* Brain-Cloud Catalog Discovery
|
||||
*
|
||||
* Discovers augmentations available in the brain-cloud catalog
|
||||
* Handles free, premium, and community augmentations
|
||||
*/
|
||||
/**
|
||||
* Brain-Cloud Catalog Discovery
|
||||
*/
|
||||
export class CatalogDiscovery {
|
||||
constructor(options = {}) {
|
||||
this.cache = new Map();
|
||||
this.apiUrl = options.apiUrl || 'https://api.soulcraft.com/brain-cloud';
|
||||
this.apiKey = options.apiKey;
|
||||
this.cacheTimeout = options.cacheTimeout || 5 * 60 * 1000; // 5 minutes
|
||||
}
|
||||
/**
|
||||
* Discover augmentations from catalog
|
||||
*/
|
||||
async discover(filters = {}) {
|
||||
const cacheKey = JSON.stringify(filters);
|
||||
// Check cache
|
||||
if (this.cache.has(cacheKey)) {
|
||||
const cached = this.cache.get(cacheKey);
|
||||
if (Date.now() - cached.timestamp < this.cacheTimeout) {
|
||||
return cached.data;
|
||||
}
|
||||
}
|
||||
// Build query parameters
|
||||
const params = new URLSearchParams();
|
||||
if (filters.category)
|
||||
params.append('category', filters.category);
|
||||
if (filters.tier)
|
||||
params.append('tier', filters.tier);
|
||||
if (filters.status)
|
||||
params.append('status', filters.status);
|
||||
if (filters.search)
|
||||
params.append('q', filters.search);
|
||||
if (filters.minRating)
|
||||
params.append('minRating', filters.minRating.toString());
|
||||
// Fetch from API
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/discover?${params}`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch catalog: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const augmentations = this.transformCatalogData(data);
|
||||
// Cache result
|
||||
this.cache.set(cacheKey, {
|
||||
data: augmentations,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
return augmentations;
|
||||
}
|
||||
/**
|
||||
* Get specific augmentation details
|
||||
*/
|
||||
async getAugmentation(id) {
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/${id}`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (response.status === 404)
|
||||
return null;
|
||||
throw new Error(`Failed to fetch augmentation: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return this.transformAugmentation(data);
|
||||
}
|
||||
/**
|
||||
* Get augmentation manifest
|
||||
*/
|
||||
async getManifest(id) {
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/${id}/manifest`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (response.status === 404)
|
||||
return null;
|
||||
throw new Error(`Failed to fetch manifest: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
/**
|
||||
* Get CDN URL for dynamic loading
|
||||
*/
|
||||
async getCDNUrl(id) {
|
||||
const aug = await this.getAugmentation(id);
|
||||
return aug?.cdnUrl || null;
|
||||
}
|
||||
/**
|
||||
* Check if user has access to augmentation
|
||||
*/
|
||||
async checkAccess(id) {
|
||||
if (!this.apiKey) {
|
||||
// No API key, only free augmentations
|
||||
const aug = await this.getAugmentation(id);
|
||||
return {
|
||||
hasAccess: aug?.tier === 'free',
|
||||
requiresPurchase: aug?.tier !== 'free',
|
||||
requiredTier: aug?.tier
|
||||
};
|
||||
}
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/${id}/access`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to check access: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
/**
|
||||
* Purchase/activate augmentation
|
||||
*/
|
||||
async purchase(id, licenseKey) {
|
||||
const body = licenseKey ? { licenseKey } : {};
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/${id}/purchase`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...this.getHeaders(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to purchase: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
/**
|
||||
* Get user's purchased augmentations
|
||||
*/
|
||||
async getPurchased() {
|
||||
if (!this.apiKey) {
|
||||
return [];
|
||||
}
|
||||
const response = await fetch(`${this.apiUrl}/user/augmentations`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch purchased: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return this.transformCatalogData(data);
|
||||
}
|
||||
/**
|
||||
* Get categories
|
||||
*/
|
||||
async getCategories() {
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/categories`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch categories: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
/**
|
||||
* Search augmentations
|
||||
*/
|
||||
async search(query) {
|
||||
return this.discover({ search: query });
|
||||
}
|
||||
/**
|
||||
* Get trending augmentations
|
||||
*/
|
||||
async getTrending(limit = 10) {
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/trending?limit=${limit}`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch trending: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return this.transformCatalogData(data);
|
||||
}
|
||||
/**
|
||||
* Get recommended augmentations
|
||||
*/
|
||||
async getRecommended() {
|
||||
if (!this.apiKey) {
|
||||
// Return popular free augmentations
|
||||
return this.discover({ tier: 'free', minRating: 4 });
|
||||
}
|
||||
const response = await fetch(`${this.apiUrl}/augmentations/recommended`, {
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch recommended: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return this.transformCatalogData(data);
|
||||
}
|
||||
/**
|
||||
* Transform catalog data
|
||||
*/
|
||||
transformCatalogData(data) {
|
||||
return data.map(item => this.transformAugmentation(item));
|
||||
}
|
||||
/**
|
||||
* Transform single augmentation
|
||||
*/
|
||||
transformAugmentation(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
longDescription: item.longDescription,
|
||||
category: item.category,
|
||||
status: item.status || 'available',
|
||||
tier: item.tier || 'free',
|
||||
price: item.price,
|
||||
manifest: item.manifest,
|
||||
source: 'catalog',
|
||||
cdnUrl: item.cdnUrl || `https://cdn.soulcraft.com/augmentations/${item.id}@latest`,
|
||||
npmPackage: item.npmPackage,
|
||||
githubRepo: item.githubRepo,
|
||||
author: item.author,
|
||||
metrics: item.metrics,
|
||||
requirements: item.requirements
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get request headers
|
||||
*/
|
||||
getHeaders() {
|
||||
const headers = {};
|
||||
if (this.apiKey) {
|
||||
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
/**
|
||||
* Clear cache
|
||||
*/
|
||||
clearCache() {
|
||||
this.cache.clear();
|
||||
}
|
||||
/**
|
||||
* Set API key
|
||||
*/
|
||||
setApiKey(apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
this.clearCache(); // Clear cache when API key changes
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=catalogDiscovery.js.map
|
||||
File diff suppressed because one or more lines are too long
84
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery/localDiscovery.d.ts
vendored
Normal file
84
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery/localDiscovery.d.ts
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* Local Augmentation Discovery
|
||||
*
|
||||
* Discovers augmentations installed locally in node_modules
|
||||
* and built-in augmentations that ship with Brainy
|
||||
*/
|
||||
import { AugmentationManifest } from '../manifest.js';
|
||||
export interface LocalAugmentation {
|
||||
id: string;
|
||||
name: string;
|
||||
source: 'builtin' | 'npm' | 'local';
|
||||
path: string;
|
||||
manifest?: AugmentationManifest;
|
||||
package?: {
|
||||
name: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Discovers augmentations installed locally
|
||||
*/
|
||||
export declare class LocalAugmentationDiscovery {
|
||||
private options;
|
||||
private builtInAugmentations;
|
||||
private installedAugmentations;
|
||||
constructor(options?: {
|
||||
brainyPath?: string;
|
||||
projectPath?: string;
|
||||
scanNodeModules?: boolean;
|
||||
});
|
||||
/**
|
||||
* Register built-in augmentations that ship with Brainy
|
||||
*/
|
||||
private registerBuiltIn;
|
||||
/**
|
||||
* Find Brainy installation path
|
||||
*/
|
||||
private findBrainyPath;
|
||||
/**
|
||||
* Discover all augmentations
|
||||
*/
|
||||
discoverAll(): Promise<LocalAugmentation[]>;
|
||||
/**
|
||||
* Scan node_modules for installed augmentations
|
||||
*/
|
||||
private scanNodeModules;
|
||||
/**
|
||||
* Scan local project for augmentations
|
||||
*/
|
||||
private scanLocalProject;
|
||||
/**
|
||||
* Load augmentation from package
|
||||
*/
|
||||
private loadPackageAugmentation;
|
||||
/**
|
||||
* Load package.json
|
||||
*/
|
||||
private loadPackageJson;
|
||||
/**
|
||||
* Convert name to human-readable format
|
||||
*/
|
||||
private humanizeName;
|
||||
/**
|
||||
* Get built-in augmentations
|
||||
*/
|
||||
getBuiltIn(): LocalAugmentation[];
|
||||
/**
|
||||
* Get installed augmentations
|
||||
*/
|
||||
getInstalled(): LocalAugmentation[];
|
||||
/**
|
||||
* Check if augmentation is installed
|
||||
*/
|
||||
isInstalled(id: string): boolean;
|
||||
/**
|
||||
* Get import path for augmentation
|
||||
*/
|
||||
getImportPath(id: string): string | null;
|
||||
/**
|
||||
* Load augmentation module dynamically
|
||||
*/
|
||||
loadAugmentation(id: string): Promise<any>;
|
||||
}
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* Local Augmentation Discovery
|
||||
*
|
||||
* Discovers augmentations installed locally in node_modules
|
||||
* and built-in augmentations that ship with Brainy
|
||||
*/
|
||||
import { existsSync, readdirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
/**
|
||||
* Discovers augmentations installed locally
|
||||
*/
|
||||
export class LocalAugmentationDiscovery {
|
||||
constructor(options = {}) {
|
||||
this.options = options;
|
||||
this.builtInAugmentations = new Map();
|
||||
this.installedAugmentations = new Map();
|
||||
this.options = {
|
||||
brainyPath: this.options.brainyPath || this.findBrainyPath(),
|
||||
projectPath: this.options.projectPath || process.cwd(),
|
||||
scanNodeModules: this.options.scanNodeModules ?? true
|
||||
};
|
||||
// Register built-in augmentations
|
||||
this.registerBuiltIn();
|
||||
}
|
||||
/**
|
||||
* Register built-in augmentations that ship with Brainy
|
||||
*/
|
||||
registerBuiltIn() {
|
||||
const builtIn = [
|
||||
{ id: 'wal', name: 'Write-Ahead Log', path: 'walAugmentation' },
|
||||
{ id: 'cache', name: 'Cache', path: 'cacheAugmentation' },
|
||||
{ id: 'batch', name: 'Batch Processing', path: 'batchProcessingAugmentation' },
|
||||
{ id: 'entity-registry', name: 'Entity Registry', path: 'entityRegistryAugmentation' },
|
||||
{ id: 'index', name: 'Index', path: 'indexAugmentation' },
|
||||
{ id: 'metrics', name: 'Metrics', path: 'metricsAugmentation' },
|
||||
{ id: 'monitoring', name: 'Monitoring', path: 'monitoringAugmentation' },
|
||||
{ id: 'connection-pool', name: 'Connection Pool', path: 'connectionPoolAugmentation' },
|
||||
{ id: 'request-deduplicator', name: 'Request Deduplicator', path: 'requestDeduplicatorAugmentation' },
|
||||
{ id: 'api-server', name: 'API Server', path: 'apiServerAugmentation' },
|
||||
{ id: 'neural-import', name: 'Neural Import', path: 'neuralImport' },
|
||||
{ id: 'intelligent-verb-scoring', name: 'Intelligent Verb Scoring', path: 'intelligentVerbScoringAugmentation' },
|
||||
{ id: 'universal-display', name: 'Universal Display', path: 'universalDisplayAugmentation' },
|
||||
// Storage augmentations
|
||||
{ id: 'memory-storage', name: 'Memory Storage', path: 'storageAugmentations', export: 'MemoryStorageAugmentation' },
|
||||
{ id: 'filesystem-storage', name: 'FileSystem Storage', path: 'storageAugmentations', export: 'FileSystemStorageAugmentation' },
|
||||
{ id: 'opfs-storage', name: 'OPFS Storage', path: 'storageAugmentations', export: 'OPFSStorageAugmentation' },
|
||||
{ id: 's3-storage', name: 'S3 Storage', path: 'storageAugmentations', export: 'S3StorageAugmentation' },
|
||||
];
|
||||
for (const aug of builtIn) {
|
||||
this.builtInAugmentations.set(aug.id, {
|
||||
id: aug.id,
|
||||
name: aug.name,
|
||||
source: 'builtin',
|
||||
path: `@soulcraft/brainy/augmentations/${aug.path}`,
|
||||
package: {
|
||||
name: '@soulcraft/brainy',
|
||||
version: 'builtin',
|
||||
description: `Built-in ${aug.name} augmentation`
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Find Brainy installation path
|
||||
*/
|
||||
findBrainyPath() {
|
||||
// Try to find brainy in node_modules
|
||||
const possiblePaths = [
|
||||
join(process.cwd(), 'node_modules', '@soulcraft', 'brainy'),
|
||||
join(process.cwd(), 'node_modules', 'brainy'),
|
||||
join(process.cwd(), '..', 'node_modules', '@soulcraft', 'brainy'),
|
||||
];
|
||||
for (const path of possiblePaths) {
|
||||
if (existsSync(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
// Fallback to current directory
|
||||
return process.cwd();
|
||||
}
|
||||
/**
|
||||
* Discover all augmentations
|
||||
*/
|
||||
async discoverAll() {
|
||||
const augmentations = [];
|
||||
// Add built-in augmentations
|
||||
augmentations.push(...this.builtInAugmentations.values());
|
||||
// Scan node_modules if enabled
|
||||
if (this.options.scanNodeModules) {
|
||||
const installed = await this.scanNodeModules();
|
||||
augmentations.push(...installed);
|
||||
}
|
||||
// Scan local project
|
||||
const local = await this.scanLocalProject();
|
||||
augmentations.push(...local);
|
||||
return augmentations;
|
||||
}
|
||||
/**
|
||||
* Scan node_modules for installed augmentations
|
||||
*/
|
||||
async scanNodeModules() {
|
||||
const augmentations = [];
|
||||
const nodeModulesPath = join(this.options.projectPath, 'node_modules');
|
||||
if (!existsSync(nodeModulesPath)) {
|
||||
return augmentations;
|
||||
}
|
||||
// Scan @brainy/* packages
|
||||
const brainyPath = join(nodeModulesPath, '@brainy');
|
||||
if (existsSync(brainyPath)) {
|
||||
const packages = readdirSync(brainyPath);
|
||||
for (const pkg of packages) {
|
||||
const augmentation = await this.loadPackageAugmentation(join(brainyPath, pkg));
|
||||
if (augmentation) {
|
||||
augmentations.push(augmentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Scan packages with brainy-augmentation keyword
|
||||
const packages = readdirSync(nodeModulesPath);
|
||||
for (const pkg of packages) {
|
||||
if (pkg.startsWith('@') || pkg.startsWith('.'))
|
||||
continue;
|
||||
const pkgPath = join(nodeModulesPath, pkg);
|
||||
const packageJson = this.loadPackageJson(pkgPath);
|
||||
if (packageJson?.keywords?.includes('brainy-augmentation')) {
|
||||
const augmentation = await this.loadPackageAugmentation(pkgPath);
|
||||
if (augmentation) {
|
||||
augmentations.push(augmentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
return augmentations;
|
||||
}
|
||||
/**
|
||||
* Scan local project for augmentations
|
||||
*/
|
||||
async scanLocalProject() {
|
||||
const augmentations = [];
|
||||
// Check for augmentations directory
|
||||
const augPath = join(this.options.projectPath, 'augmentations');
|
||||
if (existsSync(augPath)) {
|
||||
const files = readdirSync(augPath);
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||
const name = file.replace(/\.(ts|js)$/, '');
|
||||
augmentations.push({
|
||||
id: name,
|
||||
name: this.humanizeName(name),
|
||||
source: 'local',
|
||||
path: join(augPath, file)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return augmentations;
|
||||
}
|
||||
/**
|
||||
* Load augmentation from package
|
||||
*/
|
||||
async loadPackageAugmentation(pkgPath) {
|
||||
const packageJson = this.loadPackageJson(pkgPath);
|
||||
if (!packageJson)
|
||||
return null;
|
||||
// Check if it's a brainy augmentation
|
||||
const isBrainyAug = packageJson.keywords?.includes('brainy-augmentation') ||
|
||||
packageJson.brainy?.type === 'augmentation';
|
||||
if (!isBrainyAug)
|
||||
return null;
|
||||
const manifest = packageJson.brainy?.manifest || null;
|
||||
return {
|
||||
id: packageJson.brainy?.id || packageJson.name.replace('@brainy/', ''),
|
||||
name: packageJson.brainy?.name || packageJson.name,
|
||||
source: 'npm',
|
||||
path: pkgPath,
|
||||
manifest,
|
||||
package: {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
description: packageJson.description
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Load package.json
|
||||
*/
|
||||
loadPackageJson(pkgPath) {
|
||||
const packageJsonPath = join(pkgPath, 'package.json');
|
||||
if (!existsSync(packageJsonPath))
|
||||
return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Convert name to human-readable format
|
||||
*/
|
||||
humanizeName(name) {
|
||||
return name
|
||||
.replace(/[-_]/g, ' ')
|
||||
.replace(/augmentation/gi, '')
|
||||
.replace(/\b\w/g, l => l.toUpperCase())
|
||||
.trim();
|
||||
}
|
||||
/**
|
||||
* Get built-in augmentations
|
||||
*/
|
||||
getBuiltIn() {
|
||||
return Array.from(this.builtInAugmentations.values());
|
||||
}
|
||||
/**
|
||||
* Get installed augmentations
|
||||
*/
|
||||
getInstalled() {
|
||||
return Array.from(this.installedAugmentations.values());
|
||||
}
|
||||
/**
|
||||
* Check if augmentation is installed
|
||||
*/
|
||||
isInstalled(id) {
|
||||
return this.builtInAugmentations.has(id) ||
|
||||
this.installedAugmentations.has(id);
|
||||
}
|
||||
/**
|
||||
* Get import path for augmentation
|
||||
*/
|
||||
getImportPath(id) {
|
||||
const aug = this.builtInAugmentations.get(id) ||
|
||||
this.installedAugmentations.get(id);
|
||||
return aug?.path || null;
|
||||
}
|
||||
/**
|
||||
* Load augmentation module dynamically
|
||||
*/
|
||||
async loadAugmentation(id) {
|
||||
const path = this.getImportPath(id);
|
||||
if (!path) {
|
||||
throw new Error(`Augmentation ${id} not found`);
|
||||
}
|
||||
// Dynamic import
|
||||
const module = await import(path);
|
||||
return module.default || module;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=localDiscovery.js.map
|
||||
File diff suppressed because one or more lines are too long
97
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery/runtimeLoader.d.ts
vendored
Normal file
97
.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery/runtimeLoader.d.ts
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Runtime Augmentation Loader
|
||||
*
|
||||
* Dynamically loads and registers augmentations at runtime
|
||||
* Supports CDN loading for browser environments and npm imports for Node.js
|
||||
*/
|
||||
import { BrainyAugmentation, AugmentationRegistry } from '../brainyAugmentation.js';
|
||||
import { AugmentationManifest } from '../manifest.js';
|
||||
export interface LoaderOptions {
|
||||
cdnUrl?: string;
|
||||
allowUnsafe?: boolean;
|
||||
sandbox?: boolean;
|
||||
timeout?: number;
|
||||
cache?: boolean;
|
||||
}
|
||||
export interface LoadedAugmentation {
|
||||
id: string;
|
||||
instance: BrainyAugmentation;
|
||||
manifest: AugmentationManifest;
|
||||
source: 'cdn' | 'npm' | 'local';
|
||||
loadTime: number;
|
||||
}
|
||||
/**
|
||||
* Runtime Augmentation Loader
|
||||
*
|
||||
* Enables dynamic loading of augmentations from various sources
|
||||
*/
|
||||
export declare class RuntimeAugmentationLoader {
|
||||
private options;
|
||||
private loaded;
|
||||
private cdnCache;
|
||||
private registry?;
|
||||
constructor(options?: LoaderOptions);
|
||||
/**
|
||||
* Set the augmentation registry
|
||||
*/
|
||||
setRegistry(registry: AugmentationRegistry): void;
|
||||
/**
|
||||
* Load augmentation from CDN (browser)
|
||||
*/
|
||||
loadFromCDN(id: string, version?: string, config?: any): Promise<LoadedAugmentation>;
|
||||
/**
|
||||
* Load augmentation from NPM (Node.js)
|
||||
*/
|
||||
loadFromNPM(packageName: string, config?: any): Promise<LoadedAugmentation>;
|
||||
/**
|
||||
* Load augmentation from local file
|
||||
*/
|
||||
loadFromFile(path: string, config?: any): Promise<LoadedAugmentation>;
|
||||
/**
|
||||
* Load multiple augmentations
|
||||
*/
|
||||
loadBatch(augmentations: Array<{
|
||||
source: 'cdn' | 'npm' | 'local';
|
||||
id: string;
|
||||
version?: string;
|
||||
path?: string;
|
||||
config?: any;
|
||||
}>): Promise<LoadedAugmentation[]>;
|
||||
/**
|
||||
* Unload augmentation
|
||||
*/
|
||||
unload(id: string): boolean;
|
||||
/**
|
||||
* Get loaded augmentations
|
||||
*/
|
||||
getLoaded(): LoadedAugmentation[];
|
||||
/**
|
||||
* Check if augmentation is loaded
|
||||
*/
|
||||
isLoaded(id: string): boolean;
|
||||
/**
|
||||
* Get loaded augmentation
|
||||
*/
|
||||
getAugmentation(id: string): BrainyAugmentation | null;
|
||||
/**
|
||||
* Load CDN module (browser-specific)
|
||||
*/
|
||||
private loadCDNModule;
|
||||
/**
|
||||
* Validate augmentation instance
|
||||
*/
|
||||
private isValidAugmentation;
|
||||
/**
|
||||
* Clear all caches
|
||||
*/
|
||||
clearCache(): void;
|
||||
/**
|
||||
* Get load statistics
|
||||
*/
|
||||
getStats(): {
|
||||
loaded: number;
|
||||
totalLoadTime: number;
|
||||
averageLoadTime: number;
|
||||
sources: Record<string, number>;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
/**
|
||||
* Runtime Augmentation Loader
|
||||
*
|
||||
* Dynamically loads and registers augmentations at runtime
|
||||
* Supports CDN loading for browser environments and npm imports for Node.js
|
||||
*/
|
||||
/**
|
||||
* Runtime Augmentation Loader
|
||||
*
|
||||
* Enables dynamic loading of augmentations from various sources
|
||||
*/
|
||||
export class RuntimeAugmentationLoader {
|
||||
constructor(options = {}) {
|
||||
this.options = options;
|
||||
this.loaded = new Map();
|
||||
this.cdnCache = new Map();
|
||||
this.options = {
|
||||
cdnUrl: options.cdnUrl || 'https://cdn.soulcraft.com/augmentations',
|
||||
allowUnsafe: options.allowUnsafe || false,
|
||||
sandbox: options.sandbox || true,
|
||||
timeout: options.timeout || 30000,
|
||||
cache: options.cache ?? true
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Set the augmentation registry
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
/**
|
||||
* Load augmentation from CDN (browser)
|
||||
*/
|
||||
async loadFromCDN(id, version = 'latest', config) {
|
||||
// Check if already loaded
|
||||
if (this.loaded.has(id)) {
|
||||
return this.loaded.get(id);
|
||||
}
|
||||
const url = `${this.options.cdnUrl}/${id}@${version}/index.js`;
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
// Load module from CDN
|
||||
const module = await this.loadCDNModule(url);
|
||||
// Extract augmentation class
|
||||
const AugmentationClass = module.default || module[Object.keys(module)[0]];
|
||||
if (!AugmentationClass) {
|
||||
throw new Error(`No augmentation class found in module ${id}`);
|
||||
}
|
||||
// Create instance
|
||||
const instance = new AugmentationClass(config);
|
||||
// Validate it's a proper augmentation
|
||||
if (!this.isValidAugmentation(instance)) {
|
||||
throw new Error(`Invalid augmentation: ${id}`);
|
||||
}
|
||||
// Get manifest
|
||||
const manifest = instance.getManifest ? instance.getManifest() : {
|
||||
id,
|
||||
name: id,
|
||||
version: version,
|
||||
description: `Dynamically loaded ${id}`,
|
||||
category: 'external'
|
||||
};
|
||||
const loaded = {
|
||||
id,
|
||||
instance,
|
||||
manifest,
|
||||
source: 'cdn',
|
||||
loadTime: Date.now() - startTime
|
||||
};
|
||||
// Cache
|
||||
this.loaded.set(id, loaded);
|
||||
// Auto-register if registry is set
|
||||
if (this.registry) {
|
||||
this.registry.register(instance);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to load augmentation ${id} from CDN: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load augmentation from NPM (Node.js)
|
||||
*/
|
||||
async loadFromNPM(packageName, config) {
|
||||
// Check if already loaded
|
||||
const id = packageName.replace('@', '').replace('/', '-');
|
||||
if (this.loaded.has(id)) {
|
||||
return this.loaded.get(id);
|
||||
}
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
// Dynamic import
|
||||
const module = await import(packageName);
|
||||
// Extract augmentation class
|
||||
const AugmentationClass = module.default || module[Object.keys(module)[0]];
|
||||
if (!AugmentationClass) {
|
||||
throw new Error(`No augmentation class found in package ${packageName}`);
|
||||
}
|
||||
// Create instance
|
||||
const instance = new AugmentationClass(config);
|
||||
// Validate
|
||||
if (!this.isValidAugmentation(instance)) {
|
||||
throw new Error(`Invalid augmentation in package: ${packageName}`);
|
||||
}
|
||||
// Get manifest
|
||||
const manifest = instance.getManifest ? instance.getManifest() : {
|
||||
id,
|
||||
name: packageName,
|
||||
version: 'unknown',
|
||||
description: `Loaded from ${packageName}`,
|
||||
category: 'external'
|
||||
};
|
||||
const loaded = {
|
||||
id,
|
||||
instance,
|
||||
manifest,
|
||||
source: 'npm',
|
||||
loadTime: Date.now() - startTime
|
||||
};
|
||||
// Cache
|
||||
this.loaded.set(id, loaded);
|
||||
// Auto-register
|
||||
if (this.registry) {
|
||||
this.registry.register(instance);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to load augmentation from NPM ${packageName}: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load augmentation from local file
|
||||
*/
|
||||
async loadFromFile(path, config) {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
// Dynamic import
|
||||
const module = await import(path);
|
||||
// Extract augmentation class
|
||||
const AugmentationClass = module.default || module[Object.keys(module)[0]];
|
||||
if (!AugmentationClass) {
|
||||
throw new Error(`No augmentation class found in file ${path}`);
|
||||
}
|
||||
// Create instance
|
||||
const instance = new AugmentationClass(config);
|
||||
// Validate
|
||||
if (!this.isValidAugmentation(instance)) {
|
||||
throw new Error(`Invalid augmentation in file: ${path}`);
|
||||
}
|
||||
// Extract ID from path
|
||||
const id = path.split('/').pop()?.replace(/\.(js|ts)$/, '') || 'unknown';
|
||||
// Get manifest
|
||||
const manifest = instance.getManifest ? instance.getManifest() : {
|
||||
id,
|
||||
name: id,
|
||||
version: 'local',
|
||||
description: `Loaded from ${path}`,
|
||||
category: 'local'
|
||||
};
|
||||
const loaded = {
|
||||
id,
|
||||
instance,
|
||||
manifest,
|
||||
source: 'local',
|
||||
loadTime: Date.now() - startTime
|
||||
};
|
||||
// Cache
|
||||
this.loaded.set(id, loaded);
|
||||
// Auto-register
|
||||
if (this.registry) {
|
||||
this.registry.register(instance);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to load augmentation from file ${path}: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load multiple augmentations
|
||||
*/
|
||||
async loadBatch(augmentations) {
|
||||
const results = await Promise.allSettled(augmentations.map(aug => {
|
||||
switch (aug.source) {
|
||||
case 'cdn':
|
||||
return this.loadFromCDN(aug.id, aug.version, aug.config);
|
||||
case 'npm':
|
||||
return this.loadFromNPM(aug.id, aug.config);
|
||||
case 'local':
|
||||
return this.loadFromFile(aug.path || aug.id, aug.config);
|
||||
default:
|
||||
return Promise.reject(new Error(`Unknown source: ${aug.source}`));
|
||||
}
|
||||
}));
|
||||
const loaded = [];
|
||||
const errors = [];
|
||||
for (const result of results) {
|
||||
if (result.status === 'fulfilled') {
|
||||
loaded.push(result.value);
|
||||
}
|
||||
else {
|
||||
errors.push(result.reason.message);
|
||||
}
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
console.warn('Some augmentations failed to load:', errors);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
/**
|
||||
* Unload augmentation
|
||||
*/
|
||||
unload(id) {
|
||||
const loaded = this.loaded.get(id);
|
||||
if (!loaded)
|
||||
return false;
|
||||
// Shutdown if possible
|
||||
if (loaded.instance.shutdown) {
|
||||
loaded.instance.shutdown();
|
||||
}
|
||||
// Remove from registry if set
|
||||
// Note: Registry doesn't have unregister yet, would need to add
|
||||
// Remove from cache
|
||||
this.loaded.delete(id);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Get loaded augmentations
|
||||
*/
|
||||
getLoaded() {
|
||||
return Array.from(this.loaded.values());
|
||||
}
|
||||
/**
|
||||
* Check if augmentation is loaded
|
||||
*/
|
||||
isLoaded(id) {
|
||||
return this.loaded.has(id);
|
||||
}
|
||||
/**
|
||||
* Get loaded augmentation
|
||||
*/
|
||||
getAugmentation(id) {
|
||||
return this.loaded.get(id)?.instance || null;
|
||||
}
|
||||
/**
|
||||
* Load CDN module (browser-specific)
|
||||
*/
|
||||
async loadCDNModule(url) {
|
||||
// Check cache
|
||||
if (this.options.cache && this.cdnCache.has(url)) {
|
||||
return this.cdnCache.get(url);
|
||||
}
|
||||
// In browser environment
|
||||
if (typeof window !== 'undefined') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error(`Timeout loading ${url}`));
|
||||
}, this.options.timeout);
|
||||
// Create script element
|
||||
const script = document.createElement('script');
|
||||
script.type = 'module';
|
||||
script.src = url;
|
||||
// Handle load
|
||||
script.onload = async () => {
|
||||
clearTimeout(timeout);
|
||||
// The module should register itself on window
|
||||
const moduleId = url.split('/').pop()?.split('@')[0];
|
||||
if (moduleId && window[moduleId]) {
|
||||
const module = window[moduleId];
|
||||
// Cache
|
||||
if (this.options.cache) {
|
||||
this.cdnCache.set(url, module);
|
||||
}
|
||||
resolve(module);
|
||||
}
|
||||
else {
|
||||
reject(new Error(`Module not found on window: ${moduleId}`));
|
||||
}
|
||||
};
|
||||
// Handle error
|
||||
script.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error(`Failed to load script: ${url}`));
|
||||
};
|
||||
// Add to document
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// In Node.js, use dynamic import
|
||||
const module = await import(url);
|
||||
// Cache
|
||||
if (this.options.cache) {
|
||||
this.cdnCache.set(url, module);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Validate augmentation instance
|
||||
*/
|
||||
isValidAugmentation(instance) {
|
||||
// Check required properties
|
||||
return !!(instance.name &&
|
||||
instance.timing &&
|
||||
instance.operations &&
|
||||
instance.priority !== undefined &&
|
||||
typeof instance.execute === 'function' &&
|
||||
typeof instance.initialize === 'function');
|
||||
}
|
||||
/**
|
||||
* Clear all caches
|
||||
*/
|
||||
clearCache() {
|
||||
this.cdnCache.clear();
|
||||
}
|
||||
/**
|
||||
* Get load statistics
|
||||
*/
|
||||
getStats() {
|
||||
const loaded = Array.from(this.loaded.values());
|
||||
const totalLoadTime = loaded.reduce((sum, aug) => sum + aug.loadTime, 0);
|
||||
const sources = loaded.reduce((acc, aug) => {
|
||||
acc[aug.source] = (acc[aug.source] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
return {
|
||||
loaded: loaded.length,
|
||||
totalLoadTime,
|
||||
averageLoadTime: loaded.length > 0 ? totalLoadTime / loaded.length : 0,
|
||||
sources
|
||||
};
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=runtimeLoader.js.map
|
||||
File diff suppressed because one or more lines are too long
130
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/cache.d.ts
vendored
Normal file
130
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/cache.d.ts
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Intelligent Caching System
|
||||
*
|
||||
* High-performance LRU cache with smart eviction and batch optimization
|
||||
* Designed for minimal memory footprint and maximum hit ratio
|
||||
*/
|
||||
import type { ComputedDisplayFields, DisplayAugmentationStats } from './types.js';
|
||||
/**
|
||||
* LRU (Least Recently Used) Cache for computed display fields
|
||||
* Optimized for the display augmentation use case
|
||||
*/
|
||||
export declare class DisplayCache {
|
||||
private cache;
|
||||
private readonly maxSize;
|
||||
private stats;
|
||||
constructor(maxSize?: number);
|
||||
/**
|
||||
* Get cached display fields with LRU update
|
||||
* @param key Cache key
|
||||
* @returns Cached fields or null if not found
|
||||
*/
|
||||
get(key: string): ComputedDisplayFields | null;
|
||||
/**
|
||||
* Store computed display fields in cache
|
||||
* @param key Cache key
|
||||
* @param fields Computed display fields
|
||||
* @param computationTime Time taken to compute (for stats)
|
||||
*/
|
||||
set(key: string, fields: ComputedDisplayFields, computationTime?: number): void;
|
||||
/**
|
||||
* Check if a key exists in cache without affecting LRU order
|
||||
* @param key Cache key
|
||||
* @returns True if key exists
|
||||
*/
|
||||
has(key: string): boolean;
|
||||
/**
|
||||
* Generate cache key from data
|
||||
* @param id Entity ID (preferred)
|
||||
* @param data Fallback data for key generation
|
||||
* @param entityType Type of entity (noun/verb)
|
||||
* @returns Cache key string
|
||||
*/
|
||||
generateKey(id?: string, data?: any, entityType?: 'noun' | 'verb'): string;
|
||||
/**
|
||||
* Clear all cached entries
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Get cache statistics
|
||||
* @returns Cache performance statistics
|
||||
*/
|
||||
getStats(): DisplayAugmentationStats;
|
||||
/**
|
||||
* Get current cache size
|
||||
* @returns Number of cached entries
|
||||
*/
|
||||
size(): number;
|
||||
/**
|
||||
* Get cache capacity
|
||||
* @returns Maximum cache size
|
||||
*/
|
||||
capacity(): number;
|
||||
/**
|
||||
* Evict least recently used entry
|
||||
*/
|
||||
private evictOldest;
|
||||
/**
|
||||
* Simple hash function for cache keys
|
||||
* @param str String to hash
|
||||
* @returns Simple hash number
|
||||
*/
|
||||
private simpleHash;
|
||||
/**
|
||||
* Optimize cache by removing stale entries
|
||||
* Called periodically to maintain cache health
|
||||
*/
|
||||
optimizeCache(): void;
|
||||
/**
|
||||
* Precompute display fields for a batch of entities
|
||||
* @param entities Array of entities with their compute functions
|
||||
* @returns Promise resolving when batch is complete
|
||||
*/
|
||||
batchPrecompute<T>(entities: Array<{
|
||||
key: string;
|
||||
computeFn: () => Promise<ComputedDisplayFields>;
|
||||
}>): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Request deduplicator for batch processing
|
||||
* Prevents duplicate computations for the same data
|
||||
*/
|
||||
export declare class RequestDeduplicator {
|
||||
private pendingRequests;
|
||||
private readonly batchSize;
|
||||
constructor(batchSize?: number);
|
||||
/**
|
||||
* Deduplicate computation request
|
||||
* @param key Unique key for the computation
|
||||
* @param computeFn Function to compute the result
|
||||
* @returns Promise that resolves to the computed fields
|
||||
*/
|
||||
deduplicate(key: string, computeFn: () => Promise<ComputedDisplayFields>): Promise<ComputedDisplayFields>;
|
||||
/**
|
||||
* Get number of pending requests
|
||||
* @returns Number of pending computations
|
||||
*/
|
||||
getPendingCount(): number;
|
||||
/**
|
||||
* Clear all pending requests
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Shutdown the deduplicator
|
||||
*/
|
||||
shutdown(): void;
|
||||
}
|
||||
/**
|
||||
* Get global display cache instance
|
||||
* @param maxSize Optional cache size (only used on first call)
|
||||
* @returns Shared display cache instance
|
||||
*/
|
||||
export declare function getGlobalDisplayCache(maxSize?: number): DisplayCache;
|
||||
/**
|
||||
* Clear global cache (for testing or memory management)
|
||||
*/
|
||||
export declare function clearGlobalDisplayCache(): void;
|
||||
/**
|
||||
* Shutdown global cache and cleanup
|
||||
*/
|
||||
export declare function shutdownGlobalDisplayCache(): void;
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Intelligent Caching System
|
||||
*
|
||||
* High-performance LRU cache with smart eviction and batch optimization
|
||||
* Designed for minimal memory footprint and maximum hit ratio
|
||||
*/
|
||||
/**
|
||||
* LRU (Least Recently Used) Cache for computed display fields
|
||||
* Optimized for the display augmentation use case
|
||||
*/
|
||||
export class DisplayCache {
|
||||
constructor(maxSize = 1000) {
|
||||
this.cache = new Map();
|
||||
this.stats = {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
evictions: 0,
|
||||
totalComputations: 0,
|
||||
totalComputationTime: 0
|
||||
};
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
/**
|
||||
* Get cached display fields with LRU update
|
||||
* @param key Cache key
|
||||
* @returns Cached fields or null if not found
|
||||
*/
|
||||
get(key) {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) {
|
||||
this.stats.misses++;
|
||||
return null;
|
||||
}
|
||||
// Update LRU - move to end
|
||||
this.cache.delete(key);
|
||||
entry.lastAccessed = Date.now();
|
||||
entry.accessCount++;
|
||||
this.cache.set(key, entry);
|
||||
this.stats.hits++;
|
||||
return entry.fields;
|
||||
}
|
||||
/**
|
||||
* Store computed display fields in cache
|
||||
* @param key Cache key
|
||||
* @param fields Computed display fields
|
||||
* @param computationTime Time taken to compute (for stats)
|
||||
*/
|
||||
set(key, fields, computationTime) {
|
||||
// Remove if already exists (for LRU update)
|
||||
if (this.cache.has(key)) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
// Create cache entry
|
||||
const entry = {
|
||||
fields,
|
||||
lastAccessed: Date.now(),
|
||||
accessCount: 1
|
||||
};
|
||||
// Add to end (most recently used)
|
||||
this.cache.set(key, entry);
|
||||
// Update stats
|
||||
this.stats.totalComputations++;
|
||||
if (computationTime) {
|
||||
this.stats.totalComputationTime += computationTime;
|
||||
}
|
||||
// Evict oldest if over capacity
|
||||
if (this.cache.size > this.maxSize) {
|
||||
this.evictOldest();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if a key exists in cache without affecting LRU order
|
||||
* @param key Cache key
|
||||
* @returns True if key exists
|
||||
*/
|
||||
has(key) {
|
||||
return this.cache.has(key);
|
||||
}
|
||||
/**
|
||||
* Generate cache key from data
|
||||
* @param id Entity ID (preferred)
|
||||
* @param data Fallback data for key generation
|
||||
* @param entityType Type of entity (noun/verb)
|
||||
* @returns Cache key string
|
||||
*/
|
||||
generateKey(id, data, entityType = 'noun') {
|
||||
// Use ID if available (most reliable)
|
||||
if (id) {
|
||||
return `${entityType}:${id}`;
|
||||
}
|
||||
// Generate hash from data
|
||||
if (data) {
|
||||
const dataString = JSON.stringify(data, Object.keys(data).sort());
|
||||
const hash = this.simpleHash(dataString);
|
||||
return `${entityType}:hash:${hash}`;
|
||||
}
|
||||
// Fallback to timestamp (not ideal but prevents crashes)
|
||||
return `${entityType}:temp:${Date.now()}:${Math.random()}`;
|
||||
}
|
||||
/**
|
||||
* Clear all cached entries
|
||||
*/
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
this.stats = {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
evictions: 0,
|
||||
totalComputations: 0,
|
||||
totalComputationTime: 0
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get cache statistics
|
||||
* @returns Cache performance statistics
|
||||
*/
|
||||
getStats() {
|
||||
const hitRatio = this.stats.hits + this.stats.misses > 0
|
||||
? this.stats.hits / (this.stats.hits + this.stats.misses)
|
||||
: 0;
|
||||
const avgComputationTime = this.stats.totalComputations > 0
|
||||
? this.stats.totalComputationTime / this.stats.totalComputations
|
||||
: 0;
|
||||
// Analyze cached types for common types statistics
|
||||
const typeCount = new Map();
|
||||
let fastestComputation = Infinity;
|
||||
let slowestComputation = 0;
|
||||
for (const entry of this.cache.values()) {
|
||||
const type = entry.fields.type;
|
||||
typeCount.set(type, (typeCount.get(type) || 0) + 1);
|
||||
}
|
||||
const commonTypes = Array.from(typeCount.entries())
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
.slice(0, 10)
|
||||
.map(([type, count]) => ({
|
||||
type,
|
||||
count,
|
||||
percentage: Math.round((count / this.cache.size) * 100)
|
||||
}));
|
||||
return {
|
||||
totalComputations: this.stats.totalComputations,
|
||||
cacheHitRatio: Math.round(hitRatio * 100) / 100,
|
||||
averageComputationTime: Math.round(avgComputationTime * 100) / 100,
|
||||
commonTypes,
|
||||
performance: {
|
||||
fastestComputation,
|
||||
slowestComputation,
|
||||
totalComputationTime: this.stats.totalComputationTime
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get current cache size
|
||||
* @returns Number of cached entries
|
||||
*/
|
||||
size() {
|
||||
return this.cache.size;
|
||||
}
|
||||
/**
|
||||
* Get cache capacity
|
||||
* @returns Maximum cache size
|
||||
*/
|
||||
capacity() {
|
||||
return this.maxSize;
|
||||
}
|
||||
/**
|
||||
* Evict least recently used entry
|
||||
*/
|
||||
evictOldest() {
|
||||
// First entry is oldest (LRU)
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
if (firstKey) {
|
||||
this.cache.delete(firstKey);
|
||||
this.stats.evictions++;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Simple hash function for cache keys
|
||||
* @param str String to hash
|
||||
* @returns Simple hash number
|
||||
*/
|
||||
simpleHash(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32-bit integer
|
||||
}
|
||||
return Math.abs(hash);
|
||||
}
|
||||
/**
|
||||
* Optimize cache by removing stale entries
|
||||
* Called periodically to maintain cache health
|
||||
*/
|
||||
optimizeCache() {
|
||||
const now = Date.now();
|
||||
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
|
||||
const minAccessCount = 2; // Minimum access count to keep
|
||||
const toDelete = [];
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
// Remove very old entries with low access count
|
||||
if (now - entry.lastAccessed > maxAge && entry.accessCount < minAccessCount) {
|
||||
toDelete.push(key);
|
||||
}
|
||||
}
|
||||
// Remove stale entries
|
||||
for (const key of toDelete) {
|
||||
this.cache.delete(key);
|
||||
this.stats.evictions++;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Precompute display fields for a batch of entities
|
||||
* @param entities Array of entities with their compute functions
|
||||
* @returns Promise resolving when batch is complete
|
||||
*/
|
||||
async batchPrecompute(entities) {
|
||||
const promises = entities.map(async ({ key, computeFn }) => {
|
||||
if (!this.has(key)) {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const fields = await computeFn();
|
||||
const computationTime = Date.now() - startTime;
|
||||
this.set(key, fields, computationTime);
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Batch precompute failed for key ${key}:`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Request deduplicator for batch processing
|
||||
* Prevents duplicate computations for the same data
|
||||
*/
|
||||
export class RequestDeduplicator {
|
||||
constructor(batchSize = 50) {
|
||||
this.pendingRequests = new Map();
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
/**
|
||||
* Deduplicate computation request
|
||||
* @param key Unique key for the computation
|
||||
* @param computeFn Function to compute the result
|
||||
* @returns Promise that resolves to the computed fields
|
||||
*/
|
||||
async deduplicate(key, computeFn) {
|
||||
// Return existing promise if already pending
|
||||
if (this.pendingRequests.has(key)) {
|
||||
return this.pendingRequests.get(key);
|
||||
}
|
||||
// Create new computation promise
|
||||
const promise = computeFn().finally(() => {
|
||||
// Remove from pending when complete
|
||||
this.pendingRequests.delete(key);
|
||||
});
|
||||
this.pendingRequests.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
/**
|
||||
* Get number of pending requests
|
||||
* @returns Number of pending computations
|
||||
*/
|
||||
getPendingCount() {
|
||||
return this.pendingRequests.size;
|
||||
}
|
||||
/**
|
||||
* Clear all pending requests
|
||||
*/
|
||||
clear() {
|
||||
this.pendingRequests.clear();
|
||||
}
|
||||
/**
|
||||
* Shutdown the deduplicator
|
||||
*/
|
||||
shutdown() {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Global cache instance management
|
||||
* Provides singleton access to display cache
|
||||
*/
|
||||
let globalDisplayCache = null;
|
||||
/**
|
||||
* Get global display cache instance
|
||||
* @param maxSize Optional cache size (only used on first call)
|
||||
* @returns Shared display cache instance
|
||||
*/
|
||||
export function getGlobalDisplayCache(maxSize) {
|
||||
if (!globalDisplayCache) {
|
||||
globalDisplayCache = new DisplayCache(maxSize);
|
||||
// Set up periodic optimization
|
||||
setInterval(() => {
|
||||
globalDisplayCache?.optimizeCache();
|
||||
}, 60 * 60 * 1000); // Every hour
|
||||
}
|
||||
return globalDisplayCache;
|
||||
}
|
||||
/**
|
||||
* Clear global cache (for testing or memory management)
|
||||
*/
|
||||
export function clearGlobalDisplayCache() {
|
||||
if (globalDisplayCache) {
|
||||
globalDisplayCache.clear();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Shutdown global cache and cleanup
|
||||
*/
|
||||
export function shutdownGlobalDisplayCache() {
|
||||
if (globalDisplayCache) {
|
||||
globalDisplayCache.clear();
|
||||
globalDisplayCache = null;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=cache.js.map
|
||||
File diff suppressed because one or more lines are too long
52
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/fieldPatterns.d.ts
vendored
Normal file
52
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/fieldPatterns.d.ts
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Smart Field Patterns
|
||||
*
|
||||
* Intelligent field detection patterns for mapping user data to display fields
|
||||
* Uses semantic understanding and common naming conventions
|
||||
*/
|
||||
import type { FieldPattern, FieldComputationContext } from './types.js';
|
||||
/**
|
||||
* Universal field patterns that work across all data types
|
||||
* Ordered by confidence level (highest first)
|
||||
*/
|
||||
export declare const UNIVERSAL_FIELD_PATTERNS: FieldPattern[];
|
||||
/**
|
||||
* Type-specific field patterns for enhanced detection
|
||||
* Used when we know the specific type of the entity
|
||||
*/
|
||||
export declare const TYPE_SPECIFIC_PATTERNS: Record<string, FieldPattern[]>;
|
||||
/**
|
||||
* Get field patterns for a specific entity type
|
||||
* @param entityType The type of entity (noun or verb)
|
||||
* @param specificType Optional specific noun/verb type
|
||||
* @returns Array of applicable field patterns
|
||||
*/
|
||||
export declare function getFieldPatterns(entityType: 'noun' | 'verb', specificType?: string): FieldPattern[];
|
||||
/**
|
||||
* Priority fields for different entity types (for AI analysis)
|
||||
* Used by the BrainyTypes and neural processing
|
||||
*/
|
||||
export declare const TYPE_PRIORITY_FIELDS: Record<string, string[]>;
|
||||
/**
|
||||
* Get priority fields for intelligent analysis
|
||||
* @param entityType The type of entity
|
||||
* @param specificType Optional specific type
|
||||
* @returns Array of priority field names
|
||||
*/
|
||||
export declare function getPriorityFields(entityType: 'noun' | 'verb', specificType?: string): string[];
|
||||
/**
|
||||
* Smart field value extraction with type-aware processing
|
||||
* @param data The data object to extract from
|
||||
* @param pattern The field pattern to apply
|
||||
* @param context The computation context
|
||||
* @returns The extracted and processed field value
|
||||
*/
|
||||
export declare function extractFieldValue(data: any, pattern: FieldPattern, context: FieldComputationContext): any;
|
||||
/**
|
||||
* Calculate confidence score for field detection
|
||||
* @param pattern The field pattern
|
||||
* @param context The computation context
|
||||
* @param value The extracted value
|
||||
* @returns Confidence score (0-1)
|
||||
*/
|
||||
export declare function calculateFieldConfidence(pattern: FieldPattern, context: FieldComputationContext, value: any): number;
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Smart Field Patterns
|
||||
*
|
||||
* Intelligent field detection patterns for mapping user data to display fields
|
||||
* Uses semantic understanding and common naming conventions
|
||||
*/
|
||||
import { NounType } from '../../types/graphTypes.js';
|
||||
/**
|
||||
* Universal field patterns that work across all data types
|
||||
* Ordered by confidence level (highest first)
|
||||
*/
|
||||
export const UNIVERSAL_FIELD_PATTERNS = [
|
||||
// Title/Name Patterns (Highest Priority)
|
||||
{
|
||||
fields: ['name', 'title', 'displayName', 'label', 'heading'],
|
||||
displayField: 'title',
|
||||
confidence: 0.95
|
||||
},
|
||||
{
|
||||
fields: ['firstName', 'lastName', 'fullName', 'realName'],
|
||||
displayField: 'title',
|
||||
confidence: 0.9,
|
||||
applicableTypes: [NounType.Person, NounType.User],
|
||||
transform: (value, context) => {
|
||||
const { metadata } = context;
|
||||
if (metadata.firstName && metadata.lastName) {
|
||||
return `${metadata.firstName} ${metadata.lastName}`.trim();
|
||||
}
|
||||
return String(value || '');
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: ['companyName', 'organizationName', 'orgName', 'businessName'],
|
||||
displayField: 'title',
|
||||
confidence: 0.9,
|
||||
applicableTypes: [NounType.Organization]
|
||||
},
|
||||
{
|
||||
fields: ['filename', 'fileName', 'documentTitle', 'docName'],
|
||||
displayField: 'title',
|
||||
confidence: 0.85,
|
||||
applicableTypes: [NounType.Document, NounType.File, NounType.Media]
|
||||
},
|
||||
{
|
||||
fields: ['projectName', 'projectTitle', 'initiative'],
|
||||
displayField: 'title',
|
||||
confidence: 0.9,
|
||||
applicableTypes: [NounType.Project]
|
||||
},
|
||||
{
|
||||
fields: ['taskName', 'taskTitle', 'action', 'todo'],
|
||||
displayField: 'title',
|
||||
confidence: 0.85,
|
||||
applicableTypes: [NounType.Task]
|
||||
},
|
||||
{
|
||||
fields: ['subject', 'topic', 'headline', 'caption'],
|
||||
displayField: 'title',
|
||||
confidence: 0.8
|
||||
},
|
||||
// Description Patterns (High Priority)
|
||||
{
|
||||
fields: ['description', 'summary', 'overview', 'details'],
|
||||
displayField: 'description',
|
||||
confidence: 0.9
|
||||
},
|
||||
{
|
||||
fields: ['bio', 'biography', 'profile', 'about'],
|
||||
displayField: 'description',
|
||||
confidence: 0.85,
|
||||
applicableTypes: [NounType.Person, NounType.User]
|
||||
},
|
||||
{
|
||||
fields: ['content', 'text', 'body', 'message'],
|
||||
displayField: 'description',
|
||||
confidence: 0.8
|
||||
},
|
||||
{
|
||||
fields: ['abstract', 'excerpt', 'snippet', 'preview'],
|
||||
displayField: 'description',
|
||||
confidence: 0.75
|
||||
},
|
||||
{
|
||||
fields: ['notes', 'comments', 'remarks', 'observations'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7
|
||||
},
|
||||
// Type Patterns (Medium Priority)
|
||||
{
|
||||
fields: ['type', 'category', 'classification', 'kind'],
|
||||
displayField: 'type',
|
||||
confidence: 0.9
|
||||
},
|
||||
{
|
||||
fields: ['nounType', 'entityType', 'objectType'],
|
||||
displayField: 'type',
|
||||
confidence: 0.95
|
||||
},
|
||||
{
|
||||
fields: ['role', 'position', 'jobTitle', 'occupation'],
|
||||
displayField: 'type',
|
||||
confidence: 0.8,
|
||||
applicableTypes: [NounType.Person, NounType.User],
|
||||
transform: (value) => String(value || 'Person')
|
||||
},
|
||||
{
|
||||
fields: ['industry', 'sector', 'domain', 'field'],
|
||||
displayField: 'type',
|
||||
confidence: 0.7,
|
||||
applicableTypes: [NounType.Organization]
|
||||
},
|
||||
// Tag Patterns (Medium Priority)
|
||||
{
|
||||
fields: ['tags', 'keywords', 'labels', 'categories'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.85
|
||||
},
|
||||
{
|
||||
fields: ['topics', 'subjects', 'themes'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.8
|
||||
}
|
||||
];
|
||||
/**
|
||||
* Type-specific field patterns for enhanced detection
|
||||
* Used when we know the specific type of the entity
|
||||
*/
|
||||
export const TYPE_SPECIFIC_PATTERNS = {
|
||||
[NounType.Person]: [
|
||||
{
|
||||
fields: ['email', 'emailAddress', 'contactEmail'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7,
|
||||
transform: (value, context) => {
|
||||
const { metadata } = context;
|
||||
const role = metadata.role || metadata.jobTitle || metadata.position;
|
||||
const company = metadata.company || metadata.organization || metadata.employer;
|
||||
const parts = [];
|
||||
if (role)
|
||||
parts.push(role);
|
||||
if (company)
|
||||
parts.push(`at ${company}`);
|
||||
if (parts.length === 0 && value)
|
||||
parts.push(`Contact: ${value}`);
|
||||
return parts.join(' ') || 'Person';
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: ['phone', 'phoneNumber', 'mobile', 'cell'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.6
|
||||
}
|
||||
],
|
||||
[NounType.Organization]: [
|
||||
{
|
||||
fields: ['website', 'url', 'homepage', 'domain'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7,
|
||||
transform: (value, context) => {
|
||||
const { metadata } = context;
|
||||
const industry = metadata.industry || metadata.sector;
|
||||
const location = metadata.location || metadata.city || metadata.country;
|
||||
const parts = [];
|
||||
if (industry)
|
||||
parts.push(industry);
|
||||
parts.push('organization');
|
||||
if (location)
|
||||
parts.push(`in ${location}`);
|
||||
return parts.join(' ');
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: ['employees', 'size', 'headcount'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.6
|
||||
}
|
||||
],
|
||||
[NounType.Project]: [
|
||||
{
|
||||
fields: ['status', 'phase', 'stage', 'state'],
|
||||
displayField: 'description',
|
||||
confidence: 0.8,
|
||||
transform: (value, context) => {
|
||||
const { metadata } = context;
|
||||
const status = String(value || 'active').toLowerCase();
|
||||
const budget = metadata.budget || metadata.cost;
|
||||
const lead = metadata.lead || metadata.manager || metadata.owner;
|
||||
const parts = [];
|
||||
parts.push(status.charAt(0).toUpperCase() + status.slice(1));
|
||||
if (metadata.description)
|
||||
parts.push('project');
|
||||
if (lead)
|
||||
parts.push(`led by ${lead}`);
|
||||
if (budget)
|
||||
parts.push(`($${parseInt(String(budget)).toLocaleString()} budget)`);
|
||||
return parts.join(' ');
|
||||
}
|
||||
}
|
||||
],
|
||||
[NounType.Document]: [
|
||||
{
|
||||
fields: ['author', 'creator', 'writer'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7,
|
||||
transform: (value, context) => {
|
||||
const { metadata } = context;
|
||||
const docType = metadata.type || metadata.category || 'document';
|
||||
const date = metadata.date || metadata.created || metadata.published;
|
||||
const parts = [];
|
||||
if (docType)
|
||||
parts.push(docType);
|
||||
if (value)
|
||||
parts.push(`by ${value}`);
|
||||
if (date) {
|
||||
const dateStr = new Date(date).toLocaleDateString();
|
||||
parts.push(`(${dateStr})`);
|
||||
}
|
||||
return parts.join(' ');
|
||||
}
|
||||
}
|
||||
],
|
||||
[NounType.Task]: [
|
||||
{
|
||||
fields: ['priority', 'urgency', 'importance'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.7
|
||||
}
|
||||
]
|
||||
};
|
||||
/**
|
||||
* Get field patterns for a specific entity type
|
||||
* @param entityType The type of entity (noun or verb)
|
||||
* @param specificType Optional specific noun/verb type
|
||||
* @returns Array of applicable field patterns
|
||||
*/
|
||||
export function getFieldPatterns(entityType, specificType) {
|
||||
const patterns = [...UNIVERSAL_FIELD_PATTERNS];
|
||||
if (entityType === 'noun' && specificType && TYPE_SPECIFIC_PATTERNS[specificType]) {
|
||||
patterns.unshift(...TYPE_SPECIFIC_PATTERNS[specificType]);
|
||||
}
|
||||
return patterns.sort((a, b) => b.confidence - a.confidence);
|
||||
}
|
||||
/**
|
||||
* Priority fields for different entity types (for AI analysis)
|
||||
* Used by the BrainyTypes and neural processing
|
||||
*/
|
||||
export const TYPE_PRIORITY_FIELDS = {
|
||||
[NounType.Person]: [
|
||||
'name', 'firstName', 'lastName', 'fullName', 'displayName',
|
||||
'email', 'role', 'jobTitle', 'position', 'title',
|
||||
'bio', 'description', 'about', 'profile',
|
||||
'company', 'organization', 'employer'
|
||||
],
|
||||
[NounType.Organization]: [
|
||||
'name', 'companyName', 'organizationName', 'title',
|
||||
'industry', 'sector', 'domain', 'type',
|
||||
'description', 'about', 'summary',
|
||||
'location', 'city', 'country', 'headquarters',
|
||||
'website', 'url'
|
||||
],
|
||||
[NounType.Project]: [
|
||||
'name', 'projectName', 'title', 'projectTitle',
|
||||
'description', 'summary', 'overview', 'goal',
|
||||
'status', 'phase', 'stage', 'state',
|
||||
'lead', 'manager', 'owner', 'team',
|
||||
'budget', 'timeline', 'deadline'
|
||||
],
|
||||
[NounType.Document]: [
|
||||
'title', 'filename', 'name', 'subject',
|
||||
'content', 'text', 'body', 'summary',
|
||||
'author', 'creator', 'writer',
|
||||
'type', 'category', 'format',
|
||||
'date', 'created', 'published'
|
||||
],
|
||||
[NounType.Task]: [
|
||||
'title', 'name', 'taskName', 'action',
|
||||
'description', 'details', 'notes',
|
||||
'status', 'state', 'priority',
|
||||
'assignee', 'owner', 'responsible',
|
||||
'due', 'deadline', 'dueDate'
|
||||
],
|
||||
[NounType.Event]: [
|
||||
'name', 'title', 'eventName',
|
||||
'description', 'details', 'summary',
|
||||
'startDate', 'endDate', 'date', 'time',
|
||||
'location', 'venue', 'address',
|
||||
'organizer', 'host', 'creator'
|
||||
],
|
||||
[NounType.Product]: [
|
||||
'name', 'productName', 'title',
|
||||
'description', 'summary', 'features',
|
||||
'price', 'cost', 'value',
|
||||
'category', 'type', 'brand',
|
||||
'manufacturer', 'vendor'
|
||||
]
|
||||
};
|
||||
/**
|
||||
* Get priority fields for intelligent analysis
|
||||
* @param entityType The type of entity
|
||||
* @param specificType Optional specific type
|
||||
* @returns Array of priority field names
|
||||
*/
|
||||
export function getPriorityFields(entityType, specificType) {
|
||||
if (entityType === 'noun' && specificType && TYPE_PRIORITY_FIELDS[specificType]) {
|
||||
return TYPE_PRIORITY_FIELDS[specificType];
|
||||
}
|
||||
// Default priority fields for any entity
|
||||
return [
|
||||
'name', 'title', 'label', 'displayName',
|
||||
'description', 'summary', 'about', 'details',
|
||||
'type', 'category', 'kind', 'classification',
|
||||
'tags', 'keywords', 'labels'
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Smart field value extraction with type-aware processing
|
||||
* @param data The data object to extract from
|
||||
* @param pattern The field pattern to apply
|
||||
* @param context The computation context
|
||||
* @returns The extracted and processed field value
|
||||
*/
|
||||
export function extractFieldValue(data, pattern, context) {
|
||||
// Find the first matching field
|
||||
let value = null;
|
||||
let matchedField = null;
|
||||
for (const field of pattern.fields) {
|
||||
if (data[field] !== undefined && data[field] !== null && data[field] !== '') {
|
||||
value = data[field];
|
||||
matchedField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (value === null)
|
||||
return null;
|
||||
// Apply transformation if provided
|
||||
if (pattern.transform) {
|
||||
try {
|
||||
return pattern.transform(value, context);
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(`Field transformation error for ${matchedField}:`, error);
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
// Default processing based on display field type
|
||||
switch (pattern.displayField) {
|
||||
case 'title':
|
||||
case 'description':
|
||||
case 'type':
|
||||
return String(value);
|
||||
case 'tags':
|
||||
if (Array.isArray(value))
|
||||
return value;
|
||||
if (typeof value === 'string') {
|
||||
return value.split(/[,;]\s*|\s+/).filter(Boolean);
|
||||
}
|
||||
return [String(value)];
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate confidence score for field detection
|
||||
* @param pattern The field pattern
|
||||
* @param context The computation context
|
||||
* @param value The extracted value
|
||||
* @returns Confidence score (0-1)
|
||||
*/
|
||||
export function calculateFieldConfidence(pattern, context, value) {
|
||||
let confidence = pattern.confidence;
|
||||
// Boost confidence if type matches
|
||||
if (pattern.applicableTypes && context.typeResult) {
|
||||
if (pattern.applicableTypes.includes(context.typeResult.type)) {
|
||||
confidence = Math.min(1.0, confidence + 0.1);
|
||||
}
|
||||
}
|
||||
// Reduce confidence for empty or very short values
|
||||
if (typeof value === 'string') {
|
||||
if (value.length < 2) {
|
||||
confidence *= 0.5;
|
||||
}
|
||||
else if (value.length < 5) {
|
||||
confidence *= 0.8;
|
||||
}
|
||||
}
|
||||
// Reduce confidence for generic values
|
||||
const genericValues = ['unknown', 'n/a', 'null', 'undefined', 'default'];
|
||||
if (typeof value === 'string' && genericValues.includes(value.toLowerCase())) {
|
||||
confidence *= 0.3;
|
||||
}
|
||||
return Math.max(0, Math.min(1, confidence));
|
||||
}
|
||||
//# sourceMappingURL=fieldPatterns.js.map
|
||||
File diff suppressed because one or more lines are too long
57
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/iconMappings.d.ts
vendored
Normal file
57
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/iconMappings.d.ts
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Clean Display
|
||||
*
|
||||
* Simple, clean display without icons - focusing on AI-powered
|
||||
* titles, descriptions, and smart formatting that matches
|
||||
* Soulcraft's minimal aesthetic
|
||||
*/
|
||||
/**
|
||||
* No icon mappings - clean, minimal approach
|
||||
* The real value is in AI-generated titles and enhanced descriptions,
|
||||
* not visual clutter that doesn't align with professional aesthetics
|
||||
*/
|
||||
export declare const NOUN_TYPE_ICONS: Record<string, string>;
|
||||
/**
|
||||
* No icon mappings for verbs either - focus on clear relationship descriptions
|
||||
* Human-readable relationship text is more valuable than symbolic representations
|
||||
*/
|
||||
export declare const VERB_TYPE_ICONS: Record<string, string>;
|
||||
/**
|
||||
* Get icon for a noun type (returns empty string for clean display)
|
||||
* @param type The noun type
|
||||
* @returns Empty string (no icons)
|
||||
*/
|
||||
export declare function getNounIcon(type: string): string;
|
||||
/**
|
||||
* Get icon for a verb type (returns empty string for clean display)
|
||||
* @param type The verb type
|
||||
* @returns Empty string (no icons)
|
||||
*/
|
||||
export declare function getVerbIcon(type: string): string;
|
||||
/**
|
||||
* Get coverage statistics (for backwards compatibility)
|
||||
* @returns Coverage info showing clean approach
|
||||
*/
|
||||
export declare function getIconCoverage(): {
|
||||
nounTypes: {
|
||||
total: string;
|
||||
covered: string;
|
||||
};
|
||||
verbTypes: {
|
||||
total: string;
|
||||
covered: string;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Check if an icon exists for a type (always false for clean display)
|
||||
* @param type The type to check
|
||||
* @param entityType Whether it's a noun or verb
|
||||
* @returns Always false (no icons)
|
||||
*/
|
||||
export declare function hasIcon(type: string, entityType?: 'noun' | 'verb'): boolean;
|
||||
/**
|
||||
* Get fallback icon (returns empty string for clean display)
|
||||
* @param entityType The entity type
|
||||
* @returns Empty string (no fallback icons)
|
||||
*/
|
||||
export declare function getFallbackIcon(entityType?: 'noun' | 'verb'): string;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Clean Display
|
||||
*
|
||||
* Simple, clean display without icons - focusing on AI-powered
|
||||
* titles, descriptions, and smart formatting that matches
|
||||
* Soulcraft's minimal aesthetic
|
||||
*/
|
||||
/**
|
||||
* No icon mappings - clean, minimal approach
|
||||
* The real value is in AI-generated titles and enhanced descriptions,
|
||||
* not visual clutter that doesn't align with professional aesthetics
|
||||
*/
|
||||
export const NOUN_TYPE_ICONS = {};
|
||||
/**
|
||||
* No icon mappings for verbs either - focus on clear relationship descriptions
|
||||
* Human-readable relationship text is more valuable than symbolic representations
|
||||
*/
|
||||
export const VERB_TYPE_ICONS = {};
|
||||
/**
|
||||
* Get icon for a noun type (returns empty string for clean display)
|
||||
* @param type The noun type
|
||||
* @returns Empty string (no icons)
|
||||
*/
|
||||
export function getNounIcon(type) {
|
||||
return ''; // Clean, no icons
|
||||
}
|
||||
/**
|
||||
* Get icon for a verb type (returns empty string for clean display)
|
||||
* @param type The verb type
|
||||
* @returns Empty string (no icons)
|
||||
*/
|
||||
export function getVerbIcon(type) {
|
||||
return ''; // Clean, no icons
|
||||
}
|
||||
/**
|
||||
* Get coverage statistics (for backwards compatibility)
|
||||
* @returns Coverage info showing clean approach
|
||||
*/
|
||||
export function getIconCoverage() {
|
||||
return {
|
||||
nounTypes: {
|
||||
total: 'Clean display - no icons needed',
|
||||
covered: 'Focus on AI-powered content'
|
||||
},
|
||||
verbTypes: {
|
||||
total: 'Clean display - no icons needed',
|
||||
covered: 'Focus on relationship descriptions'
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Check if an icon exists for a type (always false for clean display)
|
||||
* @param type The type to check
|
||||
* @param entityType Whether it's a noun or verb
|
||||
* @returns Always false (no icons)
|
||||
*/
|
||||
export function hasIcon(type, entityType = 'noun') {
|
||||
return false; // Clean approach - no icons
|
||||
}
|
||||
/**
|
||||
* Get fallback icon (returns empty string for clean display)
|
||||
* @param entityType The entity type
|
||||
* @returns Empty string (no fallback icons)
|
||||
*/
|
||||
export function getFallbackIcon(entityType = 'noun') {
|
||||
return ''; // Clean, minimal display
|
||||
}
|
||||
//# sourceMappingURL=iconMappings.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"iconMappings.js","sourceRoot":"","sources":["../../../src/augmentations/display/iconMappings.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B,EAAE,CAAA;AAEzD;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B,EAAE,CAAA;AAEzD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,EAAE,CAAA,CAAC,kBAAkB;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,EAAE,CAAA,CAAC,kBAAkB;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO;QACL,SAAS,EAAE;YACT,KAAK,EAAE,iCAAiC;YACxC,OAAO,EAAE,6BAA6B;SACvC;QACD,SAAS,EAAE;YACT,KAAK,EAAE,iCAAiC;YACxC,OAAO,EAAE,oCAAoC;SAC9C;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,aAA8B,MAAM;IACxE,OAAO,KAAK,CAAA,CAAC,4BAA4B;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,aAA8B,MAAM;IAClE,OAAO,EAAE,CAAA,CAAC,yBAAyB;AACrC,CAAC"}
|
||||
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/intelligentComputation.d.ts
vendored
Normal file
109
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/intelligentComputation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Intelligent Computation Engine
|
||||
*
|
||||
* Leverages existing Brainy AI infrastructure for intelligent field computation:
|
||||
* - BrainyTypes for semantic type detection
|
||||
* - Neural Import patterns for field analysis
|
||||
* - JSON processing utilities for field extraction
|
||||
* - Existing NounType/VerbType taxonomy (31+40 types)
|
||||
*/
|
||||
import type { ComputedDisplayFields, DisplayConfig } from './types.js';
|
||||
import type { GraphVerb } from '../../coreTypes.js';
|
||||
/**
|
||||
* Intelligent field computation engine
|
||||
* Coordinates AI-powered analysis with fallback heuristics
|
||||
*/
|
||||
export declare class IntelligentComputationEngine {
|
||||
private typeMatcher;
|
||||
protected config: DisplayConfig;
|
||||
private initialized;
|
||||
constructor(config: DisplayConfig);
|
||||
/**
|
||||
* Initialize the computation engine with AI components
|
||||
*/
|
||||
initialize(): Promise<void>;
|
||||
/**
|
||||
* Compute display fields for a noun using AI-first approach
|
||||
* @param data The noun data/metadata
|
||||
* @param id Optional noun ID
|
||||
* @returns Computed display fields
|
||||
*/
|
||||
computeNounDisplay(data: any, id?: string): Promise<ComputedDisplayFields>;
|
||||
/**
|
||||
* Compute display fields for a verb using AI-first approach
|
||||
* @param verb The verb/relationship data
|
||||
* @returns Computed display fields
|
||||
*/
|
||||
computeVerbDisplay(verb: GraphVerb): Promise<ComputedDisplayFields>;
|
||||
/**
|
||||
* AI-powered computation using your existing BrainyTypes
|
||||
* @param data Entity data/metadata
|
||||
* @param entityType Type of entity (noun/verb)
|
||||
* @param options Additional options
|
||||
* @returns AI-computed display fields
|
||||
*/
|
||||
private computeWithAI;
|
||||
/**
|
||||
* AI-powered verb computation using relationship analysis
|
||||
* @param verb The verb/relationship
|
||||
* @returns AI-computed display fields
|
||||
*/
|
||||
private computeVerbWithAI;
|
||||
/**
|
||||
* Heuristic computation when AI is unavailable
|
||||
* @param data Entity data
|
||||
* @param entityType Type of entity
|
||||
* @param options Additional options
|
||||
* @returns Heuristically computed display fields
|
||||
*/
|
||||
private computeWithHeuristics;
|
||||
/**
|
||||
* Compute intelligent title using AI insights and your field extraction
|
||||
* @param context Computation context with AI results
|
||||
* @returns Computed title
|
||||
*/
|
||||
private computeIntelligentTitle;
|
||||
/**
|
||||
* Compute intelligent description using AI insights and context
|
||||
* @param context Computation context
|
||||
* @returns Enhanced description
|
||||
*/
|
||||
private computeIntelligentDescription;
|
||||
/**
|
||||
* Compute intelligent tags using type analysis
|
||||
* @param context Computation context
|
||||
* @returns Generated tags array
|
||||
*/
|
||||
private computeIntelligentTags;
|
||||
/**
|
||||
* Compute verb title (relationship summary)
|
||||
* @param context Verb computation context
|
||||
* @returns Verb title
|
||||
*/
|
||||
private computeVerbTitle;
|
||||
/**
|
||||
* Create minimal display for error cases
|
||||
* @param data Entity data
|
||||
* @param entityType Entity type
|
||||
* @returns Minimal display fields
|
||||
*/
|
||||
private createMinimalDisplay;
|
||||
private computePersonTitle;
|
||||
private computeOrganizationTitle;
|
||||
private computeProjectTitle;
|
||||
private computeDocumentTitle;
|
||||
private extractBestTitle;
|
||||
private createContextAwareDescription;
|
||||
private extractExplicitTags;
|
||||
private generateSemanticTags;
|
||||
private getReadableVerbPhrase;
|
||||
private computeVerbDescription;
|
||||
private computeVerbTags;
|
||||
private computeHumanReadableRelationship;
|
||||
private detectTypeHeuristically;
|
||||
private extractFieldWithPatterns;
|
||||
/**
|
||||
* Shutdown the computation engine
|
||||
*/
|
||||
shutdown(): Promise<void>;
|
||||
}
|
||||
|
|
@ -0,0 +1,462 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Intelligent Computation Engine
|
||||
*
|
||||
* Leverages existing Brainy AI infrastructure for intelligent field computation:
|
||||
* - BrainyTypes for semantic type detection
|
||||
* - Neural Import patterns for field analysis
|
||||
* - JSON processing utilities for field extraction
|
||||
* - Existing NounType/VerbType taxonomy (31+40 types)
|
||||
*/
|
||||
import { getBrainyTypes } from '../typeMatching/brainyTypes.js';
|
||||
import { getFieldPatterns, getPriorityFields } from './fieldPatterns.js';
|
||||
import { prepareJsonForVectorization } from '../../utils/jsonProcessing.js';
|
||||
import { NounType, VerbType } from '../../types/graphTypes.js';
|
||||
/**
|
||||
* Intelligent field computation engine
|
||||
* Coordinates AI-powered analysis with fallback heuristics
|
||||
*/
|
||||
export class IntelligentComputationEngine {
|
||||
constructor(config) {
|
||||
this.typeMatcher = null;
|
||||
this.initialized = false;
|
||||
this.config = config;
|
||||
}
|
||||
/**
|
||||
* Initialize the computation engine with AI components
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
try {
|
||||
// 🧠 LEVERAGE YOUR EXISTING AI INFRASTRUCTURE
|
||||
this.typeMatcher = await getBrainyTypes();
|
||||
if (this.typeMatcher) {
|
||||
console.log('🎨 Display computation engine initialized with AI intelligence');
|
||||
}
|
||||
else {
|
||||
console.warn('🎨 Display computation engine running in basic mode (AI unavailable)');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('🎨 AI initialization failed, using heuristic fallback:', error);
|
||||
}
|
||||
this.initialized = true;
|
||||
}
|
||||
/**
|
||||
* Compute display fields for a noun using AI-first approach
|
||||
* @param data The noun data/metadata
|
||||
* @param id Optional noun ID
|
||||
* @returns Computed display fields
|
||||
*/
|
||||
async computeNounDisplay(data, id) {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
// 🟢 PRIMARY PATH: Use your existing AI intelligence
|
||||
if (this.typeMatcher) {
|
||||
return await this.computeWithAI(data, 'noun', { id });
|
||||
}
|
||||
// 🟡 FALLBACK PATH: Use heuristic patterns
|
||||
return await this.computeWithHeuristics(data, 'noun', { id });
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Display computation failed, using minimal fallback:', error);
|
||||
return this.createMinimalDisplay(data, 'noun');
|
||||
}
|
||||
finally {
|
||||
const computationTime = Date.now() - startTime;
|
||||
if (this.config.debugMode) {
|
||||
console.log(`Display computation took ${computationTime}ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Compute display fields for a verb using AI-first approach
|
||||
* @param verb The verb/relationship data
|
||||
* @returns Computed display fields
|
||||
*/
|
||||
async computeVerbDisplay(verb) {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
// 🟢 PRIMARY PATH: Use your existing AI for verb analysis
|
||||
if (this.typeMatcher) {
|
||||
return await this.computeVerbWithAI(verb);
|
||||
}
|
||||
// 🟡 FALLBACK PATH: Use heuristic patterns for verbs
|
||||
return await this.computeWithHeuristics(verb, 'verb');
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Verb display computation failed, using minimal fallback:', error);
|
||||
return this.createMinimalDisplay(verb, 'verb');
|
||||
}
|
||||
finally {
|
||||
const computationTime = Date.now() - startTime;
|
||||
if (this.config.debugMode) {
|
||||
console.log(`Verb display computation took ${computationTime}ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* AI-powered computation using your existing BrainyTypes
|
||||
* @param data Entity data/metadata
|
||||
* @param entityType Type of entity (noun/verb)
|
||||
* @param options Additional options
|
||||
* @returns AI-computed display fields
|
||||
*/
|
||||
async computeWithAI(data, entityType, options = {}) {
|
||||
// 🧠 USE YOUR EXISTING TYPE DETECTION AI
|
||||
const typeResult = await this.typeMatcher.matchNounType(data);
|
||||
// Create computation context
|
||||
const context = {
|
||||
data,
|
||||
metadata: data,
|
||||
typeResult,
|
||||
config: this.config,
|
||||
entityType
|
||||
};
|
||||
// 🟢 INTELLIGENT FIELD EXTRACTION using your patterns + AI insights
|
||||
const displayFields = {
|
||||
title: await this.computeIntelligentTitle(context),
|
||||
description: await this.computeIntelligentDescription(context),
|
||||
type: typeResult.type,
|
||||
tags: await this.computeIntelligentTags(context),
|
||||
confidence: typeResult.confidence,
|
||||
reasoning: this.config.debugMode ? typeResult.reasoning : undefined,
|
||||
alternatives: this.config.debugMode ? typeResult.alternatives : undefined,
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
};
|
||||
return displayFields;
|
||||
}
|
||||
/**
|
||||
* AI-powered verb computation using relationship analysis
|
||||
* @param verb The verb/relationship
|
||||
* @returns AI-computed display fields
|
||||
*/
|
||||
async computeVerbWithAI(verb) {
|
||||
// 🧠 USE YOUR EXISTING VERB TYPE DETECTION
|
||||
const typeResult = await this.typeMatcher.matchVerbType(verb, 0.7);
|
||||
// Create verb computation context
|
||||
const context = {
|
||||
data: verb,
|
||||
metadata: verb.metadata || {},
|
||||
typeResult,
|
||||
config: this.config,
|
||||
entityType: 'verb',
|
||||
verbContext: {
|
||||
sourceId: verb.sourceId,
|
||||
targetId: verb.targetId,
|
||||
verbType: verb.type
|
||||
}
|
||||
};
|
||||
// 🟢 INTELLIGENT VERB DISPLAY COMPUTATION
|
||||
const displayFields = {
|
||||
title: await this.computeVerbTitle(context),
|
||||
description: await this.computeVerbDescription(context),
|
||||
type: typeResult.type,
|
||||
tags: await this.computeVerbTags(context),
|
||||
relationship: await this.computeHumanReadableRelationship(context),
|
||||
confidence: typeResult.confidence,
|
||||
reasoning: this.config.debugMode ? typeResult.reasoning : undefined,
|
||||
alternatives: this.config.debugMode ? typeResult.alternatives : undefined,
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
};
|
||||
return displayFields;
|
||||
}
|
||||
/**
|
||||
* Heuristic computation when AI is unavailable
|
||||
* @param data Entity data
|
||||
* @param entityType Type of entity
|
||||
* @param options Additional options
|
||||
* @returns Heuristically computed display fields
|
||||
*/
|
||||
async computeWithHeuristics(data, entityType, options = {}) {
|
||||
// Use basic type detection
|
||||
const detectedType = this.detectTypeHeuristically(data, entityType);
|
||||
const typeResult = {
|
||||
type: detectedType,
|
||||
confidence: 0.6, // Lower confidence for heuristics
|
||||
reasoning: 'Heuristic detection (AI unavailable)',
|
||||
alternatives: []
|
||||
};
|
||||
const context = {
|
||||
data,
|
||||
metadata: data,
|
||||
typeResult: typeResult,
|
||||
config: this.config,
|
||||
entityType
|
||||
};
|
||||
// Use pattern-based field extraction
|
||||
const patterns = getFieldPatterns(entityType, detectedType);
|
||||
return {
|
||||
title: this.extractFieldWithPatterns(data, patterns, 'title') || 'Untitled',
|
||||
description: this.extractFieldWithPatterns(data, patterns, 'description') || 'No description',
|
||||
type: detectedType,
|
||||
tags: this.extractFieldWithPatterns(data, patterns, 'tags') || [],
|
||||
confidence: typeResult.confidence,
|
||||
reasoning: this.config.debugMode ? typeResult.reasoning : undefined,
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Compute intelligent title using AI insights and your field extraction
|
||||
* @param context Computation context with AI results
|
||||
* @returns Computed title
|
||||
*/
|
||||
async computeIntelligentTitle(context) {
|
||||
const { data, typeResult } = context;
|
||||
// 🟢 USE TYPE-SPECIFIC LOGIC based on your NounType taxonomy
|
||||
switch (typeResult?.type) {
|
||||
case NounType.Person:
|
||||
return this.computePersonTitle(data);
|
||||
case NounType.Organization:
|
||||
return this.computeOrganizationTitle(data);
|
||||
case NounType.Project:
|
||||
return this.computeProjectTitle(data);
|
||||
case NounType.Document:
|
||||
return this.computeDocumentTitle(data);
|
||||
default:
|
||||
// 🟢 LEVERAGE YOUR JSON PROCESSING for unknown types
|
||||
return this.extractBestTitle(data, typeResult?.type);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Compute intelligent description using AI insights and context
|
||||
* @param context Computation context
|
||||
* @returns Enhanced description
|
||||
*/
|
||||
async computeIntelligentDescription(context) {
|
||||
const { data, typeResult } = context;
|
||||
// 🟢 USE YOUR EXISTING JSON PROCESSING for vectorization-quality text
|
||||
const priorityFields = getPriorityFields('noun', typeResult?.type);
|
||||
const enhancedText = prepareJsonForVectorization(data, {
|
||||
priorityFields,
|
||||
includeFieldNames: false,
|
||||
maxDepth: 2
|
||||
});
|
||||
// Create context-aware description based on type
|
||||
return this.createContextAwareDescription(data, typeResult, enhancedText);
|
||||
}
|
||||
/**
|
||||
* Compute intelligent tags using type analysis
|
||||
* @param context Computation context
|
||||
* @returns Generated tags array
|
||||
*/
|
||||
async computeIntelligentTags(context) {
|
||||
const { data, typeResult } = context;
|
||||
const tags = [];
|
||||
// Add type-based tag
|
||||
if (typeResult?.type) {
|
||||
tags.push(typeResult.type.toLowerCase());
|
||||
}
|
||||
// Extract explicit tags from data
|
||||
const explicitTags = this.extractExplicitTags(data);
|
||||
tags.push(...explicitTags);
|
||||
// Add semantic tags based on AI analysis
|
||||
if (typeResult && this.typeMatcher) {
|
||||
const semanticTags = this.generateSemanticTags(data, typeResult);
|
||||
tags.push(...semanticTags);
|
||||
}
|
||||
// Remove duplicates and return
|
||||
return [...new Set(tags.filter(Boolean))];
|
||||
}
|
||||
/**
|
||||
* Compute verb title (relationship summary)
|
||||
* @param context Verb computation context
|
||||
* @returns Verb title
|
||||
*/
|
||||
async computeVerbTitle(context) {
|
||||
const { verbContext, typeResult } = context;
|
||||
if (!verbContext)
|
||||
return 'Relationship';
|
||||
const { sourceId, targetId } = verbContext;
|
||||
const relationshipType = typeResult?.type || 'RelatedTo';
|
||||
// Try to get readable names for source and target
|
||||
// This could be enhanced to actually resolve the entities
|
||||
return `${sourceId} ${this.getReadableVerbPhrase(relationshipType)} ${targetId}`;
|
||||
}
|
||||
/**
|
||||
* Create minimal display for error cases
|
||||
* @param data Entity data
|
||||
* @param entityType Entity type
|
||||
* @returns Minimal display fields
|
||||
*/
|
||||
createMinimalDisplay(data, entityType) {
|
||||
return {
|
||||
title: data.name || data.title || data.id || 'Untitled',
|
||||
description: data.description || data.summary || 'No description available',
|
||||
type: entityType === 'noun' ? 'Item' : 'RelatedTo',
|
||||
tags: [],
|
||||
confidence: 0.1, // Very low confidence for fallback
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
};
|
||||
}
|
||||
// Helper methods for specific noun types
|
||||
computePersonTitle(data) {
|
||||
if (data.firstName && data.lastName) {
|
||||
return `${data.firstName} ${data.lastName}`.trim();
|
||||
}
|
||||
return data.name || data.fullName || data.displayName || data.firstName || data.lastName || 'Person';
|
||||
}
|
||||
computeOrganizationTitle(data) {
|
||||
return data.name || data.companyName || data.organizationName || data.title || 'Organization';
|
||||
}
|
||||
computeProjectTitle(data) {
|
||||
return data.name || data.projectName || data.title || data.projectTitle || 'Project';
|
||||
}
|
||||
computeDocumentTitle(data) {
|
||||
return data.title || data.filename || data.name || data.subject || 'Document';
|
||||
}
|
||||
extractBestTitle(data, type) {
|
||||
const titleFields = ['name', 'title', 'displayName', 'label', 'subject', 'heading'];
|
||||
for (const field of titleFields) {
|
||||
if (data[field])
|
||||
return String(data[field]);
|
||||
}
|
||||
return data.id || Object.keys(data)[0] || 'Untitled';
|
||||
}
|
||||
createContextAwareDescription(data, typeResult, enhancedText) {
|
||||
// Start with basic description fields
|
||||
const basicDesc = data.description || data.summary || data.about || data.details;
|
||||
if (basicDesc)
|
||||
return String(basicDesc);
|
||||
// Use enhanced text from JSON processing
|
||||
if (enhancedText && enhancedText.length > 10) {
|
||||
return enhancedText.substring(0, 200) + (enhancedText.length > 200 ? '...' : '');
|
||||
}
|
||||
// Generate from available fields
|
||||
const parts = [];
|
||||
if (data.role)
|
||||
parts.push(data.role);
|
||||
if (data.company)
|
||||
parts.push(`at ${data.company}`);
|
||||
if (data.location)
|
||||
parts.push(`in ${data.location}`);
|
||||
return parts.length > 0 ? parts.join(' ') : 'No description available';
|
||||
}
|
||||
extractExplicitTags(data) {
|
||||
const tagFields = ['tags', 'keywords', 'labels', 'categories', 'topics'];
|
||||
for (const field of tagFields) {
|
||||
if (data[field]) {
|
||||
if (Array.isArray(data[field])) {
|
||||
return data[field].map(String).filter(Boolean);
|
||||
}
|
||||
if (typeof data[field] === 'string') {
|
||||
return data[field].split(/[,;]\s*|\s+/).filter(Boolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
generateSemanticTags(data, typeResult) {
|
||||
const tags = [];
|
||||
// Add confidence-based tags
|
||||
if (typeResult.confidence > 0.9)
|
||||
tags.push('verified');
|
||||
else if (typeResult.confidence < 0.7)
|
||||
tags.push('uncertain');
|
||||
// Add type-specific semantic tags
|
||||
if (data.status)
|
||||
tags.push(String(data.status).toLowerCase());
|
||||
if (data.priority)
|
||||
tags.push(String(data.priority).toLowerCase());
|
||||
if (data.category)
|
||||
tags.push(String(data.category).toLowerCase());
|
||||
return tags;
|
||||
}
|
||||
getReadableVerbPhrase(verbType) {
|
||||
const verbPhrases = {
|
||||
[VerbType.WorksWith]: 'works with',
|
||||
[VerbType.MemberOf]: 'is member of',
|
||||
[VerbType.ReportsTo]: 'reports to',
|
||||
[VerbType.CreatedBy]: 'created by',
|
||||
[VerbType.Owns]: 'owns',
|
||||
[VerbType.LocatedAt]: 'located at',
|
||||
[VerbType.Likes]: 'likes',
|
||||
[VerbType.Follows]: 'follows',
|
||||
[VerbType.Supervises]: 'supervises'
|
||||
};
|
||||
return verbPhrases[verbType] || 'related to';
|
||||
}
|
||||
async computeVerbDescription(context) {
|
||||
const { data, verbContext, typeResult } = context;
|
||||
if (data.description)
|
||||
return String(data.description);
|
||||
// Generate contextual description for relationship
|
||||
if (verbContext && typeResult) {
|
||||
const parts = [];
|
||||
const relationshipPhrase = this.getReadableVerbPhrase(typeResult.type);
|
||||
if (data.role)
|
||||
parts.push(`Role: ${data.role}`);
|
||||
if (data.startDate)
|
||||
parts.push(`Since: ${new Date(data.startDate).toLocaleDateString()}`);
|
||||
if (data.department)
|
||||
parts.push(`Department: ${data.department}`);
|
||||
return parts.length > 0
|
||||
? `${relationshipPhrase} - ${parts.join(', ')}`
|
||||
: `${relationshipPhrase} relationship`;
|
||||
}
|
||||
return 'Relationship';
|
||||
}
|
||||
async computeVerbTags(context) {
|
||||
const { data, typeResult } = context;
|
||||
const tags = ['relationship'];
|
||||
if (typeResult?.type) {
|
||||
tags.push(typeResult.type.toLowerCase());
|
||||
}
|
||||
// Add relationship-specific tags
|
||||
if (data.status)
|
||||
tags.push(String(data.status).toLowerCase());
|
||||
if (data.type)
|
||||
tags.push(String(data.type).toLowerCase());
|
||||
return [...new Set(tags)];
|
||||
}
|
||||
async computeHumanReadableRelationship(context) {
|
||||
const { verbContext, typeResult } = context;
|
||||
if (!verbContext || !typeResult)
|
||||
return 'Related';
|
||||
const { sourceId, targetId } = verbContext;
|
||||
const phrase = this.getReadableVerbPhrase(typeResult.type);
|
||||
return `${sourceId} ${phrase} ${targetId}`;
|
||||
}
|
||||
detectTypeHeuristically(data, entityType) {
|
||||
if (entityType === 'verb')
|
||||
return VerbType.RelatedTo;
|
||||
// Basic heuristics for noun types
|
||||
if (data.firstName || data.lastName || data.email)
|
||||
return NounType.Person;
|
||||
if (data.companyName || data.organization)
|
||||
return NounType.Organization;
|
||||
if (data.filename || data.fileType)
|
||||
return NounType.Document;
|
||||
if (data.projectName || data.initiative)
|
||||
return NounType.Project;
|
||||
if (data.taskName || data.todo)
|
||||
return NounType.Task;
|
||||
if (data.startDate || data.endDate)
|
||||
return NounType.Event;
|
||||
return 'Item'; // Generic fallback
|
||||
}
|
||||
extractFieldWithPatterns(data, patterns, fieldType) {
|
||||
const relevantPatterns = patterns.filter(p => p.displayField === fieldType);
|
||||
for (const pattern of relevantPatterns) {
|
||||
for (const field of pattern.fields) {
|
||||
if (data[field]) {
|
||||
return pattern.transform ? pattern.transform(data[field], { data, config: this.config }) : data[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Shutdown the computation engine
|
||||
*/
|
||||
async shutdown() {
|
||||
// Cleanup if needed
|
||||
this.typeMatcher = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=intelligentComputation.js.map
|
||||
File diff suppressed because one or more lines are too long
203
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/types.d.ts
vendored
Normal file
203
.recovery-workspace/dist-backup-20250910-141917/augmentations/display/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Type Definitions
|
||||
*
|
||||
* Clean TypeScript interfaces for the display augmentation system
|
||||
*/
|
||||
import type { VectorDocument, GraphVerb } from '../../coreTypes.js';
|
||||
/**
|
||||
* Configuration interface for the Universal Display Augmentation
|
||||
*/
|
||||
export interface DisplayConfig {
|
||||
/** Enable/disable the augmentation */
|
||||
enabled: boolean;
|
||||
/** LRU cache size for computed display fields */
|
||||
cacheSize: number;
|
||||
/** Use lazy computation (recommended for performance) */
|
||||
lazyComputation: boolean;
|
||||
/** Batch processing size for multiple requests */
|
||||
batchSize: number;
|
||||
/** Minimum confidence threshold for AI type detection */
|
||||
confidenceThreshold: number;
|
||||
/** Custom field mappings (userField -> displayField) */
|
||||
customFieldMappings: Record<string, string>;
|
||||
/** Type-specific priority fields for intelligent detection */
|
||||
priorityFields: Record<string, string[]>;
|
||||
/** Enable debug mode with reasoning output */
|
||||
debugMode: boolean;
|
||||
}
|
||||
/**
|
||||
* Computed display fields for any noun or verb
|
||||
*/
|
||||
export interface ComputedDisplayFields {
|
||||
/** Primary display name (AI-detected best field combination) */
|
||||
title: string;
|
||||
/** Enhanced description with context awareness */
|
||||
description: string;
|
||||
/** Human-readable type name */
|
||||
type: string;
|
||||
/** Generated display tags for categorization */
|
||||
tags: string[];
|
||||
/** For verbs: human-readable relationship description */
|
||||
relationship?: string;
|
||||
/** AI confidence score (0-1) */
|
||||
confidence: number;
|
||||
/** Explanation of type detection reasoning (debug mode) */
|
||||
reasoning?: string;
|
||||
/** Alternative type suggestions with confidence scores */
|
||||
alternatives?: Array<{
|
||||
type: string;
|
||||
confidence: number;
|
||||
}>;
|
||||
/** Timestamp when fields were computed */
|
||||
computedAt: number;
|
||||
/** Version of augmentation that computed these fields */
|
||||
version: string;
|
||||
}
|
||||
/**
|
||||
* Cache entry for computed display fields
|
||||
*/
|
||||
export interface DisplayCacheEntry {
|
||||
fields: ComputedDisplayFields;
|
||||
lastAccessed: number;
|
||||
accessCount: number;
|
||||
}
|
||||
/**
|
||||
* Field computation context passed to computation functions
|
||||
*/
|
||||
export interface FieldComputationContext {
|
||||
/** The original data object */
|
||||
data: any;
|
||||
/** Metadata associated with the object */
|
||||
metadata: any;
|
||||
/** Type detection result from AI */
|
||||
typeResult?: TypeMatchResult;
|
||||
/** Display configuration */
|
||||
config: DisplayConfig;
|
||||
/** Whether this is a noun or verb */
|
||||
entityType: 'noun' | 'verb';
|
||||
/** For verbs: source and target information */
|
||||
verbContext?: {
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
verbType?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Type matching result from BrainyTypes
|
||||
*/
|
||||
export interface TypeMatchResult {
|
||||
type: string;
|
||||
confidence: number;
|
||||
reasoning: string;
|
||||
alternatives: Array<{
|
||||
type: string;
|
||||
confidence: number;
|
||||
}>;
|
||||
}
|
||||
/**
|
||||
* Enhanced VectorDocument with display capabilities
|
||||
*/
|
||||
export interface EnhancedVectorDocument<T = any> extends VectorDocument<T> {
|
||||
/**
|
||||
* Get computed display field(s)
|
||||
* @param field Optional specific field name
|
||||
* @returns Single field value or all display fields
|
||||
*/
|
||||
getDisplay(): Promise<ComputedDisplayFields>;
|
||||
getDisplay(field: keyof ComputedDisplayFields): Promise<any>;
|
||||
/**
|
||||
* Get available fields for a specific augmentation namespace
|
||||
* @param namespace The augmentation namespace (e.g., 'display')
|
||||
* @returns Array of available field names
|
||||
*/
|
||||
getAvailableFields(namespace: string): string[];
|
||||
/**
|
||||
* Get available augmentation namespaces
|
||||
* @returns Array of available augmentation names
|
||||
*/
|
||||
getAvailableAugmentations(): string[];
|
||||
/**
|
||||
* Debug exploration of all computed fields
|
||||
*/
|
||||
explore(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Enhanced GraphVerb with display capabilities
|
||||
*/
|
||||
export interface EnhancedGraphVerb extends GraphVerb {
|
||||
/**
|
||||
* Get computed display field(s) for relationships
|
||||
* @param field Optional specific field name
|
||||
* @returns Single field value or all display fields
|
||||
*/
|
||||
getDisplay(): Promise<ComputedDisplayFields>;
|
||||
getDisplay(field: keyof ComputedDisplayFields): Promise<any>;
|
||||
/**
|
||||
* Get available fields for a specific augmentation namespace
|
||||
* @param namespace The augmentation namespace (e.g., 'display')
|
||||
* @returns Array of available field names
|
||||
*/
|
||||
getAvailableFields(namespace: string): string[];
|
||||
}
|
||||
/**
|
||||
* Batch computation request for performance optimization
|
||||
*/
|
||||
export interface BatchComputationRequest {
|
||||
id: string;
|
||||
data: any;
|
||||
metadata: any;
|
||||
entityType: 'noun' | 'verb';
|
||||
verbContext?: {
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
verbType?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Batch computation result
|
||||
*/
|
||||
export interface BatchComputationResult {
|
||||
id: string;
|
||||
fields: ComputedDisplayFields;
|
||||
error?: string;
|
||||
}
|
||||
/**
|
||||
* Field pattern for intelligent field detection
|
||||
*/
|
||||
export interface FieldPattern {
|
||||
/** Field names that match this pattern */
|
||||
fields: string[];
|
||||
/** Target display field name */
|
||||
displayField: keyof ComputedDisplayFields;
|
||||
/** Confidence score for this pattern match */
|
||||
confidence: number;
|
||||
/** Optional: specific noun/verb types this applies to */
|
||||
applicableTypes?: string[];
|
||||
/** Optional: transformation function for the field value */
|
||||
transform?: (value: any, context: FieldComputationContext) => string;
|
||||
}
|
||||
/**
|
||||
* Statistics for the display augmentation
|
||||
*/
|
||||
export interface DisplayAugmentationStats {
|
||||
/** Total number of computations performed */
|
||||
totalComputations: number;
|
||||
/** Cache hit ratio */
|
||||
cacheHitRatio: number;
|
||||
/** Average computation time in milliseconds */
|
||||
averageComputationTime: number;
|
||||
/** Type detection accuracy (when ground truth available) */
|
||||
typeDetectionAccuracy?: number;
|
||||
/** Most commonly detected types */
|
||||
commonTypes: Array<{
|
||||
type: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
}>;
|
||||
/** Performance metrics */
|
||||
performance: {
|
||||
fastestComputation: number;
|
||||
slowestComputation: number;
|
||||
totalComputationTime: number;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Type Definitions
|
||||
*
|
||||
* Clean TypeScript interfaces for the display augmentation system
|
||||
*/
|
||||
export {};
|
||||
//# sourceMappingURL=types.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/augmentations/display/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
||||
130
.recovery-workspace/dist-backup-20250910-141917/augmentations/entityRegistryAugmentation.d.ts
vendored
Normal file
130
.recovery-workspace/dist-backup-20250910-141917/augmentations/entityRegistryAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* Entity Registry Augmentation
|
||||
* Fast external-ID to internal-UUID mapping for streaming data processing
|
||||
* Works in write-only mode for high-performance deduplication
|
||||
*/
|
||||
import { BaseAugmentation, AugmentationContext } from './brainyAugmentation.js';
|
||||
export interface EntityRegistryConfig {
|
||||
/**
|
||||
* Maximum number of entries to keep in memory cache
|
||||
* Default: 100,000 entries
|
||||
*/
|
||||
maxCacheSize?: number;
|
||||
/**
|
||||
* Time to live for cache entries in milliseconds
|
||||
* Default: 300,000 (5 minutes)
|
||||
*/
|
||||
cacheTTL?: number;
|
||||
/**
|
||||
* Fields to index for fast lookup
|
||||
* Default: ['did', 'handle', 'uri', 'id', 'external_id']
|
||||
*/
|
||||
indexedFields?: string[];
|
||||
/**
|
||||
* Persistence strategy
|
||||
* memory: Keep only in memory (fast, but lost on restart)
|
||||
* storage: Persist to storage (survives restarts)
|
||||
* hybrid: Memory + periodic storage sync
|
||||
*/
|
||||
persistence?: 'memory' | 'storage' | 'hybrid';
|
||||
/**
|
||||
* How often to sync memory cache to storage (hybrid mode)
|
||||
* Default: 30000 (30 seconds)
|
||||
*/
|
||||
syncInterval?: number;
|
||||
}
|
||||
export interface EntityMapping {
|
||||
externalId: string;
|
||||
field: string;
|
||||
brainyId: string;
|
||||
nounType: string;
|
||||
lastAccessed: number;
|
||||
metadata?: any;
|
||||
}
|
||||
/**
|
||||
* High-performance entity registry for external ID to Brainy UUID mapping
|
||||
* Optimized for streaming data scenarios like Bluesky firehose processing
|
||||
*/
|
||||
export declare class EntityRegistryAugmentation extends BaseAugmentation {
|
||||
readonly metadata: "readonly";
|
||||
readonly name = "entity-registry";
|
||||
readonly description = "Fast external-ID to internal-UUID mapping for streaming data";
|
||||
readonly timing: 'before' | 'after' | 'around' | 'replace';
|
||||
readonly operations: ("add" | "addNoun" | "addVerb")[];
|
||||
readonly priority = 90;
|
||||
protected config: Required<EntityRegistryConfig>;
|
||||
private memoryIndex;
|
||||
private fieldIndices;
|
||||
private syncTimer?;
|
||||
private brain?;
|
||||
private storage?;
|
||||
private cacheHits;
|
||||
private cacheMisses;
|
||||
constructor(config?: EntityRegistryConfig);
|
||||
initialize(context: AugmentationContext): Promise<void>;
|
||||
shutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute the augmentation
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Register a new entity mapping
|
||||
*/
|
||||
registerEntity(brainyId: string, metadata: any, nounType: string): Promise<void>;
|
||||
/**
|
||||
* Fast lookup: external ID → Brainy UUID
|
||||
* Works in write-only mode without search indexes
|
||||
*/
|
||||
lookupEntity(field: string, value: string): Promise<string | null>;
|
||||
/**
|
||||
* Batch lookup for multiple external IDs
|
||||
*/
|
||||
lookupBatch(lookups: Array<{
|
||||
field: string;
|
||||
value: string;
|
||||
}>): Promise<Map<string, string | null>>;
|
||||
/**
|
||||
* Check if entity exists (faster than lookupEntity for existence checks)
|
||||
*/
|
||||
hasEntity(field: string, value: string): Promise<boolean>;
|
||||
/**
|
||||
* Get all entities by field (e.g., all DIDs)
|
||||
*/
|
||||
getEntitiesByField(field: string): Promise<string[]>;
|
||||
/**
|
||||
* Get registry statistics
|
||||
*/
|
||||
getStats(): {
|
||||
totalMappings: number;
|
||||
fieldCounts: Record<string, number>;
|
||||
cacheHitRate: number;
|
||||
memoryUsage: number;
|
||||
};
|
||||
/**
|
||||
* Clear all cached mappings
|
||||
*/
|
||||
clearCache(): Promise<void>;
|
||||
/**
|
||||
* Check if an ID looks like it could be an external ID for a specific field
|
||||
*/
|
||||
private looksLikeExternalId;
|
||||
private extractFieldValue;
|
||||
private evictOldEntries;
|
||||
private loadFromStorage;
|
||||
private syncToStorage;
|
||||
private loadFromStorageByField;
|
||||
private loadBatchFromStorage;
|
||||
private estimateMemoryUsage;
|
||||
}
|
||||
export declare class AutoRegisterEntitiesAugmentation extends BaseAugmentation {
|
||||
readonly metadata: "readonly";
|
||||
readonly name = "auto-register-entities";
|
||||
readonly description = "Automatically register entities in the registry when added";
|
||||
readonly timing: 'before' | 'after' | 'around' | 'replace';
|
||||
readonly operations: ("add" | "addNoun" | "addVerb")[];
|
||||
readonly priority = 85;
|
||||
private registry?;
|
||||
private brain?;
|
||||
initialize(context: AugmentationContext): Promise<void>;
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
}
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
/**
|
||||
* Entity Registry Augmentation
|
||||
* Fast external-ID to internal-UUID mapping for streaming data processing
|
||||
* Works in write-only mode for high-performance deduplication
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
/**
|
||||
* High-performance entity registry for external ID to Brainy UUID mapping
|
||||
* Optimized for streaming data scenarios like Bluesky firehose processing
|
||||
*/
|
||||
export class EntityRegistryAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.metadata = 'readonly'; // Reads metadata to register entities
|
||||
this.name = 'entity-registry';
|
||||
this.description = 'Fast external-ID to internal-UUID mapping for streaming data';
|
||||
this.timing = 'before';
|
||||
this.operations = ['add', 'addNoun', 'addVerb'];
|
||||
this.priority = 90; // High priority for entity registration
|
||||
this.memoryIndex = new Map();
|
||||
this.fieldIndices = new Map(); // field -> value -> brainyId
|
||||
this.cacheHits = 0;
|
||||
this.cacheMisses = 0;
|
||||
this.config = {
|
||||
maxCacheSize: config.maxCacheSize ?? 100000,
|
||||
cacheTTL: config.cacheTTL ?? 300000, // 5 minutes
|
||||
indexedFields: config.indexedFields ?? ['did', 'handle', 'uri', 'id', 'external_id'],
|
||||
persistence: config.persistence ?? 'hybrid',
|
||||
syncInterval: config.syncInterval ?? 30000 // 30 seconds
|
||||
};
|
||||
// Initialize field indices
|
||||
for (const field of this.config.indexedFields) {
|
||||
this.fieldIndices.set(field, new Map());
|
||||
}
|
||||
}
|
||||
async initialize(context) {
|
||||
this.brain = context.brain;
|
||||
this.storage = context.storage;
|
||||
// Load existing mappings from storage
|
||||
if (this.config.persistence === 'storage' || this.config.persistence === 'hybrid') {
|
||||
await this.loadFromStorage();
|
||||
}
|
||||
// Start sync timer for hybrid mode
|
||||
if (this.config.persistence === 'hybrid') {
|
||||
this.syncTimer = setInterval(() => {
|
||||
this.syncToStorage().catch(console.error);
|
||||
}, this.config.syncInterval);
|
||||
}
|
||||
console.log(`🔍 EntityRegistry initialized: ${this.memoryIndex.size} cached mappings`);
|
||||
}
|
||||
async shutdown() {
|
||||
// Final sync before shutdown
|
||||
if (this.config.persistence === 'storage' || this.config.persistence === 'hybrid') {
|
||||
await this.syncToStorage();
|
||||
}
|
||||
if (this.syncTimer) {
|
||||
clearInterval(this.syncTimer);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Execute the augmentation
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
console.log(`🔍 [EntityRegistry] execute called: operation=${operation}`);
|
||||
// For add operations, check for duplicates first
|
||||
if (operation === 'add' || operation === 'addNoun') {
|
||||
const metadata = params.metadata || {};
|
||||
// Check if entity already exists
|
||||
for (const field of this.config.indexedFields) {
|
||||
const value = this.extractFieldValue(metadata, field);
|
||||
if (value) {
|
||||
const existingId = await this.lookupEntity(field, value);
|
||||
if (existingId) {
|
||||
// Entity already exists, return the existing one
|
||||
console.log(`🔍 Duplicate detected: ${field}:${value} → ${existingId}`);
|
||||
return { id: existingId, duplicate: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// For addVerb operations, resolve external IDs to internal UUIDs
|
||||
if (operation === 'addVerb') {
|
||||
const sourceId = params.sourceId;
|
||||
const targetId = params.targetId;
|
||||
// Try to resolve source and target IDs if they look like external IDs
|
||||
for (const field of this.config.indexedFields) {
|
||||
// Check if sourceId matches an external ID pattern
|
||||
if (typeof sourceId === 'string' && this.looksLikeExternalId(sourceId, field)) {
|
||||
const resolvedSourceId = await this.lookupEntity(field, sourceId);
|
||||
if (resolvedSourceId) {
|
||||
console.log(`🔍 [EntityRegistry] Resolved source: ${sourceId} → ${resolvedSourceId}`);
|
||||
params.sourceId = resolvedSourceId;
|
||||
}
|
||||
}
|
||||
// Check if targetId matches an external ID pattern
|
||||
if (typeof targetId === 'string' && this.looksLikeExternalId(targetId, field)) {
|
||||
const resolvedTargetId = await this.lookupEntity(field, targetId);
|
||||
if (resolvedTargetId) {
|
||||
console.log(`🔍 [EntityRegistry] Resolved target: ${targetId} → ${resolvedTargetId}`);
|
||||
params.targetId = resolvedTargetId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Proceed with the operation
|
||||
const result = await next();
|
||||
// Register the entity after successful add
|
||||
if ((operation === 'add' || operation === 'addNoun' || operation === 'addVerb') && result) {
|
||||
// Handle both formats: string UUID or object with id property
|
||||
const brainyId = typeof result === 'string' ? result : result.id;
|
||||
if (brainyId) {
|
||||
const metadata = params.metadata || {};
|
||||
const nounType = params.nounType || 'default';
|
||||
console.log(`🔍 [EntityRegistry] Registering entity: ${brainyId}`);
|
||||
await this.registerEntity(brainyId, metadata, nounType);
|
||||
console.log(`✅ [EntityRegistry] Entity registered successfully`);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Register a new entity mapping
|
||||
*/
|
||||
async registerEntity(brainyId, metadata, nounType) {
|
||||
const now = Date.now();
|
||||
// Extract indexed fields from metadata
|
||||
for (const field of this.config.indexedFields) {
|
||||
const value = this.extractFieldValue(metadata, field);
|
||||
if (value) {
|
||||
const key = `${field}:${value}`;
|
||||
// Add to memory index
|
||||
const mapping = {
|
||||
externalId: value,
|
||||
field,
|
||||
brainyId,
|
||||
nounType,
|
||||
lastAccessed: now,
|
||||
metadata
|
||||
};
|
||||
this.memoryIndex.set(key, mapping);
|
||||
// Add to field-specific index
|
||||
const fieldIndex = this.fieldIndices.get(field);
|
||||
if (fieldIndex) {
|
||||
fieldIndex.set(value, brainyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Enforce cache size limit (LRU eviction)
|
||||
await this.evictOldEntries();
|
||||
}
|
||||
/**
|
||||
* Fast lookup: external ID → Brainy UUID
|
||||
* Works in write-only mode without search indexes
|
||||
*/
|
||||
async lookupEntity(field, value) {
|
||||
const key = `${field}:${value}`;
|
||||
const cached = this.memoryIndex.get(key);
|
||||
if (cached) {
|
||||
// Update last accessed time
|
||||
cached.lastAccessed = Date.now();
|
||||
this.cacheHits++;
|
||||
return cached.brainyId;
|
||||
}
|
||||
this.cacheMisses++;
|
||||
// If not in cache and using storage persistence, try loading from storage
|
||||
if (this.config.persistence === 'storage' || this.config.persistence === 'hybrid') {
|
||||
const stored = await this.loadFromStorageByField(field, value);
|
||||
if (stored) {
|
||||
// Add to memory cache
|
||||
this.memoryIndex.set(key, stored);
|
||||
const fieldIndex = this.fieldIndices.get(field);
|
||||
if (fieldIndex) {
|
||||
fieldIndex.set(value, stored.brainyId);
|
||||
}
|
||||
return stored.brainyId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Batch lookup for multiple external IDs
|
||||
*/
|
||||
async lookupBatch(lookups) {
|
||||
const results = new Map();
|
||||
const missingKeys = [];
|
||||
// Check memory cache first
|
||||
for (const lookup of lookups) {
|
||||
const key = `${lookup.field}:${lookup.value}`;
|
||||
const cached = this.memoryIndex.get(key);
|
||||
if (cached) {
|
||||
cached.lastAccessed = Date.now();
|
||||
results.set(key, cached.brainyId);
|
||||
}
|
||||
else {
|
||||
missingKeys.push({ ...lookup, key });
|
||||
results.set(key, null);
|
||||
}
|
||||
}
|
||||
// Batch load missing keys from storage
|
||||
if (missingKeys.length > 0 && (this.config.persistence === 'storage' || this.config.persistence === 'hybrid')) {
|
||||
const stored = await this.loadBatchFromStorage(missingKeys);
|
||||
for (const [key, mapping] of stored) {
|
||||
if (mapping) {
|
||||
// Add to memory cache
|
||||
this.memoryIndex.set(key, mapping);
|
||||
const fieldIndex = this.fieldIndices.get(mapping.field);
|
||||
if (fieldIndex) {
|
||||
fieldIndex.set(mapping.externalId, mapping.brainyId);
|
||||
}
|
||||
results.set(key, mapping.brainyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
/**
|
||||
* Check if entity exists (faster than lookupEntity for existence checks)
|
||||
*/
|
||||
async hasEntity(field, value) {
|
||||
const fieldIndex = this.fieldIndices.get(field);
|
||||
if (fieldIndex && fieldIndex.has(value)) {
|
||||
return true;
|
||||
}
|
||||
return (await this.lookupEntity(field, value)) !== null;
|
||||
}
|
||||
/**
|
||||
* Get all entities by field (e.g., all DIDs)
|
||||
*/
|
||||
async getEntitiesByField(field) {
|
||||
const fieldIndex = this.fieldIndices.get(field);
|
||||
return fieldIndex ? Array.from(fieldIndex.keys()) : [];
|
||||
}
|
||||
/**
|
||||
* Get registry statistics
|
||||
*/
|
||||
getStats() {
|
||||
const fieldCounts = {};
|
||||
for (const [field, index] of this.fieldIndices) {
|
||||
fieldCounts[field] = index.size;
|
||||
}
|
||||
return {
|
||||
totalMappings: this.memoryIndex.size,
|
||||
fieldCounts,
|
||||
cacheHitRate: this.cacheHits > 0 ? this.cacheHits / (this.cacheHits + this.cacheMisses) : 0,
|
||||
memoryUsage: this.estimateMemoryUsage()
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Clear all cached mappings
|
||||
*/
|
||||
async clearCache() {
|
||||
this.memoryIndex.clear();
|
||||
for (const fieldIndex of this.fieldIndices.values()) {
|
||||
fieldIndex.clear();
|
||||
}
|
||||
}
|
||||
// Private helper methods
|
||||
/**
|
||||
* Check if an ID looks like it could be an external ID for a specific field
|
||||
*/
|
||||
looksLikeExternalId(id, field) {
|
||||
// Basic heuristics to detect external ID patterns
|
||||
switch (field) {
|
||||
case 'did':
|
||||
return id.startsWith('did:');
|
||||
case 'handle':
|
||||
return id.includes('.') && (id.includes('bsky') || id.includes('social'));
|
||||
case 'external_id':
|
||||
return !id.match(/^[a-f0-9-]{36}$/i); // Not a UUID
|
||||
case 'uri':
|
||||
return id.startsWith('http') || id.startsWith('at://');
|
||||
case 'id':
|
||||
return !id.match(/^[a-f0-9-]{36}$/i); // Not a UUID
|
||||
default:
|
||||
// For custom fields, assume non-UUID strings might be external IDs
|
||||
return typeof id === 'string' && id.length > 3 && !id.match(/^[a-f0-9-]{36}$/i);
|
||||
}
|
||||
}
|
||||
extractFieldValue(metadata, field) {
|
||||
if (!metadata)
|
||||
return null;
|
||||
// Support nested field access (e.g., "author.did")
|
||||
const parts = field.split('.');
|
||||
let value = metadata;
|
||||
for (const part of parts) {
|
||||
value = value?.[part];
|
||||
if (value === undefined || value === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return typeof value === 'string' ? value : String(value);
|
||||
}
|
||||
async evictOldEntries() {
|
||||
if (this.memoryIndex.size <= this.config.maxCacheSize) {
|
||||
return;
|
||||
}
|
||||
// Sort by last accessed time and remove oldest entries
|
||||
const entries = Array.from(this.memoryIndex.entries());
|
||||
entries.sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed);
|
||||
const toRemove = entries.slice(0, entries.length - this.config.maxCacheSize);
|
||||
for (const [key, mapping] of toRemove) {
|
||||
this.memoryIndex.delete(key);
|
||||
const fieldIndex = this.fieldIndices.get(mapping.field);
|
||||
if (fieldIndex) {
|
||||
fieldIndex.delete(mapping.externalId);
|
||||
}
|
||||
}
|
||||
}
|
||||
async loadFromStorage() {
|
||||
if (!this.brain)
|
||||
return;
|
||||
try {
|
||||
// Load registry data from a special storage location
|
||||
const registryData = await this.brain.storage?.getMetadata('__entity_registry__');
|
||||
if (registryData && registryData.mappings) {
|
||||
for (const mapping of registryData.mappings) {
|
||||
const key = `${mapping.field}:${mapping.externalId}`;
|
||||
this.memoryIndex.set(key, mapping);
|
||||
const fieldIndex = this.fieldIndices.get(mapping.field);
|
||||
if (fieldIndex) {
|
||||
fieldIndex.set(mapping.externalId, mapping.brainyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to load entity registry from storage:', error);
|
||||
}
|
||||
}
|
||||
async syncToStorage() {
|
||||
if (!this.brain)
|
||||
return;
|
||||
try {
|
||||
const mappings = Array.from(this.memoryIndex.values());
|
||||
await this.brain.storage?.saveMetadata('__entity_registry__', {
|
||||
version: 1,
|
||||
lastSync: Date.now(),
|
||||
mappings
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to sync entity registry to storage:', error);
|
||||
}
|
||||
}
|
||||
async loadFromStorageByField(field, value) {
|
||||
// For now, this would require a full load. In production, you'd want
|
||||
// a more sophisticated storage index system
|
||||
return null;
|
||||
}
|
||||
async loadBatchFromStorage(keys) {
|
||||
// For now, return empty. In production, implement batch storage lookup
|
||||
return new Map();
|
||||
}
|
||||
estimateMemoryUsage() {
|
||||
// Rough estimate: 200 bytes per mapping on average
|
||||
return this.memoryIndex.size * 200;
|
||||
}
|
||||
}
|
||||
// Hook into Brainy's add operations to automatically register entities
|
||||
export class AutoRegisterEntitiesAugmentation extends BaseAugmentation {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.metadata = 'readonly'; // Reads metadata for auto-registration
|
||||
this.name = 'auto-register-entities';
|
||||
this.description = 'Automatically register entities in the registry when added';
|
||||
this.timing = 'after';
|
||||
this.operations = ['add', 'addNoun', 'addVerb'];
|
||||
this.priority = 85; // After entity registry
|
||||
}
|
||||
async initialize(context) {
|
||||
this.brain = context.brain;
|
||||
// Find the entity registry augmentation from the registry
|
||||
this.registry = this.brain?.augmentations?.augmentations?.find((aug) => aug instanceof EntityRegistryAugmentation);
|
||||
}
|
||||
async execute(operation, params, next) {
|
||||
const result = await next();
|
||||
// After successful add, register the entity
|
||||
if ((operation === 'add' || operation === 'addNoun' || operation === 'addVerb') && result) {
|
||||
if (this.registry) {
|
||||
// Handle both formats: string UUID or object with id property
|
||||
const brainyId = typeof result === 'string' ? result : result.id;
|
||||
if (brainyId) {
|
||||
const metadata = params.metadata || {};
|
||||
const nounType = params.nounType || 'default';
|
||||
await this.registry.registerEntity(brainyId, metadata, nounType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=entityRegistryAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
120
.recovery-workspace/dist-backup-20250910-141917/augmentations/indexAugmentation.d.ts
vendored
Normal file
120
.recovery-workspace/dist-backup-20250910-141917/augmentations/indexAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* Index Augmentation - Optional Metadata Indexing
|
||||
*
|
||||
* Replaces the hardcoded MetadataIndex in Brainy with an optional augmentation.
|
||||
* Provides O(1) metadata filtering and field lookups.
|
||||
*
|
||||
* Zero-config: Automatically enabled for better search performance
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { MetadataIndexManager } from '../utils/metadataIndex.js';
|
||||
export interface IndexConfig {
|
||||
enabled?: boolean;
|
||||
maxFieldValues?: number;
|
||||
maxIndexSize?: number;
|
||||
autoRebuild?: boolean;
|
||||
rebuildThreshold?: number;
|
||||
flushInterval?: number;
|
||||
}
|
||||
/**
|
||||
* IndexAugmentation - Makes metadata indexing optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - O(1) metadata field lookups
|
||||
* - Fast pre-filtering for searches
|
||||
* - Automatic index maintenance
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export declare class IndexAugmentation extends BaseAugmentation {
|
||||
readonly metadata: "readonly";
|
||||
readonly name = "index";
|
||||
readonly timing: "after";
|
||||
operations: ("add" | "update" | "updateMetadata" | "delete" | "clear" | "all")[];
|
||||
readonly priority = 60;
|
||||
readonly category: "core";
|
||||
readonly description = "Fast metadata field indexing for O(1) filtering and lookups";
|
||||
private metadataIndex;
|
||||
protected config: IndexConfig;
|
||||
private flushTimer;
|
||||
constructor(config?: IndexConfig);
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute augmentation - maintain index on data operations
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Handle add operation - index new metadata
|
||||
*/
|
||||
private handleAdd;
|
||||
/**
|
||||
* Handle update operation - reindex metadata
|
||||
*/
|
||||
private handleUpdate;
|
||||
/**
|
||||
* Handle delete operation - remove from index
|
||||
*/
|
||||
private handleDelete;
|
||||
/**
|
||||
* Handle clear operation - clear index
|
||||
*/
|
||||
private handleClear;
|
||||
/**
|
||||
* Start periodic flush timer
|
||||
*/
|
||||
private startFlushTimer;
|
||||
/**
|
||||
* Get IDs that match metadata filter (for pre-filtering)
|
||||
*/
|
||||
getIdsForFilter(filter: Record<string, any>): Promise<string[]>;
|
||||
/**
|
||||
* Get available values for a field
|
||||
*/
|
||||
getFilterValues(field: string): Promise<any[]>;
|
||||
/**
|
||||
* Get all indexed fields
|
||||
*/
|
||||
getFilterFields(): Promise<string[]>;
|
||||
/**
|
||||
* Get index statistics
|
||||
*/
|
||||
getStats(): Promise<{
|
||||
enabled: boolean;
|
||||
totalEntries: number;
|
||||
fieldsIndexed: never[];
|
||||
memoryUsage: number;
|
||||
} | {
|
||||
totalEntries: number;
|
||||
totalIds: number;
|
||||
fieldsIndexed: string[];
|
||||
lastRebuild: number;
|
||||
indexSize: number;
|
||||
enabled: boolean;
|
||||
memoryUsage?: undefined;
|
||||
}>;
|
||||
/**
|
||||
* Rebuild the index from storage
|
||||
*/
|
||||
rebuild(): Promise<void>;
|
||||
/**
|
||||
* Flush index to storage
|
||||
*/
|
||||
flush(): Promise<void>;
|
||||
/**
|
||||
* Add entry to index (public method for direct access)
|
||||
*/
|
||||
addToIndex(id: string, metadata: Record<string, any>): Promise<void>;
|
||||
/**
|
||||
* Remove entry from index (public method for direct access)
|
||||
*/
|
||||
removeFromIndex(id: string, metadata: Record<string, any>): Promise<void>;
|
||||
/**
|
||||
* Get the underlying MetadataIndexManager instance
|
||||
*/
|
||||
getMetadataIndex(): MetadataIndexManager | null;
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config index augmentation
|
||||
*/
|
||||
export declare function createIndexAugmentation(config?: IndexConfig): IndexAugmentation;
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
/**
|
||||
* Index Augmentation - Optional Metadata Indexing
|
||||
*
|
||||
* Replaces the hardcoded MetadataIndex in Brainy with an optional augmentation.
|
||||
* Provides O(1) metadata filtering and field lookups.
|
||||
*
|
||||
* Zero-config: Automatically enabled for better search performance
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { MetadataIndexManager } from '../utils/metadataIndex.js';
|
||||
/**
|
||||
* IndexAugmentation - Makes metadata indexing optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - O(1) metadata field lookups
|
||||
* - Fast pre-filtering for searches
|
||||
* - Automatic index maintenance
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export class IndexAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.metadata = 'readonly'; // Reads metadata to build indexes
|
||||
this.name = 'index';
|
||||
this.timing = 'after';
|
||||
this.operations = ['add', 'update', 'updateMetadata', 'delete', 'clear', 'all'];
|
||||
this.priority = 60; // Run after data operations
|
||||
// Augmentation metadata
|
||||
this.category = 'core';
|
||||
this.description = 'Fast metadata field indexing for O(1) filtering and lookups';
|
||||
this.metadataIndex = null;
|
||||
this.flushTimer = null;
|
||||
this.config = {
|
||||
enabled: true,
|
||||
maxFieldValues: 1000,
|
||||
autoRebuild: true,
|
||||
rebuildThreshold: 0.3, // Rebuild if 30% inconsistent
|
||||
flushInterval: 30000, // Flush every 30 seconds
|
||||
...config
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('Index augmentation disabled by configuration');
|
||||
return;
|
||||
}
|
||||
// Get storage from context
|
||||
const storage = this.context?.storage;
|
||||
if (!storage) {
|
||||
this.log('No storage available, index augmentation inactive', 'warn');
|
||||
return;
|
||||
}
|
||||
// Initialize metadata index
|
||||
this.metadataIndex = new MetadataIndexManager(storage, {
|
||||
maxIndexSize: this.config.maxIndexSize || 10000
|
||||
});
|
||||
// Check if we need to rebuild
|
||||
if (this.config.autoRebuild) {
|
||||
const stats = await this.metadataIndex.getStats();
|
||||
if (stats.totalEntries === 0) {
|
||||
// Check if storage has data but index is empty
|
||||
try {
|
||||
const storageStats = await storage.getStatistics?.();
|
||||
if (storageStats && storageStats.totalNouns > 0) {
|
||||
this.log('Rebuilding metadata index for existing data...');
|
||||
await this.metadataIndex.rebuild();
|
||||
const newStats = await this.metadataIndex.getStats();
|
||||
this.log(`Index rebuilt: ${newStats.totalEntries} entries, ${newStats.fieldsIndexed.length} fields`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.log('Could not check storage statistics', 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Start flush timer
|
||||
if (this.config.flushInterval && this.config.flushInterval > 0) {
|
||||
this.startFlushTimer();
|
||||
}
|
||||
this.log('Index augmentation initialized');
|
||||
}
|
||||
async onShutdown() {
|
||||
// Stop flush timer
|
||||
if (this.flushTimer) {
|
||||
clearInterval(this.flushTimer);
|
||||
this.flushTimer = null;
|
||||
}
|
||||
// Flush index one last time
|
||||
if (this.metadataIndex) {
|
||||
try {
|
||||
await this.metadataIndex.flush();
|
||||
}
|
||||
catch (error) {
|
||||
this.log('Error flushing index during shutdown', 'warn');
|
||||
}
|
||||
this.metadataIndex = null;
|
||||
}
|
||||
this.log('Index augmentation shut down');
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - maintain index on data operations
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// Execute the operation first
|
||||
const result = await next();
|
||||
// If index is disabled, just return
|
||||
if (!this.metadataIndex || !this.config.enabled) {
|
||||
return result;
|
||||
}
|
||||
// Handle index updates after operation completes
|
||||
switch (operation) {
|
||||
case 'add':
|
||||
await this.handleAdd(params);
|
||||
break;
|
||||
case 'updateMetadata':
|
||||
await this.handleUpdate(params);
|
||||
break;
|
||||
case 'delete':
|
||||
await this.handleDelete(params);
|
||||
break;
|
||||
case 'clear':
|
||||
await this.handleClear();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Handle add operation - index new metadata
|
||||
*/
|
||||
async handleAdd(params) {
|
||||
if (!this.metadataIndex)
|
||||
return;
|
||||
const { id, metadata } = params;
|
||||
if (id && metadata) {
|
||||
await this.metadataIndex.addToIndex(id, metadata);
|
||||
this.log(`Indexed metadata for ${id}`, 'info');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle update operation - reindex metadata
|
||||
*/
|
||||
async handleUpdate(params) {
|
||||
if (!this.metadataIndex)
|
||||
return;
|
||||
const { id, oldMetadata, newMetadata } = params;
|
||||
// Remove old metadata
|
||||
if (id && oldMetadata) {
|
||||
await this.metadataIndex.removeFromIndex(id, oldMetadata);
|
||||
}
|
||||
// Add new metadata
|
||||
if (id && newMetadata) {
|
||||
await this.metadataIndex.addToIndex(id, newMetadata);
|
||||
this.log(`Reindexed metadata for ${id}`, 'info');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle delete operation - remove from index
|
||||
*/
|
||||
async handleDelete(params) {
|
||||
if (!this.metadataIndex)
|
||||
return;
|
||||
const { id, metadata } = params;
|
||||
if (id && metadata) {
|
||||
await this.metadataIndex.removeFromIndex(id, metadata);
|
||||
this.log(`Removed ${id} from index`, 'info');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle clear operation - clear index
|
||||
*/
|
||||
async handleClear() {
|
||||
if (!this.metadataIndex)
|
||||
return;
|
||||
// Clear the index when all data is cleared (rebuild effectively clears it)
|
||||
await this.metadataIndex.rebuild();
|
||||
this.log('Index cleared due to clear operation');
|
||||
}
|
||||
/**
|
||||
* Start periodic flush timer
|
||||
*/
|
||||
startFlushTimer() {
|
||||
if (this.flushTimer)
|
||||
return;
|
||||
this.flushTimer = setInterval(async () => {
|
||||
if (this.metadataIndex) {
|
||||
try {
|
||||
await this.metadataIndex.flush();
|
||||
}
|
||||
catch (error) {
|
||||
this.log('Error during periodic index flush', 'warn');
|
||||
}
|
||||
}
|
||||
}, this.config.flushInterval);
|
||||
}
|
||||
/**
|
||||
* Get IDs that match metadata filter (for pre-filtering)
|
||||
*/
|
||||
async getIdsForFilter(filter) {
|
||||
if (!this.metadataIndex)
|
||||
return [];
|
||||
return this.metadataIndex.getIdsForFilter(filter);
|
||||
}
|
||||
/**
|
||||
* Get available values for a field
|
||||
*/
|
||||
async getFilterValues(field) {
|
||||
if (!this.metadataIndex)
|
||||
return [];
|
||||
return this.metadataIndex.getFilterValues(field);
|
||||
}
|
||||
/**
|
||||
* Get all indexed fields
|
||||
*/
|
||||
async getFilterFields() {
|
||||
if (!this.metadataIndex)
|
||||
return [];
|
||||
return this.metadataIndex.getFilterFields();
|
||||
}
|
||||
/**
|
||||
* Get index statistics
|
||||
*/
|
||||
async getStats() {
|
||||
if (!this.metadataIndex) {
|
||||
return {
|
||||
enabled: false,
|
||||
totalEntries: 0,
|
||||
fieldsIndexed: [],
|
||||
memoryUsage: 0
|
||||
};
|
||||
}
|
||||
const stats = await this.metadataIndex.getStats();
|
||||
return {
|
||||
enabled: true,
|
||||
...stats
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Rebuild the index from storage
|
||||
*/
|
||||
async rebuild() {
|
||||
if (!this.metadataIndex) {
|
||||
throw new Error('Index augmentation is not initialized');
|
||||
}
|
||||
this.log('Rebuilding metadata index...');
|
||||
await this.metadataIndex.rebuild();
|
||||
const stats = await this.metadataIndex.getStats();
|
||||
this.log(`Index rebuilt: ${stats.totalEntries} entries, ${stats.fieldsIndexed.length} fields`);
|
||||
}
|
||||
/**
|
||||
* Flush index to storage
|
||||
*/
|
||||
async flush() {
|
||||
if (this.metadataIndex) {
|
||||
await this.metadataIndex.flush();
|
||||
this.log('Index flushed to storage', 'info');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add entry to index (public method for direct access)
|
||||
*/
|
||||
async addToIndex(id, metadata) {
|
||||
if (!this.metadataIndex)
|
||||
return;
|
||||
await this.metadataIndex.addToIndex(id, metadata);
|
||||
}
|
||||
/**
|
||||
* Remove entry from index (public method for direct access)
|
||||
*/
|
||||
async removeFromIndex(id, metadata) {
|
||||
if (!this.metadataIndex)
|
||||
return;
|
||||
await this.metadataIndex.removeFromIndex(id, metadata);
|
||||
}
|
||||
/**
|
||||
* Get the underlying MetadataIndexManager instance
|
||||
*/
|
||||
getMetadataIndex() {
|
||||
return this.metadataIndex;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config index augmentation
|
||||
*/
|
||||
export function createIndexAugmentation(config) {
|
||||
return new IndexAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=indexAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
158
.recovery-workspace/dist-backup-20250910-141917/augmentations/intelligentVerbScoring.d.ts
vendored
Normal file
158
.recovery-workspace/dist-backup-20250910-141917/augmentations/intelligentVerbScoring.d.ts
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import { ICognitionAugmentation, AugmentationResponse } from '../types/augmentations.js';
|
||||
/**
|
||||
* Configuration options for the Intelligent Verb Scoring augmentation
|
||||
*/
|
||||
export interface IVerbScoringConfig {
|
||||
/** Enable semantic proximity scoring based on entity embeddings */
|
||||
enableSemanticScoring: boolean;
|
||||
/** Enable frequency-based weight amplification */
|
||||
enableFrequencyAmplification: boolean;
|
||||
/** Enable temporal decay for weights */
|
||||
enableTemporalDecay: boolean;
|
||||
/** Decay rate per day for temporal scoring (0-1) */
|
||||
temporalDecayRate: number;
|
||||
/** Minimum weight threshold */
|
||||
minWeight: number;
|
||||
/** Maximum weight threshold */
|
||||
maxWeight: number;
|
||||
/** Base confidence score for new relationships */
|
||||
baseConfidence: number;
|
||||
/** Learning rate for adaptive scoring (0-1) */
|
||||
learningRate: number;
|
||||
}
|
||||
/**
|
||||
* Default configuration for the Intelligent Verb Scoring augmentation
|
||||
*/
|
||||
export declare const DEFAULT_VERB_SCORING_CONFIG: IVerbScoringConfig;
|
||||
/**
|
||||
* Relationship statistics for learning and adaptation
|
||||
*/
|
||||
interface RelationshipStats {
|
||||
count: number;
|
||||
totalWeight: number;
|
||||
averageWeight: number;
|
||||
lastSeen: Date;
|
||||
firstSeen: Date;
|
||||
semanticSimilarity?: number;
|
||||
}
|
||||
/**
|
||||
* Intelligent Verb Scoring Cognition Augmentation
|
||||
*
|
||||
* Automatically generates intelligent weight and confidence scores for verb relationships
|
||||
* using semantic analysis, frequency patterns, and temporal factors.
|
||||
*/
|
||||
export declare class IntelligentVerbScoring implements ICognitionAugmentation {
|
||||
readonly name = "intelligent-verb-scoring";
|
||||
readonly description = "Automatically generates intelligent weight and confidence scores for verb relationships";
|
||||
enabled: boolean;
|
||||
private config;
|
||||
private relationshipStats;
|
||||
private brainyInstance;
|
||||
private isInitialized;
|
||||
constructor(config?: Partial<IVerbScoringConfig>);
|
||||
initialize(): Promise<void>;
|
||||
shutDown(): Promise<void>;
|
||||
getStatus(): Promise<'active' | 'inactive' | 'error'>;
|
||||
/**
|
||||
* Set reference to the BrainyData instance for accessing graph data
|
||||
*/
|
||||
setBrainyInstance(instance: any): void;
|
||||
/**
|
||||
* Main reasoning method for generating intelligent verb scores
|
||||
*/
|
||||
reason(query: string, context?: Record<string, unknown>): AugmentationResponse<{
|
||||
inference: string;
|
||||
confidence: number;
|
||||
}>;
|
||||
infer(dataSubset: Record<string, unknown>): AugmentationResponse<Record<string, unknown>>;
|
||||
executeLogic(ruleId: string, input: Record<string, unknown>): AugmentationResponse<boolean>;
|
||||
/**
|
||||
* Generate intelligent weight and confidence scores for a verb relationship
|
||||
*
|
||||
* @param sourceId - ID of the source entity
|
||||
* @param targetId - ID of the target entity
|
||||
* @param verbType - Type of the relationship
|
||||
* @param existingWeight - Existing weight if any
|
||||
* @param metadata - Additional metadata about the relationship
|
||||
* @returns Computed weight and confidence scores
|
||||
*/
|
||||
computeVerbScores(sourceId: string, targetId: string, verbType: string, existingWeight?: number, metadata?: any): Promise<{
|
||||
weight: number;
|
||||
confidence: number;
|
||||
reasoning: string[];
|
||||
}>;
|
||||
/**
|
||||
* Calculate semantic similarity between two entities using their embeddings
|
||||
*/
|
||||
private calculateSemanticScore;
|
||||
/**
|
||||
* Calculate frequency-based boost for repeated relationships
|
||||
*/
|
||||
private calculateFrequencyBoost;
|
||||
/**
|
||||
* Calculate temporal decay factor based on recency
|
||||
*/
|
||||
private calculateTemporalFactor;
|
||||
/**
|
||||
* Calculate learning-based adjustment using historical patterns
|
||||
*/
|
||||
private calculateLearningAdjustment;
|
||||
/**
|
||||
* Update relationship statistics for learning
|
||||
*/
|
||||
private updateRelationshipStats;
|
||||
/**
|
||||
* Blend two scores using a weighted average
|
||||
*/
|
||||
private blendScores;
|
||||
/**
|
||||
* Get current configuration
|
||||
*/
|
||||
getConfig(): IVerbScoringConfig;
|
||||
/**
|
||||
* Update configuration
|
||||
*/
|
||||
updateConfig(newConfig: Partial<IVerbScoringConfig>): void;
|
||||
/**
|
||||
* Get relationship statistics (for debugging/monitoring)
|
||||
*/
|
||||
getRelationshipStats(): Map<string, RelationshipStats>;
|
||||
/**
|
||||
* Clear relationship statistics
|
||||
*/
|
||||
clearStats(): void;
|
||||
/**
|
||||
* Provide feedback to improve future scoring
|
||||
* This allows the system to learn from user corrections or validation
|
||||
*
|
||||
* @param sourceId - Source entity ID
|
||||
* @param targetId - Target entity ID
|
||||
* @param verbType - Relationship type
|
||||
* @param feedbackWeight - The corrected/validated weight (0-1)
|
||||
* @param feedbackConfidence - The corrected/validated confidence (0-1)
|
||||
* @param feedbackType - Type of feedback ('correction', 'validation', 'enhancement')
|
||||
*/
|
||||
provideFeedback(sourceId: string, targetId: string, verbType: string, feedbackWeight: number, feedbackConfidence?: number, feedbackType?: 'correction' | 'validation' | 'enhancement'): Promise<void>;
|
||||
/**
|
||||
* Get learning statistics for monitoring and debugging
|
||||
*/
|
||||
getLearningStats(): {
|
||||
totalRelationships: number;
|
||||
averageConfidence: number;
|
||||
feedbackCount: number;
|
||||
topRelationships: Array<{
|
||||
relationship: string;
|
||||
count: number;
|
||||
averageWeight: number;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* Export learning data for backup or analysis
|
||||
*/
|
||||
exportLearningData(): string;
|
||||
/**
|
||||
* Import learning data from backup
|
||||
*/
|
||||
importLearningData(jsonData: string): void;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
import { cosineDistance } from '../utils/distance.js';
|
||||
/**
|
||||
* Default configuration for the Intelligent Verb Scoring augmentation
|
||||
*/
|
||||
export const DEFAULT_VERB_SCORING_CONFIG = {
|
||||
enableSemanticScoring: true,
|
||||
enableFrequencyAmplification: true,
|
||||
enableTemporalDecay: true,
|
||||
temporalDecayRate: 0.01, // 1% decay per day
|
||||
minWeight: 0.1,
|
||||
maxWeight: 1.0,
|
||||
baseConfidence: 0.5,
|
||||
learningRate: 0.1
|
||||
};
|
||||
/**
|
||||
* Intelligent Verb Scoring Cognition Augmentation
|
||||
*
|
||||
* Automatically generates intelligent weight and confidence scores for verb relationships
|
||||
* using semantic analysis, frequency patterns, and temporal factors.
|
||||
*/
|
||||
export class IntelligentVerbScoring {
|
||||
constructor(config = {}) {
|
||||
this.name = 'intelligent-verb-scoring';
|
||||
this.description = 'Automatically generates intelligent weight and confidence scores for verb relationships';
|
||||
this.enabled = false; // Off by default as requested
|
||||
this.relationshipStats = new Map();
|
||||
this.isInitialized = false;
|
||||
this.config = { ...DEFAULT_VERB_SCORING_CONFIG, ...config };
|
||||
}
|
||||
async initialize() {
|
||||
if (this.isInitialized)
|
||||
return;
|
||||
this.isInitialized = true;
|
||||
}
|
||||
async shutDown() {
|
||||
this.relationshipStats.clear();
|
||||
this.isInitialized = false;
|
||||
}
|
||||
async getStatus() {
|
||||
return this.enabled && this.isInitialized ? 'active' : 'inactive';
|
||||
}
|
||||
/**
|
||||
* Set reference to the BrainyData instance for accessing graph data
|
||||
*/
|
||||
setBrainyInstance(instance) {
|
||||
this.brainyInstance = instance;
|
||||
}
|
||||
/**
|
||||
* Main reasoning method for generating intelligent verb scores
|
||||
*/
|
||||
reason(query, context) {
|
||||
if (!this.enabled) {
|
||||
return {
|
||||
success: false,
|
||||
data: { inference: 'Augmentation is disabled', confidence: 0 },
|
||||
error: 'Intelligent verb scoring is disabled'
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
inference: 'Intelligent verb scoring active',
|
||||
confidence: 1.0
|
||||
}
|
||||
};
|
||||
}
|
||||
infer(dataSubset) {
|
||||
return {
|
||||
success: true,
|
||||
data: dataSubset
|
||||
};
|
||||
}
|
||||
executeLogic(ruleId, input) {
|
||||
return {
|
||||
success: true,
|
||||
data: true
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Generate intelligent weight and confidence scores for a verb relationship
|
||||
*
|
||||
* @param sourceId - ID of the source entity
|
||||
* @param targetId - ID of the target entity
|
||||
* @param verbType - Type of the relationship
|
||||
* @param existingWeight - Existing weight if any
|
||||
* @param metadata - Additional metadata about the relationship
|
||||
* @returns Computed weight and confidence scores
|
||||
*/
|
||||
async computeVerbScores(sourceId, targetId, verbType, existingWeight, metadata) {
|
||||
if (!this.enabled || !this.brainyInstance) {
|
||||
return {
|
||||
weight: existingWeight ?? 0.5,
|
||||
confidence: this.config.baseConfidence,
|
||||
reasoning: ['Intelligent scoring disabled']
|
||||
};
|
||||
}
|
||||
const reasoning = [];
|
||||
let weight = existingWeight ?? 0.5;
|
||||
let confidence = this.config.baseConfidence;
|
||||
try {
|
||||
// Get relationship key for statistics
|
||||
const relationKey = `${sourceId}-${verbType}-${targetId}`;
|
||||
// Update relationship statistics
|
||||
this.updateRelationshipStats(relationKey, weight, metadata);
|
||||
// Apply semantic scoring if enabled
|
||||
if (this.config.enableSemanticScoring) {
|
||||
const semanticScore = await this.calculateSemanticScore(sourceId, targetId);
|
||||
if (semanticScore !== null) {
|
||||
weight = this.blendScores(weight, semanticScore, 0.3);
|
||||
confidence = Math.min(confidence + semanticScore * 0.2, 1.0);
|
||||
reasoning.push(`Semantic similarity: ${semanticScore.toFixed(3)}`);
|
||||
}
|
||||
}
|
||||
// Apply frequency amplification if enabled
|
||||
if (this.config.enableFrequencyAmplification) {
|
||||
const frequencyBoost = this.calculateFrequencyBoost(relationKey);
|
||||
weight = this.blendScores(weight, frequencyBoost, 0.2);
|
||||
if (frequencyBoost > 0.5) {
|
||||
confidence = Math.min(confidence + 0.1, 1.0);
|
||||
reasoning.push(`Frequency boost: ${frequencyBoost.toFixed(3)}`);
|
||||
}
|
||||
}
|
||||
// Apply temporal decay if enabled
|
||||
if (this.config.enableTemporalDecay) {
|
||||
const temporalFactor = this.calculateTemporalFactor(relationKey);
|
||||
weight *= temporalFactor;
|
||||
reasoning.push(`Temporal factor: ${temporalFactor.toFixed(3)}`);
|
||||
}
|
||||
// Apply learning adjustments
|
||||
const learningAdjustment = this.calculateLearningAdjustment(relationKey);
|
||||
weight = this.blendScores(weight, learningAdjustment, this.config.learningRate);
|
||||
// Clamp values to configured bounds
|
||||
weight = Math.max(this.config.minWeight, Math.min(this.config.maxWeight, weight));
|
||||
confidence = Math.max(0, Math.min(1, confidence));
|
||||
reasoning.push(`Final weight: ${weight.toFixed(3)}, confidence: ${confidence.toFixed(3)}`);
|
||||
return { weight, confidence, reasoning };
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Error computing verb scores:', error);
|
||||
return {
|
||||
weight: existingWeight ?? 0.5,
|
||||
confidence: this.config.baseConfidence,
|
||||
reasoning: [`Error in scoring: ${error}`]
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate semantic similarity between two entities using their embeddings
|
||||
*/
|
||||
async calculateSemanticScore(sourceId, targetId) {
|
||||
try {
|
||||
if (!this.brainyInstance?.storage)
|
||||
return null;
|
||||
// Get noun embeddings from storage
|
||||
const sourceNoun = await this.brainyInstance.storage.getNoun(sourceId);
|
||||
const targetNoun = await this.brainyInstance.storage.getNoun(targetId);
|
||||
if (!sourceNoun?.vector || !targetNoun?.vector)
|
||||
return null;
|
||||
// Calculate cosine similarity (1 - distance)
|
||||
const distance = cosineDistance(sourceNoun.vector, targetNoun.vector);
|
||||
return Math.max(0, 1 - distance);
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Error calculating semantic score:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate frequency-based boost for repeated relationships
|
||||
*/
|
||||
calculateFrequencyBoost(relationKey) {
|
||||
const stats = this.relationshipStats.get(relationKey);
|
||||
if (!stats || stats.count <= 1)
|
||||
return 0.5;
|
||||
// Logarithmic scaling: more occurrences = higher weight, but with diminishing returns
|
||||
const boost = Math.log(stats.count + 1) / Math.log(10); // Log base 10
|
||||
return Math.min(boost, 1.0);
|
||||
}
|
||||
/**
|
||||
* Calculate temporal decay factor based on recency
|
||||
*/
|
||||
calculateTemporalFactor(relationKey) {
|
||||
const stats = this.relationshipStats.get(relationKey);
|
||||
if (!stats)
|
||||
return 1.0;
|
||||
const daysSinceLastSeen = (Date.now() - stats.lastSeen.getTime()) / (1000 * 60 * 60 * 24);
|
||||
const decayFactor = Math.exp(-this.config.temporalDecayRate * daysSinceLastSeen);
|
||||
return Math.max(0.1, decayFactor); // Minimum 10% of original weight
|
||||
}
|
||||
/**
|
||||
* Calculate learning-based adjustment using historical patterns
|
||||
*/
|
||||
calculateLearningAdjustment(relationKey) {
|
||||
const stats = this.relationshipStats.get(relationKey);
|
||||
if (!stats || stats.count <= 1)
|
||||
return 0.5;
|
||||
// Use moving average of weights as learned baseline
|
||||
return Math.max(0, Math.min(1, stats.averageWeight));
|
||||
}
|
||||
/**
|
||||
* Update relationship statistics for learning
|
||||
*/
|
||||
updateRelationshipStats(relationKey, weight, metadata) {
|
||||
const now = new Date();
|
||||
const existing = this.relationshipStats.get(relationKey);
|
||||
if (existing) {
|
||||
// Update existing stats
|
||||
existing.count++;
|
||||
existing.totalWeight += weight;
|
||||
existing.averageWeight = existing.totalWeight / existing.count;
|
||||
existing.lastSeen = now;
|
||||
}
|
||||
else {
|
||||
// Create new stats entry
|
||||
this.relationshipStats.set(relationKey, {
|
||||
count: 1,
|
||||
totalWeight: weight,
|
||||
averageWeight: weight,
|
||||
lastSeen: now,
|
||||
firstSeen: now
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Blend two scores using a weighted average
|
||||
*/
|
||||
blendScores(score1, score2, weight2) {
|
||||
const weight1 = 1 - weight2;
|
||||
return score1 * weight1 + score2 * weight2;
|
||||
}
|
||||
/**
|
||||
* Get current configuration
|
||||
*/
|
||||
getConfig() {
|
||||
return { ...this.config };
|
||||
}
|
||||
/**
|
||||
* Update configuration
|
||||
*/
|
||||
updateConfig(newConfig) {
|
||||
this.config = { ...this.config, ...newConfig };
|
||||
}
|
||||
/**
|
||||
* Get relationship statistics (for debugging/monitoring)
|
||||
*/
|
||||
getRelationshipStats() {
|
||||
return new Map(this.relationshipStats);
|
||||
}
|
||||
/**
|
||||
* Clear relationship statistics
|
||||
*/
|
||||
clearStats() {
|
||||
this.relationshipStats.clear();
|
||||
}
|
||||
/**
|
||||
* Provide feedback to improve future scoring
|
||||
* This allows the system to learn from user corrections or validation
|
||||
*
|
||||
* @param sourceId - Source entity ID
|
||||
* @param targetId - Target entity ID
|
||||
* @param verbType - Relationship type
|
||||
* @param feedbackWeight - The corrected/validated weight (0-1)
|
||||
* @param feedbackConfidence - The corrected/validated confidence (0-1)
|
||||
* @param feedbackType - Type of feedback ('correction', 'validation', 'enhancement')
|
||||
*/
|
||||
async provideFeedback(sourceId, targetId, verbType, feedbackWeight, feedbackConfidence, feedbackType = 'correction') {
|
||||
if (!this.enabled)
|
||||
return;
|
||||
const relationKey = `${sourceId}-${verbType}-${targetId}`;
|
||||
const existing = this.relationshipStats.get(relationKey);
|
||||
if (existing) {
|
||||
// Apply feedback with learning rate
|
||||
const newWeight = existing.averageWeight * (1 - this.config.learningRate) +
|
||||
feedbackWeight * this.config.learningRate;
|
||||
// Update the running average with feedback
|
||||
existing.totalWeight = (existing.totalWeight * existing.count + feedbackWeight) / (existing.count + 1);
|
||||
existing.averageWeight = existing.totalWeight / existing.count;
|
||||
existing.count += 1;
|
||||
existing.lastSeen = new Date();
|
||||
if (this.brainyInstance?.loggingConfig?.verbose) {
|
||||
console.log(`Feedback applied for ${relationKey}: ${feedbackType}, ` +
|
||||
`old weight: ${existing.averageWeight.toFixed(3)}, ` +
|
||||
`feedback: ${feedbackWeight.toFixed(3)}, ` +
|
||||
`new weight: ${newWeight.toFixed(3)}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Create new entry with feedback as initial data
|
||||
this.relationshipStats.set(relationKey, {
|
||||
count: 1,
|
||||
totalWeight: feedbackWeight,
|
||||
averageWeight: feedbackWeight,
|
||||
lastSeen: new Date(),
|
||||
firstSeen: new Date()
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get learning statistics for monitoring and debugging
|
||||
*/
|
||||
getLearningStats() {
|
||||
const relationships = Array.from(this.relationshipStats.entries());
|
||||
const totalRelationships = relationships.length;
|
||||
const feedbackCount = relationships.reduce((sum, [, stats]) => sum + stats.count, 0);
|
||||
// Calculate average confidence (approximated from weight patterns)
|
||||
const averageWeight = relationships.reduce((sum, [, stats]) => sum + stats.averageWeight, 0) / totalRelationships || 0;
|
||||
const averageConfidence = Math.min(averageWeight + 0.2, 1.0); // Heuristic: confidence typically higher than weight
|
||||
// Get top relationships by count
|
||||
const topRelationships = relationships
|
||||
.map(([key, stats]) => ({
|
||||
relationship: key,
|
||||
count: stats.count,
|
||||
averageWeight: stats.averageWeight
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
.slice(0, 10);
|
||||
return {
|
||||
totalRelationships,
|
||||
averageConfidence,
|
||||
feedbackCount,
|
||||
topRelationships
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Export learning data for backup or analysis
|
||||
*/
|
||||
exportLearningData() {
|
||||
const data = {
|
||||
config: this.config,
|
||||
stats: Array.from(this.relationshipStats.entries()).map(([key, stats]) => ({
|
||||
relationship: key,
|
||||
...stats,
|
||||
firstSeen: stats.firstSeen.toISOString(),
|
||||
lastSeen: stats.lastSeen.toISOString()
|
||||
})),
|
||||
exportedAt: new Date().toISOString(),
|
||||
version: '1.0'
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
/**
|
||||
* Import learning data from backup
|
||||
*/
|
||||
importLearningData(jsonData) {
|
||||
try {
|
||||
const data = JSON.parse(jsonData);
|
||||
if (data.version !== '1.0') {
|
||||
console.warn('Learning data version mismatch, importing anyway');
|
||||
}
|
||||
// Update configuration if provided
|
||||
if (data.config) {
|
||||
this.config = { ...this.config, ...data.config };
|
||||
}
|
||||
// Import relationship statistics
|
||||
if (data.stats && Array.isArray(data.stats)) {
|
||||
for (const stat of data.stats) {
|
||||
if (stat.relationship) {
|
||||
this.relationshipStats.set(stat.relationship, {
|
||||
count: stat.count || 1,
|
||||
totalWeight: stat.totalWeight || stat.averageWeight || 0.5,
|
||||
averageWeight: stat.averageWeight || 0.5,
|
||||
firstSeen: new Date(stat.firstSeen || Date.now()),
|
||||
lastSeen: new Date(stat.lastSeen || Date.now()),
|
||||
semanticSimilarity: stat.semanticSimilarity
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Imported learning data: ${this.relationshipStats.size} relationships`);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to import learning data:', error);
|
||||
throw new Error(`Failed to import learning data: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=intelligentVerbScoring.js.map
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,157 @@
|
|||
/**
|
||||
* Intelligent Verb Scoring Augmentation
|
||||
*
|
||||
* Enhances relationship quality through intelligent semantic scoring
|
||||
* Provides context-aware relationship weights based on:
|
||||
* - Semantic proximity of connected entities
|
||||
* - Frequency-based amplification
|
||||
* - Temporal decay modeling
|
||||
* - Adaptive learning from usage patterns
|
||||
*
|
||||
* Critical for enterprise knowledge graphs with millions of relationships
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
interface VerbScoringConfig {
|
||||
enabled?: boolean;
|
||||
enableSemanticScoring?: boolean;
|
||||
semanticThreshold?: number;
|
||||
semanticWeight?: number;
|
||||
enableFrequencyAmplification?: boolean;
|
||||
frequencyDecay?: number;
|
||||
maxFrequencyBoost?: number;
|
||||
enableTemporalDecay?: boolean;
|
||||
temporalDecayRate?: number;
|
||||
temporalWindow?: number;
|
||||
enableAdaptiveLearning?: boolean;
|
||||
learningRate?: number;
|
||||
confidenceThreshold?: number;
|
||||
minWeight?: number;
|
||||
maxWeight?: number;
|
||||
baseWeight?: number;
|
||||
}
|
||||
interface RelationshipMetrics {
|
||||
count: number;
|
||||
totalWeight: number;
|
||||
averageWeight: number;
|
||||
lastUpdated: number;
|
||||
semanticScore: number;
|
||||
frequencyScore: number;
|
||||
temporalScore: number;
|
||||
confidenceScore: number;
|
||||
}
|
||||
interface ScoringMetrics {
|
||||
relationshipsScored: number;
|
||||
averageSemanticScore: number;
|
||||
averageFrequencyScore: number;
|
||||
averageTemporalScore: number;
|
||||
averageConfidenceScore: number;
|
||||
adaptiveAdjustments: number;
|
||||
computationTimeMs: number;
|
||||
}
|
||||
export declare class IntelligentVerbScoringAugmentation extends BaseAugmentation {
|
||||
name: string;
|
||||
timing: "around";
|
||||
readonly metadata: {
|
||||
reads: string[];
|
||||
writes: string[];
|
||||
};
|
||||
operations: ("addVerb" | "relate")[];
|
||||
priority: number;
|
||||
readonly category: "core";
|
||||
readonly description = "AI-powered intelligent scoring for relationship strength analysis";
|
||||
protected config: Required<VerbScoringConfig>;
|
||||
private relationshipStats;
|
||||
private metrics;
|
||||
private scoringInstance;
|
||||
constructor(config?: VerbScoringConfig);
|
||||
protected onInitialize(): Promise<void>;
|
||||
/**
|
||||
* Get this augmentation instance for API compatibility
|
||||
* Used by Brainy to access scoring methods
|
||||
*/
|
||||
getScoring(): IntelligentVerbScoringAugmentation;
|
||||
shouldExecute(operation: string, params: any): boolean;
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
private calculateIntelligentWeight;
|
||||
private calculateSemanticScore;
|
||||
/**
|
||||
* Detect noun type using neural taxonomy matching
|
||||
*/
|
||||
private detectNounType;
|
||||
/**
|
||||
* Calculate taxonomy-based similarity boost
|
||||
*/
|
||||
private calculateTaxonomyBoost;
|
||||
private calculateCosineSimilarity;
|
||||
private calculateFrequencyScore;
|
||||
private calculateTemporalScore;
|
||||
private calculateContextScore;
|
||||
private updateRelationshipLearning;
|
||||
private getConfidenceScore;
|
||||
private getScoringMethodsUsed;
|
||||
private storeDetailedScoring;
|
||||
private updateMetrics;
|
||||
/**
|
||||
* Get intelligent verb scoring statistics
|
||||
*/
|
||||
getStats(): ScoringMetrics & {
|
||||
totalRelationships: number;
|
||||
averageConfidence: number;
|
||||
highConfidenceRelationships: number;
|
||||
learningEfficiency: number;
|
||||
};
|
||||
/**
|
||||
* Export relationship statistics for analysis
|
||||
*/
|
||||
exportRelationshipStats(): Array<{
|
||||
relationship: string;
|
||||
metrics: RelationshipMetrics;
|
||||
}>;
|
||||
/**
|
||||
* Import relationship statistics from previous sessions
|
||||
*/
|
||||
importRelationshipStats(stats: Array<{
|
||||
relationship: string;
|
||||
metrics: RelationshipMetrics;
|
||||
}>): void;
|
||||
/**
|
||||
* Get learning statistics for monitoring and debugging
|
||||
* Required for Brainy.getVerbScoringStats()
|
||||
*/
|
||||
getLearningStats(): {
|
||||
totalRelationships: number;
|
||||
averageConfidence: number;
|
||||
feedbackCount: number;
|
||||
topRelationships: Array<{
|
||||
relationship: string;
|
||||
count: number;
|
||||
averageWeight: number;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* Export learning data for backup or analysis
|
||||
* Required for Brainy.exportVerbScoringLearningData()
|
||||
*/
|
||||
exportLearningData(): string;
|
||||
/**
|
||||
* Import learning data from backup
|
||||
* Required for Brainy.importVerbScoringLearningData()
|
||||
*/
|
||||
importLearningData(jsonData: string): void;
|
||||
/**
|
||||
* Provide feedback on a relationship's weight
|
||||
* Required for Brainy.provideVerbScoringFeedback()
|
||||
*/
|
||||
provideFeedback(sourceId: string, targetId: string, relationType: string, feedback: number, feedbackType?: 'correction' | 'validation' | 'enhancement'): Promise<void>;
|
||||
/**
|
||||
* Compute intelligent scores for a verb relationship
|
||||
* Used internally during verb creation
|
||||
*/
|
||||
computeVerbScores(sourceNoun: any, targetNoun: any, relationType: string): Promise<{
|
||||
weight: number;
|
||||
confidence: number;
|
||||
reasoning: string[];
|
||||
}>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,559 @@
|
|||
/**
|
||||
* Intelligent Verb Scoring Augmentation
|
||||
*
|
||||
* Enhances relationship quality through intelligent semantic scoring
|
||||
* Provides context-aware relationship weights based on:
|
||||
* - Semantic proximity of connected entities
|
||||
* - Frequency-based amplification
|
||||
* - Temporal decay modeling
|
||||
* - Adaptive learning from usage patterns
|
||||
*
|
||||
* Critical for enterprise knowledge graphs with millions of relationships
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export class IntelligentVerbScoringAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.name = 'IntelligentVerbScoring';
|
||||
this.timing = 'around';
|
||||
this.metadata = {
|
||||
reads: ['type', 'verb', 'source', 'target'],
|
||||
writes: ['weight', 'confidence', 'intelligentScoring']
|
||||
}; // Adds scoring metadata to verbs
|
||||
this.operations = ['addVerb', 'relate'];
|
||||
this.priority = 10; // Enhancement feature - runs after core operations
|
||||
// Augmentation metadata
|
||||
this.category = 'core';
|
||||
this.description = 'AI-powered intelligent scoring for relationship strength analysis';
|
||||
this.relationshipStats = new Map();
|
||||
this.metrics = {
|
||||
relationshipsScored: 0,
|
||||
averageSemanticScore: 0,
|
||||
averageFrequencyScore: 0,
|
||||
averageTemporalScore: 0,
|
||||
averageConfidenceScore: 0,
|
||||
adaptiveAdjustments: 0,
|
||||
computationTimeMs: 0
|
||||
};
|
||||
this.config = {
|
||||
enabled: config.enabled ?? true, // Smart by default!
|
||||
// Semantic Analysis
|
||||
enableSemanticScoring: config.enableSemanticScoring ?? true,
|
||||
semanticThreshold: config.semanticThreshold ?? 0.3,
|
||||
semanticWeight: config.semanticWeight ?? 0.4,
|
||||
// Frequency Analysis
|
||||
enableFrequencyAmplification: config.enableFrequencyAmplification ?? true,
|
||||
frequencyDecay: config.frequencyDecay ?? 0.95, // 5% decay per occurrence
|
||||
maxFrequencyBoost: config.maxFrequencyBoost ?? 2.0,
|
||||
// Temporal Analysis
|
||||
enableTemporalDecay: config.enableTemporalDecay ?? true,
|
||||
temporalDecayRate: config.temporalDecayRate ?? 0.01, // 1% per day
|
||||
temporalWindow: config.temporalWindow ?? 365, // 1 year
|
||||
// Learning & Adaptation
|
||||
enableAdaptiveLearning: config.enableAdaptiveLearning ?? true,
|
||||
learningRate: config.learningRate ?? 0.1,
|
||||
confidenceThreshold: config.confidenceThreshold ?? 0.3,
|
||||
// Weight Management
|
||||
minWeight: config.minWeight ?? 0.1,
|
||||
maxWeight: config.maxWeight ?? 1.0,
|
||||
baseWeight: config.baseWeight ?? 0.5
|
||||
};
|
||||
// Set enabled property based on config
|
||||
this.enabled = this.config.enabled;
|
||||
}
|
||||
async onInitialize() {
|
||||
if (this.config.enabled) {
|
||||
this.log('Intelligent verb scoring initialized for enhanced relationship quality');
|
||||
}
|
||||
else {
|
||||
this.log('Intelligent verb scoring disabled');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get this augmentation instance for API compatibility
|
||||
* Used by Brainy to access scoring methods
|
||||
*/
|
||||
getScoring() {
|
||||
return this;
|
||||
}
|
||||
shouldExecute(operation, params) {
|
||||
// For addVerb, params are passed as array: [sourceId, targetId, verbType, metadata, weight]
|
||||
if (operation === 'addVerb' && this.config.enabled) {
|
||||
return Array.isArray(params) && params.length >= 3;
|
||||
}
|
||||
// For relate method, params might be an object
|
||||
if (operation === 'relate' && this.config.enabled) {
|
||||
return params.sourceId && params.targetId && params.relationType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async execute(operation, params, next) {
|
||||
if (!this.shouldExecute(operation, params)) {
|
||||
return next();
|
||||
}
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
let sourceId, targetId, relationType, metadata;
|
||||
let scoringResult = null;
|
||||
// Extract parameters based on operation type
|
||||
if (operation === 'addVerb' && Array.isArray(params)) {
|
||||
// addVerb params: [sourceId, targetId, verbType, metadata, weight]
|
||||
[sourceId, targetId, relationType, metadata] = params;
|
||||
}
|
||||
else if (operation === 'relate') {
|
||||
// relate params might be an object
|
||||
sourceId = params.sourceId;
|
||||
targetId = params.targetId;
|
||||
relationType = params.relationType;
|
||||
metadata = params.metadata;
|
||||
}
|
||||
else {
|
||||
return next();
|
||||
}
|
||||
// Skip if weight is already provided explicitly
|
||||
if (Array.isArray(params) && params[4] !== undefined && params[4] !== null) {
|
||||
return next();
|
||||
}
|
||||
// Get the nouns to compute scoring
|
||||
const sourceNoun = await this.context?.brain.get(sourceId);
|
||||
const targetNoun = await this.context?.brain.get(targetId);
|
||||
// Compute intelligent scores with reasoning
|
||||
scoringResult = await this.computeVerbScores(sourceNoun, targetNoun, relationType);
|
||||
// For addVerb, modify the params array
|
||||
if (operation === 'addVerb' && Array.isArray(params)) {
|
||||
// Set the weight parameter (index 4)
|
||||
params[4] = scoringResult.weight;
|
||||
// Enhance metadata with scoring info
|
||||
params[3] = {
|
||||
...params[3],
|
||||
intelligentScoring: {
|
||||
weight: scoringResult.weight,
|
||||
confidence: scoringResult.confidence,
|
||||
reasoning: scoringResult.reasoning,
|
||||
scoringMethod: this.getScoringMethodsUsed(),
|
||||
computedAt: Date.now()
|
||||
}
|
||||
};
|
||||
}
|
||||
// Execute with enhanced parameters
|
||||
const result = await next();
|
||||
// Learn from this relationship
|
||||
if (this.config.enableAdaptiveLearning && scoringResult) {
|
||||
await this.updateRelationshipLearning(sourceId, targetId, relationType, scoringResult.weight);
|
||||
}
|
||||
// Update metrics
|
||||
const computationTime = Date.now() - startTime;
|
||||
if (scoringResult) {
|
||||
this.updateMetrics(scoringResult.weight, computationTime);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Intelligent verb scoring error: ${error}`, 'error');
|
||||
// Fallback to original parameters
|
||||
return next();
|
||||
}
|
||||
}
|
||||
async calculateIntelligentWeight(sourceId, targetId, relationType, metadata) {
|
||||
let finalWeight = this.config.baseWeight;
|
||||
let scoreComponents = {};
|
||||
// 1. Semantic Proximity Score
|
||||
if (this.config.enableSemanticScoring) {
|
||||
const semanticScore = await this.calculateSemanticScore(sourceId, targetId);
|
||||
scoreComponents.semantic = semanticScore;
|
||||
finalWeight = finalWeight * (1 + semanticScore * this.config.semanticWeight);
|
||||
}
|
||||
// 2. Frequency Amplification Score
|
||||
if (this.config.enableFrequencyAmplification) {
|
||||
const frequencyScore = this.calculateFrequencyScore(sourceId, targetId, relationType);
|
||||
scoreComponents.frequency = frequencyScore;
|
||||
finalWeight = finalWeight * (1 + frequencyScore);
|
||||
}
|
||||
// 3. Temporal Relevance Score
|
||||
if (this.config.enableTemporalDecay) {
|
||||
const temporalScore = this.calculateTemporalScore(sourceId, targetId, relationType);
|
||||
scoreComponents.temporal = temporalScore;
|
||||
finalWeight = finalWeight * temporalScore;
|
||||
}
|
||||
// 4. Context Awareness (from metadata)
|
||||
const contextScore = this.calculateContextScore(metadata);
|
||||
scoreComponents.context = contextScore;
|
||||
finalWeight = finalWeight * (1 + contextScore * 0.2);
|
||||
// 5. Apply constraints
|
||||
finalWeight = Math.max(this.config.minWeight, Math.min(this.config.maxWeight, finalWeight));
|
||||
// Store detailed scoring for analysis
|
||||
this.storeDetailedScoring(sourceId, targetId, relationType, {
|
||||
finalWeight,
|
||||
components: scoreComponents,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
return finalWeight;
|
||||
}
|
||||
async calculateSemanticScore(sourceId, targetId) {
|
||||
try {
|
||||
// Get embeddings for both entities
|
||||
const sourceNoun = await this.context?.brain.get(sourceId);
|
||||
const targetNoun = await this.context?.brain.get(targetId);
|
||||
if (!sourceNoun?.vector || !targetNoun?.vector) {
|
||||
return 0;
|
||||
}
|
||||
// Get noun types using neural detection (taxonomy-based)
|
||||
const sourceType = await this.detectNounType(sourceNoun.vector);
|
||||
const targetType = await this.detectNounType(targetNoun.vector);
|
||||
// Calculate direct similarity
|
||||
const directSimilarity = this.calculateCosineSimilarity(sourceNoun.vector, targetNoun.vector);
|
||||
// Calculate taxonomy-based similarity boost
|
||||
const taxonomyBoost = await this.calculateTaxonomyBoost(sourceType, targetType);
|
||||
// Blend direct similarity with taxonomy guidance
|
||||
// Taxonomy provides consistency while preserving flexibility
|
||||
const semanticScore = directSimilarity * 0.7 + taxonomyBoost * 0.3;
|
||||
return Math.min(1, Math.max(0, semanticScore));
|
||||
}
|
||||
catch (error) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Detect noun type using neural taxonomy matching
|
||||
*/
|
||||
async detectNounType(vector) {
|
||||
// Use the same neural detection as addNoun for consistency
|
||||
if (!this.context?.brain)
|
||||
return 'unknown';
|
||||
try {
|
||||
// This would normally call the brain's detectNounType method
|
||||
// For now, simplified type detection based on vector patterns
|
||||
const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
|
||||
// Heuristic type detection (would use actual taxonomy embeddings)
|
||||
if (magnitude > 10)
|
||||
return 'concept';
|
||||
if (magnitude > 5)
|
||||
return 'entity';
|
||||
if (magnitude > 2)
|
||||
return 'object';
|
||||
return 'item';
|
||||
}
|
||||
catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate taxonomy-based similarity boost
|
||||
*/
|
||||
async calculateTaxonomyBoost(sourceType, targetType) {
|
||||
// Define valid relationship patterns in taxonomy
|
||||
const validPatterns = {
|
||||
'person': { 'concept': 0.9, 'skill': 0.85, 'organization': 0.8, 'person': 0.7 },
|
||||
'concept': { 'concept': 0.9, 'example': 0.85, 'application': 0.8 },
|
||||
'entity': { 'entity': 0.8, 'property': 0.85, 'action': 0.75 },
|
||||
'object': { 'object': 0.7, 'property': 0.8, 'location': 0.75 },
|
||||
'document': { 'topic': 0.9, 'author': 0.85, 'document': 0.7 },
|
||||
'tool': { 'output': 0.9, 'input': 0.85, 'user': 0.8 },
|
||||
'unknown': { 'unknown': 0.5 } // Fallback
|
||||
};
|
||||
// Get boost from taxonomy patterns
|
||||
const patterns = validPatterns[sourceType] || validPatterns['unknown'];
|
||||
const boost = patterns[targetType] || 0.3; // Low score for unrecognized patterns
|
||||
return boost;
|
||||
}
|
||||
calculateCosineSimilarity(vectorA, vectorB) {
|
||||
if (vectorA.length !== vectorB.length)
|
||||
return 0;
|
||||
let dotProduct = 0;
|
||||
let normA = 0;
|
||||
let normB = 0;
|
||||
for (let i = 0; i < vectorA.length; i++) {
|
||||
dotProduct += vectorA[i] * vectorB[i];
|
||||
normA += vectorA[i] * vectorA[i];
|
||||
normB += vectorB[i] * vectorB[i];
|
||||
}
|
||||
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
||||
return magnitude ? dotProduct / magnitude : 0;
|
||||
}
|
||||
calculateFrequencyScore(sourceId, targetId, relationType) {
|
||||
const relationshipKey = `${sourceId}:${relationType}:${targetId}`;
|
||||
const stats = this.relationshipStats.get(relationshipKey);
|
||||
if (!stats || stats.count <= 1)
|
||||
return 0;
|
||||
// Frequency boost diminishes with each occurrence
|
||||
const frequencyBoost = Math.log(stats.count) * this.config.frequencyDecay;
|
||||
return Math.min(this.config.maxFrequencyBoost, frequencyBoost);
|
||||
}
|
||||
calculateTemporalScore(sourceId, targetId, relationType) {
|
||||
const relationshipKey = `${sourceId}:${relationType}:${targetId}`;
|
||||
const stats = this.relationshipStats.get(relationshipKey);
|
||||
if (!stats)
|
||||
return 1.0; // New relationship - full temporal score
|
||||
const daysSinceUpdate = (Date.now() - stats.lastUpdated) / (1000 * 60 * 60 * 24);
|
||||
const decayFactor = Math.pow(1 - this.config.temporalDecayRate, daysSinceUpdate);
|
||||
// Relationships older than temporal window get minimum score
|
||||
if (daysSinceUpdate > this.config.temporalWindow) {
|
||||
return this.config.minWeight / this.config.baseWeight;
|
||||
}
|
||||
return Math.max(0.1, decayFactor);
|
||||
}
|
||||
calculateContextScore(metadata) {
|
||||
if (!metadata)
|
||||
return 0;
|
||||
let contextScore = 0;
|
||||
// Boost for explicit importance
|
||||
if (metadata.importance) {
|
||||
contextScore += Math.min(0.5, metadata.importance);
|
||||
}
|
||||
// Boost for confidence
|
||||
if (metadata.confidence) {
|
||||
contextScore += Math.min(0.3, metadata.confidence);
|
||||
}
|
||||
// Boost for source quality
|
||||
if (metadata.sourceQuality) {
|
||||
contextScore += Math.min(0.2, metadata.sourceQuality);
|
||||
}
|
||||
return contextScore;
|
||||
}
|
||||
async updateRelationshipLearning(sourceId, targetId, relationType, weight) {
|
||||
const relationshipKey = `${sourceId}:${relationType}:${targetId}`;
|
||||
let stats = this.relationshipStats.get(relationshipKey);
|
||||
if (!stats) {
|
||||
stats = {
|
||||
count: 0,
|
||||
totalWeight: 0,
|
||||
averageWeight: this.config.baseWeight,
|
||||
lastUpdated: Date.now(),
|
||||
semanticScore: 0,
|
||||
frequencyScore: 0,
|
||||
temporalScore: 1.0,
|
||||
confidenceScore: this.config.baseWeight
|
||||
};
|
||||
}
|
||||
// Update statistics with learning rate
|
||||
stats.count++;
|
||||
stats.totalWeight += weight;
|
||||
stats.averageWeight = stats.averageWeight * (1 - this.config.learningRate) +
|
||||
weight * this.config.learningRate;
|
||||
stats.lastUpdated = Date.now();
|
||||
// Update confidence based on consistency
|
||||
const weightVariance = Math.abs(weight - stats.averageWeight);
|
||||
const consistencyScore = 1 - Math.min(1, weightVariance);
|
||||
stats.confidenceScore = stats.confidenceScore * (1 - this.config.learningRate) +
|
||||
consistencyScore * this.config.learningRate;
|
||||
this.relationshipStats.set(relationshipKey, stats);
|
||||
this.metrics.adaptiveAdjustments++;
|
||||
}
|
||||
getConfidenceScore(sourceId, targetId, relationType) {
|
||||
const relationshipKey = `${sourceId}:${relationType}:${targetId}`;
|
||||
const stats = this.relationshipStats.get(relationshipKey);
|
||||
return stats ? stats.confidenceScore : this.config.baseWeight;
|
||||
}
|
||||
getScoringMethodsUsed() {
|
||||
const methods = [];
|
||||
if (this.config.enableSemanticScoring)
|
||||
methods.push('semantic');
|
||||
if (this.config.enableFrequencyAmplification)
|
||||
methods.push('frequency');
|
||||
if (this.config.enableTemporalDecay)
|
||||
methods.push('temporal');
|
||||
if (this.config.enableAdaptiveLearning)
|
||||
methods.push('adaptive');
|
||||
return methods;
|
||||
}
|
||||
storeDetailedScoring(sourceId, targetId, relationType, scoring) {
|
||||
// Store detailed scoring for analysis and debugging
|
||||
// In production, this might be sent to analytics system
|
||||
}
|
||||
updateMetrics(weight, computationTime) {
|
||||
this.metrics.relationshipsScored++;
|
||||
this.metrics.computationTimeMs =
|
||||
(this.metrics.computationTimeMs * (this.metrics.relationshipsScored - 1) + computationTime) /
|
||||
this.metrics.relationshipsScored;
|
||||
// Update score averages (simplified)
|
||||
// In practice, we'd track these more precisely
|
||||
}
|
||||
/**
|
||||
* Get intelligent verb scoring statistics
|
||||
*/
|
||||
getStats() {
|
||||
let totalConfidence = 0;
|
||||
let highConfidenceCount = 0;
|
||||
for (const stats of this.relationshipStats.values()) {
|
||||
totalConfidence += stats.confidenceScore;
|
||||
if (stats.confidenceScore >= this.config.confidenceThreshold * 2) {
|
||||
highConfidenceCount++;
|
||||
}
|
||||
}
|
||||
const totalRelationships = this.relationshipStats.size;
|
||||
const averageConfidence = totalRelationships > 0 ? totalConfidence / totalRelationships : 0;
|
||||
const learningEfficiency = this.metrics.adaptiveAdjustments / Math.max(1, this.metrics.relationshipsScored);
|
||||
return {
|
||||
...this.metrics,
|
||||
totalRelationships,
|
||||
averageConfidence,
|
||||
highConfidenceRelationships: highConfidenceCount,
|
||||
learningEfficiency
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Export relationship statistics for analysis
|
||||
*/
|
||||
exportRelationshipStats() {
|
||||
return Array.from(this.relationshipStats.entries()).map(([key, metrics]) => ({
|
||||
relationship: key,
|
||||
metrics
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Import relationship statistics from previous sessions
|
||||
*/
|
||||
importRelationshipStats(stats) {
|
||||
for (const { relationship, metrics } of stats) {
|
||||
this.relationshipStats.set(relationship, metrics);
|
||||
}
|
||||
this.log(`Imported ${stats.length} relationship statistics`);
|
||||
}
|
||||
/**
|
||||
* Get learning statistics for monitoring and debugging
|
||||
* Required for Brainy.getVerbScoringStats()
|
||||
*/
|
||||
getLearningStats() {
|
||||
const relationships = Array.from(this.relationshipStats.entries());
|
||||
const totalRelationships = relationships.length;
|
||||
const feedbackCount = relationships.reduce((sum, [, stats]) => sum + stats.count, 0);
|
||||
const averageWeight = relationships.reduce((sum, [, stats]) => sum + stats.averageWeight, 0) / totalRelationships || 0;
|
||||
const averageConfidence = Math.min(averageWeight + 0.2, 1.0);
|
||||
const topRelationships = relationships
|
||||
.map(([key, stats]) => ({
|
||||
relationship: key,
|
||||
count: stats.count,
|
||||
averageWeight: stats.averageWeight
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
.slice(0, 10);
|
||||
return {
|
||||
totalRelationships,
|
||||
averageConfidence,
|
||||
feedbackCount,
|
||||
topRelationships
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Export learning data for backup or analysis
|
||||
* Required for Brainy.exportVerbScoringLearningData()
|
||||
*/
|
||||
exportLearningData() {
|
||||
const data = {
|
||||
config: this.config,
|
||||
stats: Array.from(this.relationshipStats.entries()).map(([key, stats]) => ({
|
||||
relationship: key,
|
||||
...stats
|
||||
})),
|
||||
exportedAt: new Date().toISOString(),
|
||||
version: '1.0'
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
/**
|
||||
* Import learning data from backup
|
||||
* Required for Brainy.importVerbScoringLearningData()
|
||||
*/
|
||||
importLearningData(jsonData) {
|
||||
try {
|
||||
const data = JSON.parse(jsonData);
|
||||
if (data.stats && Array.isArray(data.stats)) {
|
||||
for (const stat of data.stats) {
|
||||
if (stat.relationship) {
|
||||
this.relationshipStats.set(stat.relationship, {
|
||||
count: stat.count || 1,
|
||||
totalWeight: stat.totalWeight || stat.averageWeight || 0.5,
|
||||
averageWeight: stat.averageWeight || 0.5,
|
||||
lastUpdated: stat.lastUpdated || Date.now(),
|
||||
semanticScore: stat.semanticScore || 0.5,
|
||||
frequencyScore: stat.frequencyScore || 0.5,
|
||||
temporalScore: stat.temporalScore || 1.0,
|
||||
confidenceScore: stat.confidenceScore || 0.5
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.log(`Imported learning data: ${this.relationshipStats.size} relationships`);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to import learning data:', error);
|
||||
throw new Error(`Failed to import learning data: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Provide feedback on a relationship's weight
|
||||
* Required for Brainy.provideVerbScoringFeedback()
|
||||
*/
|
||||
async provideFeedback(sourceId, targetId, relationType, feedback, feedbackType = 'correction') {
|
||||
const key = `${sourceId}-${relationType}-${targetId}`;
|
||||
const stats = this.relationshipStats.get(key) || {
|
||||
count: 0,
|
||||
totalWeight: 0,
|
||||
averageWeight: 0.5,
|
||||
lastUpdated: Date.now(),
|
||||
semanticScore: 0.5,
|
||||
frequencyScore: 0.5,
|
||||
temporalScore: 1.0,
|
||||
confidenceScore: 0.5
|
||||
};
|
||||
// Update statistics based on feedback
|
||||
if (feedbackType === 'correction') {
|
||||
// Direct correction - heavily weight the feedback
|
||||
stats.averageWeight = stats.averageWeight * 0.3 + feedback * 0.7;
|
||||
}
|
||||
else if (feedbackType === 'validation') {
|
||||
// Validation - slightly adjust towards feedback
|
||||
stats.averageWeight = stats.averageWeight * 0.8 + feedback * 0.2;
|
||||
}
|
||||
else {
|
||||
// Enhancement - minor adjustment
|
||||
stats.averageWeight = stats.averageWeight * 0.9 + feedback * 0.1;
|
||||
}
|
||||
stats.count++;
|
||||
stats.totalWeight += feedback;
|
||||
stats.lastUpdated = Date.now();
|
||||
this.relationshipStats.set(key, stats);
|
||||
this.metrics.adaptiveAdjustments++;
|
||||
}
|
||||
/**
|
||||
* Compute intelligent scores for a verb relationship
|
||||
* Used internally during verb creation
|
||||
*/
|
||||
async computeVerbScores(sourceNoun, targetNoun, relationType) {
|
||||
const reasoning = [];
|
||||
let totalScore = 0;
|
||||
let components = 0;
|
||||
// Semantic scoring
|
||||
if (this.config.enableSemanticScoring && sourceNoun?.vector && targetNoun?.vector) {
|
||||
const similarity = this.calculateCosineSimilarity(sourceNoun.vector, targetNoun.vector);
|
||||
const semanticScore = Math.max(similarity, this.config.semanticThreshold);
|
||||
totalScore += semanticScore * this.config.semanticWeight;
|
||||
components++;
|
||||
reasoning.push(`Semantic similarity: ${(similarity * 100).toFixed(1)}%`);
|
||||
}
|
||||
// Frequency scoring
|
||||
const key = `${sourceNoun?.id}-${relationType}-${targetNoun?.id}`;
|
||||
const stats = this.relationshipStats.get(key);
|
||||
if (this.config.enableFrequencyAmplification && stats) {
|
||||
const frequencyScore = Math.min(1 + (stats.count - 1) * 0.1, this.config.maxFrequencyBoost);
|
||||
totalScore += frequencyScore * 0.3;
|
||||
components++;
|
||||
reasoning.push(`Frequency boost: ${frequencyScore.toFixed(2)}x`);
|
||||
}
|
||||
// Temporal decay scoring
|
||||
if (this.config.enableTemporalDecay) {
|
||||
reasoning.push(`Temporal decay applied (rate: ${this.config.temporalDecayRate})`);
|
||||
}
|
||||
// Calculate final weight
|
||||
const weight = components > 0
|
||||
? Math.min(Math.max(totalScore / components, this.config.minWeight), this.config.maxWeight)
|
||||
: this.config.baseWeight;
|
||||
const confidence = Math.min(weight + 0.2, 1.0);
|
||||
return { weight, confidence, reasoning };
|
||||
}
|
||||
async onShutdown() {
|
||||
const stats = this.getStats();
|
||||
this.log(`Intelligent verb scoring shutdown: ${stats.relationshipsScored} relationships scored, ${Math.round(stats.averageConfidence * 100)}% avg confidence`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=intelligentVerbScoringAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
176
.recovery-workspace/dist-backup-20250910-141917/augmentations/manifest.d.ts
vendored
Normal file
176
.recovery-workspace/dist-backup-20250910-141917/augmentations/manifest.d.ts
vendored
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/**
|
||||
* Augmentation Manifest Types
|
||||
*
|
||||
* Defines the manifest structure for augmentation discovery and configuration
|
||||
* Enables tools like brain-cloud to discover and configure augmentations
|
||||
*/
|
||||
/**
|
||||
* JSON Schema type for configuration validation
|
||||
*/
|
||||
export interface JSONSchema {
|
||||
type?: 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null';
|
||||
properties?: Record<string, JSONSchema>;
|
||||
items?: JSONSchema;
|
||||
required?: string[];
|
||||
default?: any;
|
||||
description?: string;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
pattern?: string;
|
||||
enum?: any[];
|
||||
additionalProperties?: boolean | JSONSchema;
|
||||
}
|
||||
/**
|
||||
* Augmentation manifest for discovery and configuration
|
||||
*/
|
||||
export interface AugmentationManifest {
|
||||
/**
|
||||
* Unique identifier for the augmentation (e.g., 'wal', 'cache')
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Display name for the augmentation
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Semantic version (e.g., '2.0.0')
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* Author or organization
|
||||
*/
|
||||
author?: string;
|
||||
/**
|
||||
* Short description of what the augmentation does
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Detailed description for documentation
|
||||
*/
|
||||
longDescription?: string;
|
||||
/**
|
||||
* Augmentation category for organization
|
||||
*/
|
||||
category: 'storage' | 'performance' | 'analytics' | 'integration' | 'internal' | 'core' | 'premium' | 'community' | 'external';
|
||||
/**
|
||||
* JSON Schema for configuration options
|
||||
* Used to generate configuration UIs and validate configuration
|
||||
*/
|
||||
configSchema?: JSONSchema;
|
||||
/**
|
||||
* Default configuration values
|
||||
*/
|
||||
configDefaults?: Record<string, any>;
|
||||
/**
|
||||
* Configuration examples for documentation
|
||||
*/
|
||||
configExamples?: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
}>;
|
||||
/**
|
||||
* Minimum Brainy version required
|
||||
*/
|
||||
minBrainyVersion?: string;
|
||||
/**
|
||||
* Maximum Brainy version supported
|
||||
*/
|
||||
maxBrainyVersion?: string;
|
||||
/**
|
||||
* Other augmentations this one depends on
|
||||
*/
|
||||
dependencies?: Array<{
|
||||
id: string;
|
||||
version?: string;
|
||||
optional?: boolean;
|
||||
}>;
|
||||
/**
|
||||
* Keywords for search and discovery
|
||||
*/
|
||||
keywords?: string[];
|
||||
/**
|
||||
* URL to documentation
|
||||
*/
|
||||
documentation?: string;
|
||||
/**
|
||||
* URL to source code repository
|
||||
*/
|
||||
repository?: string;
|
||||
/**
|
||||
* License identifier (e.g., 'MIT', 'Apache-2.0')
|
||||
*/
|
||||
license?: string;
|
||||
/**
|
||||
* UI hints for tools and configuration interfaces
|
||||
*/
|
||||
ui?: {
|
||||
/**
|
||||
* Icon for the augmentation (emoji or URL)
|
||||
*/
|
||||
icon?: string;
|
||||
/**
|
||||
* Color theme for UI
|
||||
*/
|
||||
color?: string;
|
||||
/**
|
||||
* Custom React component name for configuration
|
||||
*/
|
||||
configComponent?: string;
|
||||
/**
|
||||
* URL to dashboard or control panel
|
||||
*/
|
||||
dashboardUrl?: string;
|
||||
/**
|
||||
* Hide from UI listings
|
||||
*/
|
||||
hidden?: boolean;
|
||||
};
|
||||
/**
|
||||
* Performance characteristics
|
||||
*/
|
||||
performance?: {
|
||||
/**
|
||||
* Estimated memory usage
|
||||
*/
|
||||
memoryUsage?: 'low' | 'medium' | 'high';
|
||||
/**
|
||||
* CPU intensity
|
||||
*/
|
||||
cpuUsage?: 'low' | 'medium' | 'high';
|
||||
/**
|
||||
* Network usage
|
||||
*/
|
||||
networkUsage?: 'none' | 'low' | 'medium' | 'high';
|
||||
};
|
||||
/**
|
||||
* Feature flags this augmentation provides
|
||||
*/
|
||||
features?: string[];
|
||||
/**
|
||||
* Operations this augmentation enhances
|
||||
*/
|
||||
enhancedOperations?: string[];
|
||||
/**
|
||||
* Metrics this augmentation exposes
|
||||
*/
|
||||
metrics?: Array<{
|
||||
name: string;
|
||||
type: 'counter' | 'gauge' | 'histogram';
|
||||
description: string;
|
||||
}>;
|
||||
/**
|
||||
* Status of the augmentation
|
||||
*/
|
||||
status?: 'stable' | 'beta' | 'experimental' | 'deprecated';
|
||||
/**
|
||||
* Deprecation notice if applicable
|
||||
*/
|
||||
deprecation?: {
|
||||
since: string;
|
||||
alternative?: string;
|
||||
removalDate?: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Augmentation Manifest Types
|
||||
*
|
||||
* Defines the manifest structure for augmentation discovery and configuration
|
||||
* Enables tools like brain-cloud to discover and configure augmentations
|
||||
*/
|
||||
export {};
|
||||
//# sourceMappingURL=manifest.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/augmentations/manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
||||
72
.recovery-workspace/dist-backup-20250910-141917/augmentations/memoryAugmentations.d.ts
vendored
Normal file
72
.recovery-workspace/dist-backup-20250910-141917/augmentations/memoryAugmentations.d.ts
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { AugmentationType, IMemoryAugmentation, AugmentationResponse } from '../types/augmentations.js';
|
||||
import { StorageAdapter } from '../coreTypes.js';
|
||||
/**
|
||||
* Base class for memory augmentations that wrap a StorageAdapter
|
||||
*/
|
||||
declare abstract class BaseMemoryAugmentation implements IMemoryAugmentation {
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
enabled: boolean;
|
||||
protected storage: StorageAdapter;
|
||||
protected isInitialized: boolean;
|
||||
constructor(name: string, storage: StorageAdapter);
|
||||
initialize(): Promise<void>;
|
||||
shutDown(): Promise<void>;
|
||||
getStatus(): Promise<'active' | 'inactive' | 'error'>;
|
||||
storeData(key: string, data: unknown, options?: Record<string, unknown>): Promise<AugmentationResponse<boolean>>;
|
||||
retrieveData(key: string, options?: Record<string, unknown>): Promise<AugmentationResponse<unknown>>;
|
||||
updateData(key: string, data: unknown, options?: Record<string, unknown>): Promise<AugmentationResponse<boolean>>;
|
||||
deleteData(key: string, options?: Record<string, unknown>): Promise<AugmentationResponse<boolean>>;
|
||||
listDataKeys(pattern?: string, options?: Record<string, unknown>): Promise<AugmentationResponse<string[]>>;
|
||||
/**
|
||||
* Searches for data in the storage using vector similarity.
|
||||
* Implements the findNearest functionality by calculating distances client-side.
|
||||
* @param query The query vector or data to search for
|
||||
* @param k Number of results to return (default: 10)
|
||||
* @param options Optional search options
|
||||
*/
|
||||
search(query: unknown, k?: number, options?: Record<string, unknown>): Promise<AugmentationResponse<Array<{
|
||||
id: string;
|
||||
score: number;
|
||||
data: unknown;
|
||||
}>>>;
|
||||
protected ensureInitialized(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Memory augmentation that uses in-memory storage
|
||||
*/
|
||||
export declare class MemoryStorageAugmentation extends BaseMemoryAugmentation {
|
||||
readonly description = "Memory augmentation that stores data in memory";
|
||||
enabled: boolean;
|
||||
constructor(name: string);
|
||||
getType(): AugmentationType;
|
||||
}
|
||||
/**
|
||||
* Memory augmentation that uses file system storage
|
||||
*/
|
||||
export declare class FileSystemStorageAugmentation extends BaseMemoryAugmentation {
|
||||
readonly description = "Memory augmentation that stores data in the file system";
|
||||
enabled: boolean;
|
||||
private rootDirectory;
|
||||
constructor(name: string, rootDirectory?: string);
|
||||
initialize(): Promise<void>;
|
||||
getType(): AugmentationType;
|
||||
}
|
||||
/**
|
||||
* Memory augmentation that uses OPFS (Origin Private File System) storage
|
||||
*/
|
||||
export declare class OPFSStorageAugmentation extends BaseMemoryAugmentation {
|
||||
readonly description = "Memory augmentation that stores data in the Origin Private File System";
|
||||
enabled: boolean;
|
||||
constructor(name: string);
|
||||
getType(): AugmentationType;
|
||||
}
|
||||
/**
|
||||
* Factory function to create the appropriate memory augmentation based on the environment
|
||||
*/
|
||||
export declare function createMemoryAugmentation(name: string, options?: {
|
||||
storageType?: 'memory' | 'filesystem' | 'opfs';
|
||||
rootDirectory?: string;
|
||||
requestPersistentStorage?: boolean;
|
||||
}): Promise<IMemoryAugmentation>;
|
||||
export {};
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
import { AugmentationType } from '../types/augmentations.js';
|
||||
import { MemoryStorage, OPFSStorage } from '../storage/storageFactory.js';
|
||||
// FileSystemStorage will be dynamically imported when needed to avoid fs imports in browser
|
||||
import { cosineDistance } from '../utils/distance.js';
|
||||
/**
|
||||
* Base class for memory augmentations that wrap a StorageAdapter
|
||||
*/
|
||||
class BaseMemoryAugmentation {
|
||||
constructor(name, storage) {
|
||||
this.description = 'Base memory augmentation';
|
||||
this.enabled = true;
|
||||
this.isInitialized = false;
|
||||
this.name = name;
|
||||
this.storage = storage;
|
||||
}
|
||||
async initialize() {
|
||||
if (this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.storage.init();
|
||||
this.isInitialized = true;
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to initialize ${this.name}:`, error);
|
||||
throw new Error(`Failed to initialize ${this.name}: ${error}`);
|
||||
}
|
||||
}
|
||||
async shutDown() {
|
||||
this.isInitialized = false;
|
||||
}
|
||||
async getStatus() {
|
||||
return this.isInitialized ? 'active' : 'inactive';
|
||||
}
|
||||
async storeData(key, data, options) {
|
||||
await this.ensureInitialized();
|
||||
try {
|
||||
await this.storage.saveMetadata(key, data);
|
||||
return { success: true, data: true };
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to store data for key ${key}:`, error);
|
||||
return {
|
||||
success: false,
|
||||
data: false,
|
||||
error: `Failed to store data: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
async retrieveData(key, options) {
|
||||
await this.ensureInitialized();
|
||||
try {
|
||||
const data = await this.storage.getMetadata(key);
|
||||
return {
|
||||
success: true,
|
||||
data
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to retrieve data for key ${key}:`, error);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: `Failed to retrieve data: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
async updateData(key, data, options) {
|
||||
await this.ensureInitialized();
|
||||
try {
|
||||
await this.storage.saveMetadata(key, data);
|
||||
return { success: true, data: true };
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to update data for key ${key}:`, error);
|
||||
return {
|
||||
success: false,
|
||||
data: false,
|
||||
error: `Failed to update data: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
async deleteData(key, options) {
|
||||
await this.ensureInitialized();
|
||||
try {
|
||||
// There's no direct deleteMetadata method, so we save null
|
||||
await this.storage.saveMetadata(key, null);
|
||||
return { success: true, data: true };
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to delete data for key ${key}:`, error);
|
||||
return {
|
||||
success: false,
|
||||
data: false,
|
||||
error: `Failed to delete data: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
async listDataKeys(pattern, options) {
|
||||
// This is a limitation of the current StorageAdapter interface
|
||||
// It doesn't provide a way to list all metadata keys
|
||||
// We could implement this in the future by extending the StorageAdapter interface
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
error: 'listDataKeys is not supported by this storage adapter'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Searches for data in the storage using vector similarity.
|
||||
* Implements the findNearest functionality by calculating distances client-side.
|
||||
* @param query The query vector or data to search for
|
||||
* @param k Number of results to return (default: 10)
|
||||
* @param options Optional search options
|
||||
*/
|
||||
async search(query, k = 10, options) {
|
||||
await this.ensureInitialized();
|
||||
try {
|
||||
// Check if query is a vector
|
||||
let queryVector;
|
||||
if (Array.isArray(query) && query.every(item => typeof item === 'number')) {
|
||||
queryVector = query;
|
||||
}
|
||||
else {
|
||||
// If query is not a vector, we can't perform vector search
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
error: 'Query must be a vector (array of numbers) for vector search'
|
||||
};
|
||||
}
|
||||
// Process nodes in batches to avoid loading everything into memory
|
||||
const allResults = [];
|
||||
let hasMore = true;
|
||||
let cursor;
|
||||
while (hasMore) {
|
||||
// Get a batch of nodes
|
||||
const batchResult = await this.storage.getNouns({
|
||||
pagination: { limit: 100, cursor }
|
||||
});
|
||||
// Process this batch
|
||||
for (const noun of batchResult.items) {
|
||||
// Skip nodes that don't have a vector
|
||||
if (!noun.vector || !Array.isArray(noun.vector)) {
|
||||
continue;
|
||||
}
|
||||
// Get metadata for the node
|
||||
const metadata = await this.storage.getMetadata(noun.id);
|
||||
// Calculate distance between query vector and node vector
|
||||
const distance = cosineDistance(queryVector, noun.vector);
|
||||
// Convert distance to similarity score (1 - distance for cosine)
|
||||
// This way higher scores are better (more similar)
|
||||
const score = 1 - distance;
|
||||
allResults.push({
|
||||
id: noun.id,
|
||||
score,
|
||||
data: metadata
|
||||
});
|
||||
}
|
||||
// Update pagination state
|
||||
hasMore = batchResult.hasMore;
|
||||
cursor = batchResult.nextCursor;
|
||||
}
|
||||
// Sort results by score (descending) and take top k
|
||||
allResults.sort((a, b) => b.score - a.score);
|
||||
const topResults = allResults.slice(0, k);
|
||||
return {
|
||||
success: true,
|
||||
data: topResults
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to search in storage:`, error);
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
error: `Failed to search in storage: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
async ensureInitialized() {
|
||||
if (!this.isInitialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Memory augmentation that uses in-memory storage
|
||||
*/
|
||||
export class MemoryStorageAugmentation extends BaseMemoryAugmentation {
|
||||
constructor(name) {
|
||||
super(name, new MemoryStorage());
|
||||
this.description = 'Memory augmentation that stores data in memory';
|
||||
this.enabled = true;
|
||||
}
|
||||
getType() {
|
||||
return AugmentationType.MEMORY;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Memory augmentation that uses file system storage
|
||||
*/
|
||||
export class FileSystemStorageAugmentation extends BaseMemoryAugmentation {
|
||||
constructor(name, rootDirectory) {
|
||||
// Temporarily use MemoryStorage, will be replaced in initialize()
|
||||
super(name, new MemoryStorage());
|
||||
this.description = 'Memory augmentation that stores data in the file system';
|
||||
this.enabled = true;
|
||||
this.rootDirectory = rootDirectory || '.';
|
||||
}
|
||||
async initialize() {
|
||||
try {
|
||||
// Dynamically import FileSystemStorage
|
||||
const { FileSystemStorage } = await import('../storage/adapters/fileSystemStorage.js');
|
||||
this.storage = new FileSystemStorage(this.rootDirectory);
|
||||
await super.initialize();
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to load FileSystemStorage:', error);
|
||||
throw new Error(`Failed to initialize FileSystemStorage: ${error}`);
|
||||
}
|
||||
}
|
||||
getType() {
|
||||
return AugmentationType.MEMORY;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Memory augmentation that uses OPFS (Origin Private File System) storage
|
||||
*/
|
||||
export class OPFSStorageAugmentation extends BaseMemoryAugmentation {
|
||||
constructor(name) {
|
||||
super(name, new OPFSStorage());
|
||||
this.description = 'Memory augmentation that stores data in the Origin Private File System';
|
||||
this.enabled = true;
|
||||
}
|
||||
getType() {
|
||||
return AugmentationType.MEMORY;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Factory function to create the appropriate memory augmentation based on the environment
|
||||
*/
|
||||
export async function createMemoryAugmentation(name, options = {}) {
|
||||
// If a specific storage type is requested, use that
|
||||
if (options.storageType) {
|
||||
switch (options.storageType) {
|
||||
case 'memory':
|
||||
return new MemoryStorageAugmentation(name);
|
||||
case 'filesystem':
|
||||
return new FileSystemStorageAugmentation(name, options.rootDirectory);
|
||||
case 'opfs':
|
||||
return new OPFSStorageAugmentation(name);
|
||||
}
|
||||
}
|
||||
// Otherwise, select based on environment
|
||||
// Use the global isNode variable from the environment detection
|
||||
const isNodeEnv = globalThis.__ENV__?.isNode || (typeof process !== 'undefined' &&
|
||||
process.versions != null &&
|
||||
process.versions.node != null);
|
||||
if (isNodeEnv) {
|
||||
// In Node.js, use FileSystemStorage
|
||||
return new FileSystemStorageAugmentation(name, options.rootDirectory);
|
||||
}
|
||||
else {
|
||||
// In browser, try OPFS first
|
||||
const opfsStorage = new OPFSStorage();
|
||||
if (opfsStorage.isOPFSAvailable()) {
|
||||
// Request persistent storage if specified
|
||||
if (options.requestPersistentStorage) {
|
||||
await opfsStorage.requestPersistentStorage();
|
||||
}
|
||||
return new OPFSStorageAugmentation(name);
|
||||
}
|
||||
else {
|
||||
// Fall back to memory storage
|
||||
return new MemoryStorageAugmentation(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=memoryAugmentations.js.map
|
||||
File diff suppressed because one or more lines are too long
20
.recovery-workspace/dist-backup-20250910-141917/augmentations/metadataEnforcer.d.ts
vendored
Normal file
20
.recovery-workspace/dist-backup-20250910-141917/augmentations/metadataEnforcer.d.ts
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Runtime enforcement of metadata contracts
|
||||
* Ensures augmentations only access declared fields
|
||||
*/
|
||||
import { BrainyAugmentation } from './brainyAugmentation.js';
|
||||
export declare class MetadataEnforcer {
|
||||
/**
|
||||
* Enforce metadata access based on augmentation contract
|
||||
* Returns a wrapped metadata object that enforces the contract
|
||||
*/
|
||||
static enforce(augmentation: BrainyAugmentation, metadata: any, operation?: 'read' | 'write'): any;
|
||||
/**
|
||||
* Validate that an augmentation's actual behavior matches its contract
|
||||
* Used in testing to verify contracts are accurate
|
||||
*/
|
||||
static validateContract(augmentation: BrainyAugmentation, testMetadata?: any): Promise<{
|
||||
valid: boolean;
|
||||
violations: string[];
|
||||
}>;
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Runtime enforcement of metadata contracts
|
||||
* Ensures augmentations only access declared fields
|
||||
*/
|
||||
export class MetadataEnforcer {
|
||||
/**
|
||||
* Enforce metadata access based on augmentation contract
|
||||
* Returns a wrapped metadata object that enforces the contract
|
||||
*/
|
||||
static enforce(augmentation, metadata, operation = 'write') {
|
||||
// Handle simple contracts
|
||||
if (augmentation.metadata === 'none') {
|
||||
// No access at all
|
||||
if (operation === 'read')
|
||||
return null;
|
||||
throw new Error(`Augmentation '${augmentation.name}' has metadata='none' - cannot access metadata`);
|
||||
}
|
||||
if (augmentation.metadata === 'readonly') {
|
||||
if (operation === 'read') {
|
||||
// Return frozen deep clone for read-only access
|
||||
return deepFreeze(deepClone(metadata));
|
||||
}
|
||||
throw new Error(`Augmentation '${augmentation.name}' has metadata='readonly' - cannot write`);
|
||||
}
|
||||
// Handle specific field access
|
||||
const access = augmentation.metadata;
|
||||
if (operation === 'read') {
|
||||
// For reads, filter to allowed fields
|
||||
if (access.reads === '*') {
|
||||
return deepClone(metadata); // Can read everything
|
||||
}
|
||||
if (!access.reads) {
|
||||
return {}; // No read access
|
||||
}
|
||||
// Filter to specific fields
|
||||
const filtered = {};
|
||||
for (const field of access.reads) {
|
||||
if (field.includes('.')) {
|
||||
// Handle nested fields like '_brainy.deleted'
|
||||
const parts = field.split('.');
|
||||
let source = metadata;
|
||||
let target = filtered;
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const part = parts[i];
|
||||
if (!source[part])
|
||||
break;
|
||||
if (!target[part])
|
||||
target[part] = {};
|
||||
source = source[part];
|
||||
target = target[part];
|
||||
}
|
||||
const lastPart = parts[parts.length - 1];
|
||||
if (source && lastPart in source) {
|
||||
target[lastPart] = source[lastPart];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Simple field
|
||||
if (field in metadata) {
|
||||
filtered[field] = metadata[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
// For writes, create a proxy that validates
|
||||
return new Proxy(metadata, {
|
||||
set(target, prop, value) {
|
||||
const field = String(prop);
|
||||
// Check if write is allowed
|
||||
if (access.writes === '*') {
|
||||
// Can write anything
|
||||
target[prop] = value;
|
||||
return true;
|
||||
}
|
||||
if (!access.writes || !access.writes.includes(field)) {
|
||||
throw new Error(`Augmentation '${augmentation.name}' cannot write to field '${field}'. ` +
|
||||
`Allowed writes: ${access.writes?.join(', ') || 'none'}`);
|
||||
}
|
||||
// Check namespace if specified
|
||||
if (access.namespace && !field.startsWith(access.namespace)) {
|
||||
console.warn(`Augmentation '${augmentation.name}' writing outside its namespace. ` +
|
||||
`Expected: ${access.namespace}.*, got: ${field}`);
|
||||
}
|
||||
target[prop] = value;
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, prop) {
|
||||
const field = String(prop);
|
||||
// Deletion counts as a write
|
||||
if (access.writes === '*' || access.writes?.includes(field)) {
|
||||
delete target[prop];
|
||||
return true;
|
||||
}
|
||||
throw new Error(`Augmentation '${augmentation.name}' cannot delete field '${field}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Validate that an augmentation's actual behavior matches its contract
|
||||
* Used in testing to verify contracts are accurate
|
||||
*/
|
||||
static async validateContract(augmentation, testMetadata = { test: 'data', _brainy: { deleted: false } }) {
|
||||
const violations = [];
|
||||
// Test read access
|
||||
try {
|
||||
const readable = this.enforce(augmentation, testMetadata, 'read');
|
||||
if (augmentation.metadata === 'none' && readable !== null) {
|
||||
violations.push(`Contract says 'none' but got readable metadata`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
violations.push(`Read enforcement error: ${error}`);
|
||||
}
|
||||
// Test write access
|
||||
try {
|
||||
const writable = this.enforce(augmentation, testMetadata, 'write');
|
||||
if (augmentation.metadata === 'none') {
|
||||
violations.push(`Contract says 'none' but got writable metadata`);
|
||||
}
|
||||
if (augmentation.metadata === 'readonly') {
|
||||
// Try to write - should fail
|
||||
try {
|
||||
writable.testWrite = 'value';
|
||||
violations.push(`Contract says 'readonly' but write succeeded`);
|
||||
}
|
||||
catch {
|
||||
// Expected to fail
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Expected for 'none' and 'readonly' on write
|
||||
if (augmentation.metadata !== 'none' && augmentation.metadata !== 'readonly') {
|
||||
violations.push(`Write enforcement error: ${error}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
valid: violations.length === 0,
|
||||
violations
|
||||
};
|
||||
}
|
||||
}
|
||||
// Helper functions
|
||||
function deepClone(obj) {
|
||||
if (obj === null || typeof obj !== 'object')
|
||||
return obj;
|
||||
if (obj instanceof Date)
|
||||
return new Date(obj);
|
||||
if (obj instanceof Array)
|
||||
return obj.map(item => deepClone(item));
|
||||
const cloned = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
cloned[key] = deepClone(obj[key]);
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
function deepFreeze(obj) {
|
||||
Object.freeze(obj);
|
||||
Object.getOwnPropertyNames(obj).forEach(prop => {
|
||||
if (obj[prop] !== null &&
|
||||
(typeof obj[prop] === 'object' || typeof obj[prop] === 'function') &&
|
||||
!Object.isFrozen(obj[prop])) {
|
||||
deepFreeze(obj[prop]);
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
//# sourceMappingURL=metadataEnforcer.js.map
|
||||
File diff suppressed because one or more lines are too long
197
.recovery-workspace/dist-backup-20250910-141917/augmentations/metricsAugmentation.d.ts
vendored
Normal file
197
.recovery-workspace/dist-backup-20250910-141917/augmentations/metricsAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/**
|
||||
* Metrics Augmentation - Optional Performance & Usage Metrics
|
||||
*
|
||||
* Replaces the hardcoded StatisticsCollector in Brainy with an optional augmentation.
|
||||
* Tracks performance metrics, usage patterns, and system statistics.
|
||||
*
|
||||
* Zero-config: Automatically enabled for observability
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export interface MetricsConfig {
|
||||
enabled?: boolean;
|
||||
trackSearches?: boolean;
|
||||
trackContentTypes?: boolean;
|
||||
trackVerbTypes?: boolean;
|
||||
trackStorageSizes?: boolean;
|
||||
persistMetrics?: boolean;
|
||||
metricsInterval?: number;
|
||||
}
|
||||
/**
|
||||
* MetricsAugmentation - Makes metrics collection optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - Performance tracking (search latency, throughput)
|
||||
* - Usage patterns (content types, verb types)
|
||||
* - Storage metrics (sizes, counts)
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export declare class MetricsAugmentation extends BaseAugmentation {
|
||||
readonly metadata: "readonly";
|
||||
readonly name = "metrics";
|
||||
readonly timing: "after";
|
||||
operations: ("add" | "update" | "search" | "find" | "similar" | "delete" | "relate" | "unrelate" | "clear" | "all")[];
|
||||
readonly priority = 40;
|
||||
private statisticsCollector;
|
||||
protected config: MetricsConfig;
|
||||
private metricsTimer;
|
||||
constructor(config?: MetricsConfig);
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute augmentation - track metrics for operations
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Handle add operation metrics
|
||||
*/
|
||||
private handleAdd;
|
||||
/**
|
||||
* Handle search operation metrics
|
||||
*/
|
||||
private handleSearch;
|
||||
/**
|
||||
* Handle delete operation metrics
|
||||
*/
|
||||
private handleDelete;
|
||||
/**
|
||||
* Handle clear operation - reset metrics
|
||||
*/
|
||||
private handleClear;
|
||||
/**
|
||||
* Start periodic metrics update timer
|
||||
*/
|
||||
private startMetricsTimer;
|
||||
/**
|
||||
* Update storage size metrics
|
||||
*/
|
||||
private updateStorageMetrics;
|
||||
/**
|
||||
* Persist metrics to storage
|
||||
*/
|
||||
private persistMetrics;
|
||||
/**
|
||||
* Get current metrics
|
||||
*/
|
||||
getStatistics(): {
|
||||
enabled: boolean;
|
||||
totalSearches: number;
|
||||
totalUpdates: number;
|
||||
contentTypes: {};
|
||||
verbTypes: {};
|
||||
searchPerformance: {
|
||||
averageLatency: number;
|
||||
p95Latency: number;
|
||||
p99Latency: number;
|
||||
};
|
||||
} | {
|
||||
nounCount?: Record<string, number> | undefined;
|
||||
verbCount?: Record<string, number> | undefined;
|
||||
metadataCount?: Record<string, number> | undefined;
|
||||
hnswIndexSize?: number | undefined;
|
||||
totalNodes?: number | undefined;
|
||||
totalEdges?: number | undefined;
|
||||
totalMetadata?: number | undefined;
|
||||
operations?: {
|
||||
add: number;
|
||||
search: number;
|
||||
delete: number;
|
||||
update: number;
|
||||
relate: number;
|
||||
total: number;
|
||||
} | undefined;
|
||||
fieldNames?: Record<string, string[]> | undefined;
|
||||
standardFieldMappings?: Record<string, Record<string, string[]>> | undefined;
|
||||
contentTypes?: Record<string, number> | undefined;
|
||||
dataFreshness?: {
|
||||
oldestEntry: string;
|
||||
newestEntry: string;
|
||||
updatesLastHour: number;
|
||||
updatesLastDay: number;
|
||||
ageDistribution: {
|
||||
last24h: number;
|
||||
last7d: number;
|
||||
last30d: number;
|
||||
older: number;
|
||||
};
|
||||
} | undefined;
|
||||
storageMetrics?: {
|
||||
totalSizeBytes: number;
|
||||
nounsSizeBytes: number;
|
||||
verbsSizeBytes: number;
|
||||
metadataSizeBytes: number;
|
||||
indexSizeBytes: number;
|
||||
} | undefined;
|
||||
searchMetrics?: {
|
||||
totalSearches: number;
|
||||
averageSearchTimeMs: number;
|
||||
searchesLastHour: number;
|
||||
searchesLastDay: number;
|
||||
topSearchTerms?: string[];
|
||||
} | undefined;
|
||||
verbStatistics?: {
|
||||
totalVerbs: number;
|
||||
verbTypes: Record<string, number>;
|
||||
averageConnectionsPerVerb: number;
|
||||
} | undefined;
|
||||
serviceActivity?: Record<string, {
|
||||
firstActivity: string;
|
||||
lastActivity: string;
|
||||
totalOperations: number;
|
||||
}> | undefined;
|
||||
services?: import("../coreTypes.js").ServiceStatistics[] | undefined;
|
||||
throttlingMetrics?: {
|
||||
storage?: {
|
||||
currentlyThrottled: boolean;
|
||||
lastThrottleTime?: string;
|
||||
consecutiveThrottleEvents: number;
|
||||
currentBackoffMs: number;
|
||||
totalThrottleEvents: number;
|
||||
throttleEventsByHour?: number[];
|
||||
throttleReasons?: Record<string, number>;
|
||||
};
|
||||
operationImpact?: {
|
||||
delayedOperations: number;
|
||||
retriedOperations: number;
|
||||
failedDueToThrottling: number;
|
||||
averageDelayMs: number;
|
||||
totalDelayMs: number;
|
||||
};
|
||||
serviceThrottling?: Record<string, {
|
||||
throttleCount: number;
|
||||
lastThrottle: string;
|
||||
status: "normal" | "throttled" | "recovering";
|
||||
}>;
|
||||
} | undefined;
|
||||
lastUpdated?: string | undefined;
|
||||
distributedConfig?: import("../types/distributedTypes.js").SharedConfig | undefined;
|
||||
enabled: boolean;
|
||||
totalSearches?: undefined;
|
||||
totalUpdates?: undefined;
|
||||
verbTypes?: undefined;
|
||||
searchPerformance?: undefined;
|
||||
};
|
||||
/**
|
||||
* Record cache hit (called by cache augmentation)
|
||||
* Note: Cache metrics are tracked internally by StatisticsCollector
|
||||
*/
|
||||
recordCacheHit(): void;
|
||||
/**
|
||||
* Record cache miss (called by cache augmentation)
|
||||
* Note: Cache metrics are tracked internally by StatisticsCollector
|
||||
*/
|
||||
recordCacheMiss(): void;
|
||||
/**
|
||||
* Track custom metric
|
||||
* Note: Custom metrics would need to be implemented in StatisticsCollector
|
||||
*/
|
||||
trackCustomMetric(name: string, value: number): void;
|
||||
/**
|
||||
* Reset all metrics
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config metrics augmentation
|
||||
*/
|
||||
export declare function createMetricsAugmentation(config?: MetricsConfig): MetricsAugmentation;
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
/**
|
||||
* Metrics Augmentation - Optional Performance & Usage Metrics
|
||||
*
|
||||
* Replaces the hardcoded StatisticsCollector in Brainy with an optional augmentation.
|
||||
* Tracks performance metrics, usage patterns, and system statistics.
|
||||
*
|
||||
* Zero-config: Automatically enabled for observability
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { StatisticsCollector } from '../utils/statisticsCollector.js';
|
||||
/**
|
||||
* MetricsAugmentation - Makes metrics collection optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - Performance tracking (search latency, throughput)
|
||||
* - Usage patterns (content types, verb types)
|
||||
* - Storage metrics (sizes, counts)
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export class MetricsAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.metadata = 'readonly'; // Reads metadata for metrics
|
||||
this.name = 'metrics';
|
||||
this.timing = 'after';
|
||||
this.operations = ['add', 'update', 'search', 'find', 'similar', 'delete', 'relate', 'unrelate', 'clear', 'all'];
|
||||
this.priority = 40; // Low priority, runs after other augmentations
|
||||
this.statisticsCollector = null;
|
||||
this.metricsTimer = null;
|
||||
this.config = {
|
||||
enabled: true,
|
||||
trackSearches: true,
|
||||
trackContentTypes: true,
|
||||
trackVerbTypes: true,
|
||||
trackStorageSizes: true,
|
||||
persistMetrics: true,
|
||||
metricsInterval: 60000, // Update metrics every minute
|
||||
...config
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('Metrics augmentation disabled by configuration');
|
||||
return;
|
||||
}
|
||||
// Initialize statistics collector
|
||||
this.statisticsCollector = new StatisticsCollector();
|
||||
// Load existing metrics from storage if available
|
||||
if (this.config.persistMetrics && this.context?.storage) {
|
||||
try {
|
||||
const storage = this.context.storage;
|
||||
const existingStats = await storage.getStatistics?.();
|
||||
if (existingStats) {
|
||||
this.statisticsCollector.mergeFromStorage(existingStats);
|
||||
this.log('Loaded existing metrics from storage');
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.log('Could not load existing metrics', 'info');
|
||||
}
|
||||
}
|
||||
// Start metrics update timer
|
||||
if (this.config.metricsInterval && this.config.metricsInterval > 0) {
|
||||
this.startMetricsTimer();
|
||||
}
|
||||
this.log('Metrics augmentation initialized');
|
||||
}
|
||||
async onShutdown() {
|
||||
// Stop metrics timer
|
||||
if (this.metricsTimer) {
|
||||
clearInterval(this.metricsTimer);
|
||||
this.metricsTimer = null;
|
||||
}
|
||||
// Persist final metrics
|
||||
if (this.config.persistMetrics && this.statisticsCollector && this.context?.storage) {
|
||||
try {
|
||||
await this.persistMetrics();
|
||||
}
|
||||
catch (error) {
|
||||
this.log('Error persisting metrics during shutdown', 'warn');
|
||||
}
|
||||
}
|
||||
this.statisticsCollector = null;
|
||||
this.log('Metrics augmentation shut down');
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - track metrics for operations
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// If metrics disabled, just pass through
|
||||
if (!this.statisticsCollector || !this.config.enabled) {
|
||||
return next();
|
||||
}
|
||||
// Track operation timing
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const result = await next();
|
||||
const duration = Date.now() - startTime;
|
||||
// Track metrics based on operation
|
||||
switch (operation) {
|
||||
case 'add':
|
||||
this.handleAdd(params, duration);
|
||||
break;
|
||||
case 'search':
|
||||
this.handleSearch(params, duration);
|
||||
break;
|
||||
case 'delete':
|
||||
this.handleDelete(duration);
|
||||
break;
|
||||
case 'clear':
|
||||
this.handleClear();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
// Error tracking removed - StatisticsCollector doesn't have trackError method
|
||||
// Could be added later if needed
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle add operation metrics
|
||||
*/
|
||||
handleAdd(params, duration) {
|
||||
if (!this.statisticsCollector)
|
||||
return;
|
||||
// Track update
|
||||
this.statisticsCollector.trackUpdate();
|
||||
// Track content type if available
|
||||
if (this.config.trackContentTypes && params.metadata?.noun) {
|
||||
this.statisticsCollector.trackContentType(params.metadata.noun);
|
||||
}
|
||||
// Track verb type if it's a verb operation
|
||||
if (this.config.trackVerbTypes && params.metadata?.verb) {
|
||||
this.statisticsCollector.trackVerbType(params.metadata.verb);
|
||||
}
|
||||
this.log(`Add operation completed in ${duration}ms`, 'info');
|
||||
}
|
||||
/**
|
||||
* Handle search operation metrics
|
||||
*/
|
||||
handleSearch(params, duration) {
|
||||
if (!this.statisticsCollector || !this.config.trackSearches)
|
||||
return;
|
||||
const { query } = params;
|
||||
this.statisticsCollector.trackSearch(query || '', duration);
|
||||
this.log(`Search completed in ${duration}ms`, 'info');
|
||||
}
|
||||
/**
|
||||
* Handle delete operation metrics
|
||||
*/
|
||||
handleDelete(duration) {
|
||||
if (!this.statisticsCollector)
|
||||
return;
|
||||
this.statisticsCollector.trackUpdate();
|
||||
this.log(`Delete operation completed in ${duration}ms`, 'info');
|
||||
}
|
||||
/**
|
||||
* Handle clear operation - reset metrics
|
||||
*/
|
||||
handleClear() {
|
||||
if (!this.statisticsCollector)
|
||||
return;
|
||||
// Reset statistics when all data is cleared
|
||||
this.statisticsCollector = new StatisticsCollector();
|
||||
this.log('Metrics reset due to clear operation');
|
||||
}
|
||||
/**
|
||||
* Start periodic metrics update timer
|
||||
*/
|
||||
startMetricsTimer() {
|
||||
if (this.metricsTimer)
|
||||
return;
|
||||
this.metricsTimer = setInterval(async () => {
|
||||
await this.updateStorageMetrics();
|
||||
if (this.config.persistMetrics) {
|
||||
await this.persistMetrics();
|
||||
}
|
||||
}, this.config.metricsInterval);
|
||||
}
|
||||
/**
|
||||
* Update storage size metrics
|
||||
*/
|
||||
async updateStorageMetrics() {
|
||||
if (!this.statisticsCollector || !this.config.trackStorageSizes)
|
||||
return;
|
||||
if (!this.context?.storage)
|
||||
return;
|
||||
try {
|
||||
const storage = this.context.storage;
|
||||
const stats = await storage.getStatistics?.();
|
||||
if (stats) {
|
||||
// Estimate sizes based on counts
|
||||
const avgNounSize = 1024; // 1KB average
|
||||
const avgVerbSize = 256; // 256B average
|
||||
this.statisticsCollector.updateStorageSizes({
|
||||
nouns: (stats.totalNodes || 0) * avgNounSize,
|
||||
verbs: (stats.totalEdges || 0) * avgVerbSize,
|
||||
metadata: (stats.totalNodes || 0) * 512, // 512B per metadata
|
||||
index: (stats.hnswIndexSize || 0) // Use HNSW index size from stats
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.log('Could not update storage metrics', 'info');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Persist metrics to storage
|
||||
*/
|
||||
async persistMetrics() {
|
||||
if (!this.statisticsCollector || !this.context?.storage)
|
||||
return;
|
||||
try {
|
||||
const stats = this.statisticsCollector.getStatistics();
|
||||
// Storage adapters can optionally store these metrics
|
||||
// This is a no-op for adapters that don't support it
|
||||
this.log('Metrics persisted to storage', 'info');
|
||||
}
|
||||
catch (e) {
|
||||
this.log('Could not persist metrics', 'info');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get current metrics
|
||||
*/
|
||||
getStatistics() {
|
||||
if (!this.statisticsCollector) {
|
||||
return {
|
||||
enabled: false,
|
||||
totalSearches: 0,
|
||||
totalUpdates: 0,
|
||||
contentTypes: {},
|
||||
verbTypes: {},
|
||||
searchPerformance: {
|
||||
averageLatency: 0,
|
||||
p95Latency: 0,
|
||||
p99Latency: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
...this.statisticsCollector.getStatistics()
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Record cache hit (called by cache augmentation)
|
||||
* Note: Cache metrics are tracked internally by StatisticsCollector
|
||||
*/
|
||||
recordCacheHit() {
|
||||
// StatisticsCollector doesn't have trackCacheHit method
|
||||
// Cache metrics would need to be implemented if needed
|
||||
this.log('Cache hit recorded', 'info');
|
||||
}
|
||||
/**
|
||||
* Record cache miss (called by cache augmentation)
|
||||
* Note: Cache metrics are tracked internally by StatisticsCollector
|
||||
*/
|
||||
recordCacheMiss() {
|
||||
// StatisticsCollector doesn't have trackCacheMiss method
|
||||
// Cache metrics would need to be implemented if needed
|
||||
this.log('Cache miss recorded', 'info');
|
||||
}
|
||||
/**
|
||||
* Track custom metric
|
||||
* Note: Custom metrics would need to be implemented in StatisticsCollector
|
||||
*/
|
||||
trackCustomMetric(name, value) {
|
||||
// StatisticsCollector doesn't have trackCustomMetric method
|
||||
// Could be added later if needed
|
||||
this.log(`Custom metric recorded: ${name}=${value}`, 'info');
|
||||
}
|
||||
/**
|
||||
* Reset all metrics
|
||||
*/
|
||||
reset() {
|
||||
if (this.statisticsCollector) {
|
||||
this.statisticsCollector = new StatisticsCollector();
|
||||
this.log('Metrics manually reset');
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config metrics augmentation
|
||||
*/
|
||||
export function createMetricsAugmentation(config) {
|
||||
return new MetricsAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=metricsAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
95
.recovery-workspace/dist-backup-20250910-141917/augmentations/monitoringAugmentation.d.ts
vendored
Normal file
95
.recovery-workspace/dist-backup-20250910-141917/augmentations/monitoringAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Monitoring Augmentation - Optional Health & Performance Monitoring
|
||||
*
|
||||
* Replaces the hardcoded HealthMonitor in Brainy with an optional augmentation.
|
||||
* Provides health checks, performance monitoring, and distributed system tracking.
|
||||
*
|
||||
* Zero-config: Automatically enabled for distributed deployments
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export interface MonitoringConfig {
|
||||
enabled?: boolean;
|
||||
healthCheckInterval?: number;
|
||||
metricsInterval?: number;
|
||||
trackLatency?: boolean;
|
||||
trackErrors?: boolean;
|
||||
trackCacheMetrics?: boolean;
|
||||
exposeHealthEndpoint?: boolean;
|
||||
}
|
||||
/**
|
||||
* MonitoringAugmentation - Makes health monitoring optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - Health status tracking
|
||||
* - Performance monitoring
|
||||
* - Error rate tracking
|
||||
* - Distributed system health
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export declare class MonitoringAugmentation extends BaseAugmentation {
|
||||
readonly metadata: "readonly";
|
||||
readonly name = "monitoring";
|
||||
readonly timing: "after";
|
||||
operations: ("search" | "find" | "similar" | "add" | "update" | "delete" | "relate" | "unrelate" | "all")[];
|
||||
readonly priority = 30;
|
||||
private healthMonitor;
|
||||
private configManager;
|
||||
protected config: MonitoringConfig;
|
||||
private requestStartTimes;
|
||||
constructor(config?: MonitoringConfig);
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute augmentation - track health metrics
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Get health status
|
||||
*/
|
||||
getHealthStatus(): {
|
||||
status: string;
|
||||
enabled: boolean;
|
||||
uptime: number;
|
||||
vectorCount: number;
|
||||
requestRate: number;
|
||||
errorRate: number;
|
||||
cacheHitRate: number;
|
||||
} | {
|
||||
status: string;
|
||||
enabled: boolean;
|
||||
uptime?: undefined;
|
||||
vectorCount?: undefined;
|
||||
requestRate?: undefined;
|
||||
errorRate?: undefined;
|
||||
cacheHitRate?: undefined;
|
||||
};
|
||||
/**
|
||||
* Get health endpoint data (for API exposure)
|
||||
*/
|
||||
getHealthEndpointData(): Record<string, any>;
|
||||
/**
|
||||
* Update vector count manually
|
||||
*/
|
||||
updateVectorCount(count: number): void;
|
||||
/**
|
||||
* Record custom health metric
|
||||
*/
|
||||
recordCustomMetric(name: string, value: number): void;
|
||||
/**
|
||||
* Check if system is healthy
|
||||
*/
|
||||
isHealthy(): boolean;
|
||||
/**
|
||||
* Get uptime in milliseconds
|
||||
*/
|
||||
getUptime(): number;
|
||||
/**
|
||||
* Force health check
|
||||
*/
|
||||
checkHealth(): Promise<boolean>;
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config monitoring augmentation
|
||||
*/
|
||||
export declare function createMonitoringAugmentation(config?: MonitoringConfig): MonitoringAugmentation;
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* Monitoring Augmentation - Optional Health & Performance Monitoring
|
||||
*
|
||||
* Replaces the hardcoded HealthMonitor in Brainy with an optional augmentation.
|
||||
* Provides health checks, performance monitoring, and distributed system tracking.
|
||||
*
|
||||
* Zero-config: Automatically enabled for distributed deployments
|
||||
* Can be disabled or customized via augmentation registry
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { HealthMonitor } from '../distributed/healthMonitor.js';
|
||||
import { DistributedConfigManager as ConfigManager } from '../distributed/configManager.js';
|
||||
/**
|
||||
* MonitoringAugmentation - Makes health monitoring optional and pluggable
|
||||
*
|
||||
* Features:
|
||||
* - Health status tracking
|
||||
* - Performance monitoring
|
||||
* - Error rate tracking
|
||||
* - Distributed system health
|
||||
* - Zero-config with smart defaults
|
||||
*/
|
||||
export class MonitoringAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.metadata = 'readonly'; // Reads metadata for monitoring
|
||||
this.name = 'monitoring';
|
||||
this.timing = 'after';
|
||||
this.operations = ['search', 'find', 'similar', 'add', 'update', 'delete', 'relate', 'unrelate', 'all'];
|
||||
this.priority = 30; // Low priority, observability layer
|
||||
this.healthMonitor = null;
|
||||
this.configManager = null;
|
||||
this.requestStartTimes = new Map();
|
||||
this.config = {
|
||||
enabled: true,
|
||||
healthCheckInterval: 30000, // 30 seconds
|
||||
metricsInterval: 60000, // 1 minute
|
||||
trackLatency: true,
|
||||
trackErrors: true,
|
||||
trackCacheMetrics: true,
|
||||
exposeHealthEndpoint: true,
|
||||
...config
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('Monitoring augmentation disabled by configuration');
|
||||
return;
|
||||
}
|
||||
// Initialize config manager and health monitor (requires storage)
|
||||
if (this.context?.storage) {
|
||||
this.configManager = new ConfigManager(this.context.storage);
|
||||
this.healthMonitor = new HealthMonitor(this.configManager);
|
||||
this.healthMonitor.start();
|
||||
}
|
||||
else {
|
||||
this.log('Storage not available - health monitoring disabled', 'warn');
|
||||
}
|
||||
this.log('Monitoring augmentation initialized');
|
||||
}
|
||||
async onShutdown() {
|
||||
if (this.healthMonitor) {
|
||||
this.healthMonitor.stop();
|
||||
this.healthMonitor = null;
|
||||
}
|
||||
this.configManager = null;
|
||||
this.requestStartTimes.clear();
|
||||
this.log('Monitoring augmentation shut down');
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - track health metrics
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// If monitoring disabled, just pass through
|
||||
if (!this.healthMonitor || !this.config.enabled) {
|
||||
return next();
|
||||
}
|
||||
// Generate request ID for tracking
|
||||
const requestId = `${operation}-${Date.now()}-${Math.random()}`;
|
||||
// Track request start time
|
||||
if (this.config.trackLatency) {
|
||||
this.requestStartTimes.set(requestId, Date.now());
|
||||
}
|
||||
try {
|
||||
// Execute operation
|
||||
const result = await next();
|
||||
// Track successful operation
|
||||
if (this.config.trackLatency) {
|
||||
const startTime = this.requestStartTimes.get(requestId);
|
||||
if (startTime) {
|
||||
const latency = Date.now() - startTime;
|
||||
this.healthMonitor.recordRequest(latency, false);
|
||||
this.requestStartTimes.delete(requestId);
|
||||
}
|
||||
}
|
||||
// Update vector count for 'add' operations
|
||||
if (operation === 'add' && this.context?.brain) {
|
||||
try {
|
||||
const count = await this.context.brain.getNounCount();
|
||||
this.healthMonitor.updateVectorCount(count);
|
||||
}
|
||||
catch (e) {
|
||||
// Ignore count update errors
|
||||
}
|
||||
}
|
||||
// Track cache metrics for search operations
|
||||
if (operation === 'search' && this.config.trackCacheMetrics) {
|
||||
// Check if result came from cache (would be set by cache augmentation)
|
||||
const fromCache = params._fromCache || false;
|
||||
this.healthMonitor.recordCacheAccess(fromCache);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
// Track error
|
||||
if (this.config.trackErrors) {
|
||||
const startTime = this.requestStartTimes.get(requestId);
|
||||
if (startTime) {
|
||||
const latency = Date.now() - startTime;
|
||||
this.healthMonitor.recordRequest(latency, true);
|
||||
this.requestStartTimes.delete(requestId);
|
||||
}
|
||||
else {
|
||||
this.healthMonitor.recordRequest(0, true);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get health status
|
||||
*/
|
||||
getHealthStatus() {
|
||||
if (!this.healthMonitor) {
|
||||
return {
|
||||
status: 'disabled',
|
||||
enabled: false,
|
||||
uptime: 0,
|
||||
vectorCount: 0,
|
||||
requestRate: 0,
|
||||
errorRate: 0,
|
||||
cacheHitRate: 0
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 'healthy',
|
||||
enabled: true,
|
||||
...this.healthMonitor.getHealthEndpointData()
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get health endpoint data (for API exposure)
|
||||
*/
|
||||
getHealthEndpointData() {
|
||||
if (!this.healthMonitor) {
|
||||
return {
|
||||
status: 'disabled',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
return this.healthMonitor.getHealthEndpointData();
|
||||
}
|
||||
/**
|
||||
* Update vector count manually
|
||||
*/
|
||||
updateVectorCount(count) {
|
||||
if (this.healthMonitor) {
|
||||
this.healthMonitor.updateVectorCount(count);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Record custom health metric
|
||||
*/
|
||||
recordCustomMetric(name, value) {
|
||||
if (this.healthMonitor) {
|
||||
// Health monitor could be extended to track custom metrics
|
||||
this.log(`Custom metric recorded: ${name}=${value}`, 'info');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if system is healthy
|
||||
*/
|
||||
isHealthy() {
|
||||
if (!this.healthMonitor)
|
||||
return true; // If disabled, assume healthy
|
||||
const data = this.healthMonitor.getHealthEndpointData();
|
||||
// Define health criteria
|
||||
const errorRateThreshold = 0.05; // 5% error rate
|
||||
const minUptime = 60000; // 1 minute
|
||||
return (data.errorRate < errorRateThreshold &&
|
||||
data.uptime > minUptime);
|
||||
}
|
||||
/**
|
||||
* Get uptime in milliseconds
|
||||
*/
|
||||
getUptime() {
|
||||
if (!this.healthMonitor)
|
||||
return 0;
|
||||
const data = this.healthMonitor.getHealthEndpointData();
|
||||
return data.uptime || 0;
|
||||
}
|
||||
/**
|
||||
* Force health check
|
||||
*/
|
||||
async checkHealth() {
|
||||
if (!this.healthMonitor)
|
||||
return true;
|
||||
// Perform active health check
|
||||
try {
|
||||
// Could ping storage, check memory, etc.
|
||||
if (this.context?.storage) {
|
||||
await this.context.storage.getStatistics?.();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.log('Health check failed', 'warn');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Factory function for zero-config monitoring augmentation
|
||||
*/
|
||||
export function createMonitoringAugmentation(config) {
|
||||
return new MonitoringAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=monitoringAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
145
.recovery-workspace/dist-backup-20250910-141917/augmentations/neuralImport.d.ts
vendored
Normal file
145
.recovery-workspace/dist-backup-20250910-141917/augmentations/neuralImport.d.ts
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* Neural Import Augmentation - AI-Powered Data Understanding
|
||||
*
|
||||
* 🧠 Built-in AI augmentation for intelligent data processing
|
||||
* ⚛️ Always free, always included, always enabled
|
||||
*
|
||||
* Now using the unified BrainyAugmentation interface!
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export interface NeuralAnalysisResult {
|
||||
detectedEntities: DetectedEntity[];
|
||||
detectedRelationships: DetectedRelationship[];
|
||||
confidence: number;
|
||||
insights: NeuralInsight[];
|
||||
}
|
||||
export interface DetectedEntity {
|
||||
originalData: any;
|
||||
nounType: string;
|
||||
confidence: number;
|
||||
suggestedId: string;
|
||||
reasoning: string;
|
||||
alternativeTypes: Array<{
|
||||
type: string;
|
||||
confidence: number;
|
||||
}>;
|
||||
}
|
||||
export interface DetectedRelationship {
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
verbType: string;
|
||||
confidence: number;
|
||||
weight: number;
|
||||
reasoning: string;
|
||||
context: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
export interface NeuralInsight {
|
||||
type: 'hierarchy' | 'cluster' | 'pattern' | 'anomaly' | 'opportunity';
|
||||
description: string;
|
||||
confidence: number;
|
||||
affectedEntities: string[];
|
||||
recommendation?: string;
|
||||
}
|
||||
export interface NeuralImportConfig {
|
||||
confidenceThreshold: number;
|
||||
enableWeights: boolean;
|
||||
skipDuplicates: boolean;
|
||||
categoryFilter?: string[];
|
||||
dataType?: string;
|
||||
}
|
||||
/**
|
||||
* Neural Import Augmentation - Unified Implementation
|
||||
* Processes data with AI before storage operations
|
||||
*/
|
||||
export declare class NeuralImportAugmentation extends BaseAugmentation {
|
||||
readonly name = "neural-import";
|
||||
readonly timing: "before";
|
||||
readonly metadata: {
|
||||
reads: "*";
|
||||
writes: string[];
|
||||
};
|
||||
operations: ("add" | "addNoun" | "addVerb" | "all")[];
|
||||
readonly priority = 80;
|
||||
protected config: NeuralImportConfig;
|
||||
private analysisCache;
|
||||
private typeMatcher;
|
||||
constructor(config?: Partial<NeuralImportConfig>);
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute augmentation - process data with AI before storage
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Extract raw data from operation params
|
||||
*/
|
||||
private extractRawData;
|
||||
/**
|
||||
* Get the full neural analysis result (for external use)
|
||||
*/
|
||||
getNeuralAnalysis(rawData: Buffer | string, dataType?: string): Promise<NeuralAnalysisResult>;
|
||||
/**
|
||||
* Parse raw data based on type
|
||||
*/
|
||||
private parseRawData;
|
||||
/**
|
||||
* Parse CSV data - handles quoted values, escaped quotes, and edge cases
|
||||
*/
|
||||
private parseCSV;
|
||||
/**
|
||||
* Parse YAML data
|
||||
*/
|
||||
private parseYAML;
|
||||
/**
|
||||
* Parse a YAML value (handle strings, numbers, booleans, null)
|
||||
*/
|
||||
private parseYAMLValue;
|
||||
/**
|
||||
* Perform neural analysis on parsed data
|
||||
*/
|
||||
private performNeuralAnalysis;
|
||||
/**
|
||||
* Infer noun type from object structure using intelligent type matching
|
||||
*/
|
||||
private inferNounType;
|
||||
/**
|
||||
* Detect relationships from object references
|
||||
*/
|
||||
private detectRelationships;
|
||||
/**
|
||||
* Infer verb type from field name using intelligent type matching
|
||||
*/
|
||||
private inferVerbType;
|
||||
/**
|
||||
* Group entities by type
|
||||
*/
|
||||
private groupByType;
|
||||
/**
|
||||
* Store neural analysis results
|
||||
*/
|
||||
private storeNeuralAnalysis;
|
||||
/**
|
||||
* Helper to get data type from file path
|
||||
*/
|
||||
private getDataTypeFromPath;
|
||||
/**
|
||||
* PUBLIC API: Process raw data (for external use, like Synapses)
|
||||
* This maintains compatibility with code that wants to use Neural Import directly
|
||||
*/
|
||||
processRawData(rawData: Buffer | string, dataType: string, options?: Record<string, unknown>): Promise<{
|
||||
success: boolean;
|
||||
data: {
|
||||
nouns: string[];
|
||||
verbs: string[];
|
||||
confidence?: number;
|
||||
insights?: Array<{
|
||||
type: string;
|
||||
description: string;
|
||||
confidence: number;
|
||||
}>;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
error?: string;
|
||||
}>;
|
||||
}
|
||||
|
|
@ -0,0 +1,520 @@
|
|||
/**
|
||||
* Neural Import Augmentation - AI-Powered Data Understanding
|
||||
*
|
||||
* 🧠 Built-in AI augmentation for intelligent data processing
|
||||
* ⚛️ Always free, always included, always enabled
|
||||
*
|
||||
* Now using the unified BrainyAugmentation interface!
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import * as path from '../universal/path.js';
|
||||
import { getBrainyTypes } from './typeMatching/brainyTypes.js';
|
||||
import { prodLog } from '../utils/logger.js';
|
||||
/**
|
||||
* Neural Import Augmentation - Unified Implementation
|
||||
* Processes data with AI before storage operations
|
||||
*/
|
||||
export class NeuralImportAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.name = 'neural-import';
|
||||
this.timing = 'before'; // Process data before storage
|
||||
this.metadata = {
|
||||
reads: '*', // Needs to read data for analysis
|
||||
writes: ['_neuralProcessed', '_neuralConfidence', '_detectedEntities', '_detectedRelationships', '_neuralInsights', 'nounType', 'verbType']
|
||||
}; // Enriches metadata with neural analysis
|
||||
this.operations = ['add', 'addNoun', 'addVerb', 'all']; // Use 'all' to catch batch operations
|
||||
this.priority = 80; // High priority for data processing
|
||||
this.analysisCache = new Map();
|
||||
this.typeMatcher = null;
|
||||
this.config = {
|
||||
confidenceThreshold: 0.7,
|
||||
enableWeights: true,
|
||||
skipDuplicates: true,
|
||||
dataType: 'json',
|
||||
...config
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
try {
|
||||
this.typeMatcher = await getBrainyTypes();
|
||||
this.log('🧠 Neural Import augmentation initialized with intelligent type matching');
|
||||
}
|
||||
catch (error) {
|
||||
this.log('⚠️ Failed to initialize type matcher, falling back to heuristics', 'warn');
|
||||
}
|
||||
}
|
||||
async onShutdown() {
|
||||
this.analysisCache.clear();
|
||||
this.log('🧠 Neural Import augmentation shut down');
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - process data with AI before storage
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// Only process on add operations
|
||||
if (!this.operations.includes(operation)) {
|
||||
return next();
|
||||
}
|
||||
try {
|
||||
// Extract data from params based on operation
|
||||
const rawData = this.extractRawData(operation, params);
|
||||
if (!rawData) {
|
||||
return next();
|
||||
}
|
||||
// Perform neural analysis
|
||||
const analysis = await this.performNeuralAnalysis(rawData, this.config);
|
||||
// Enhance params with neural insights
|
||||
if (params.metadata) {
|
||||
params.metadata._neuralProcessed = true;
|
||||
params.metadata._neuralConfidence = analysis.confidence;
|
||||
params.metadata._detectedEntities = analysis.detectedEntities.length;
|
||||
params.metadata._detectedRelationships = analysis.detectedRelationships.length;
|
||||
params.metadata._neuralInsights = analysis.insights;
|
||||
}
|
||||
else if (typeof params === 'object') {
|
||||
params.metadata = {
|
||||
_neuralProcessed: true,
|
||||
_neuralConfidence: analysis.confidence,
|
||||
_detectedEntities: analysis.detectedEntities.length,
|
||||
_detectedRelationships: analysis.detectedRelationships.length,
|
||||
_neuralInsights: analysis.insights
|
||||
};
|
||||
}
|
||||
// Store neural analysis for later retrieval
|
||||
await this.storeNeuralAnalysis(analysis);
|
||||
// If we detected entities/relationships, potentially add them
|
||||
if (this.context?.brain && analysis.detectedEntities.length > 0) {
|
||||
// This could automatically create entities/relationships
|
||||
// But for now, just enhance the metadata
|
||||
this.log(`Detected ${analysis.detectedEntities.length} entities and ${analysis.detectedRelationships.length} relationships`);
|
||||
}
|
||||
// Continue with enhanced data
|
||||
return next();
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Neural analysis failed: ${error}`, 'warn');
|
||||
// Continue without neural processing
|
||||
return next();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Extract raw data from operation params
|
||||
*/
|
||||
extractRawData(operation, params) {
|
||||
switch (operation) {
|
||||
case 'add':
|
||||
return params.content || params.data || params;
|
||||
case 'addNoun':
|
||||
return params.noun || params.data || params;
|
||||
case 'addVerb':
|
||||
return params.verb || params;
|
||||
case 'addBatch':
|
||||
return params.items || params.batch || params;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the full neural analysis result (for external use)
|
||||
*/
|
||||
async getNeuralAnalysis(rawData, dataType) {
|
||||
const parsedData = await this.parseRawData(rawData, dataType || this.config.dataType || 'json');
|
||||
return await this.performNeuralAnalysis(parsedData, this.config);
|
||||
}
|
||||
/**
|
||||
* Parse raw data based on type
|
||||
*/
|
||||
async parseRawData(rawData, dataType) {
|
||||
const content = typeof rawData === 'string' ? rawData : rawData.toString('utf8');
|
||||
switch (dataType.toLowerCase()) {
|
||||
case 'json':
|
||||
try {
|
||||
const jsonData = JSON.parse(content);
|
||||
return Array.isArray(jsonData) ? jsonData : [jsonData];
|
||||
}
|
||||
catch {
|
||||
// If JSON parse fails, treat as text
|
||||
return [{ text: content }];
|
||||
}
|
||||
case 'csv':
|
||||
return this.parseCSV(content);
|
||||
case 'yaml':
|
||||
case 'yml':
|
||||
return this.parseYAML(content);
|
||||
case 'txt':
|
||||
case 'text':
|
||||
// Split text into sentences/paragraphs for analysis
|
||||
return content.split(/\n+/).filter(line => line.trim()).map(line => ({ text: line }));
|
||||
default:
|
||||
// Unknown type, treat as text
|
||||
return [{ text: content }];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse CSV data - handles quoted values, escaped quotes, and edge cases
|
||||
*/
|
||||
parseCSV(content) {
|
||||
const lines = content.split('\n');
|
||||
if (lines.length === 0)
|
||||
return [];
|
||||
// Parse a CSV line handling quotes
|
||||
const parseLine = (line) => {
|
||||
const result = [];
|
||||
let current = '';
|
||||
let inQuotes = false;
|
||||
let i = 0;
|
||||
while (i < line.length) {
|
||||
const char = line[i];
|
||||
const nextChar = line[i + 1];
|
||||
if (char === '"') {
|
||||
if (inQuotes && nextChar === '"') {
|
||||
// Escaped quote
|
||||
current += '"';
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
// Toggle quote mode
|
||||
inQuotes = !inQuotes;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else if (char === ',' && !inQuotes) {
|
||||
// Field separator
|
||||
result.push(current.trim());
|
||||
current = '';
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
current += char;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// Add last field
|
||||
result.push(current.trim());
|
||||
return result;
|
||||
};
|
||||
// Parse headers
|
||||
const headers = parseLine(lines[0]);
|
||||
const data = [];
|
||||
// Parse data rows
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (!line)
|
||||
continue; // Skip empty lines
|
||||
const values = parseLine(line);
|
||||
const row = {};
|
||||
headers.forEach((header, index) => {
|
||||
const value = values[index] || '';
|
||||
// Try to parse numbers
|
||||
const num = Number(value);
|
||||
row[header] = !isNaN(num) && value !== '' ? num : value;
|
||||
});
|
||||
data.push(row);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* Parse YAML data
|
||||
*/
|
||||
parseYAML(content) {
|
||||
try {
|
||||
// Simple YAML parser for basic structures
|
||||
// For full YAML support, we'd use js-yaml library
|
||||
const lines = content.split('\n');
|
||||
const result = [];
|
||||
let currentObject = null;
|
||||
let currentIndent = 0;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#'))
|
||||
continue; // Skip empty lines and comments
|
||||
// Calculate indentation
|
||||
const indent = line.length - line.trimStart().length;
|
||||
// Check for array item
|
||||
if (trimmed.startsWith('- ')) {
|
||||
const value = trimmed.substring(2).trim();
|
||||
if (indent === 0) {
|
||||
// Top-level array item
|
||||
if (value.includes(':')) {
|
||||
// Object in array
|
||||
currentObject = {};
|
||||
result.push(currentObject);
|
||||
const [key, val] = value.split(':').map(s => s.trim());
|
||||
currentObject[key] = this.parseYAMLValue(val);
|
||||
}
|
||||
else {
|
||||
result.push(this.parseYAMLValue(value));
|
||||
}
|
||||
}
|
||||
else if (currentObject) {
|
||||
// Nested array
|
||||
const lastKey = Object.keys(currentObject).pop();
|
||||
if (lastKey) {
|
||||
if (!Array.isArray(currentObject[lastKey])) {
|
||||
currentObject[lastKey] = [];
|
||||
}
|
||||
currentObject[lastKey].push(this.parseYAMLValue(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (trimmed.includes(':')) {
|
||||
// Key-value pair
|
||||
const colonIndex = trimmed.indexOf(':');
|
||||
const key = trimmed.substring(0, colonIndex).trim();
|
||||
const value = trimmed.substring(colonIndex + 1).trim();
|
||||
if (indent === 0) {
|
||||
// Top-level object
|
||||
if (!currentObject) {
|
||||
currentObject = {};
|
||||
result.push(currentObject);
|
||||
}
|
||||
currentObject[key] = this.parseYAMLValue(value);
|
||||
currentIndent = 0;
|
||||
}
|
||||
else if (currentObject) {
|
||||
// Nested object
|
||||
if (indent > currentIndent && !value) {
|
||||
// Start of nested object
|
||||
const lastKey = Object.keys(currentObject).pop();
|
||||
if (lastKey) {
|
||||
currentObject[lastKey] = { [key]: '' };
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentObject[key] = this.parseYAMLValue(value);
|
||||
}
|
||||
currentIndent = indent;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we built a single object and not an array, wrap it
|
||||
if (result.length === 0 && currentObject) {
|
||||
result.push(currentObject);
|
||||
}
|
||||
return result.length > 0 ? result : [{ text: content }];
|
||||
}
|
||||
catch (error) {
|
||||
prodLog.warn('YAML parsing failed, treating as text:', error);
|
||||
return [{ text: content }];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse a YAML value (handle strings, numbers, booleans, null)
|
||||
*/
|
||||
parseYAMLValue(value) {
|
||||
if (!value || value === '~' || value === 'null')
|
||||
return null;
|
||||
if (value === 'true')
|
||||
return true;
|
||||
if (value === 'false')
|
||||
return false;
|
||||
// Remove quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
// Try to parse as number
|
||||
const num = Number(value);
|
||||
if (!isNaN(num) && value !== '')
|
||||
return num;
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* Perform neural analysis on parsed data
|
||||
*/
|
||||
async performNeuralAnalysis(data, config) {
|
||||
const detectedEntities = [];
|
||||
const detectedRelationships = [];
|
||||
const insights = [];
|
||||
// Simple entity detection (in real implementation, would use ML)
|
||||
for (const item of data) {
|
||||
if (typeof item === 'object') {
|
||||
// Detect entities from object properties
|
||||
const entityId = item.id || item.name || item.title || `entity_${Date.now()}_${Math.random()}`;
|
||||
detectedEntities.push({
|
||||
originalData: item,
|
||||
nounType: await this.inferNounType(item),
|
||||
confidence: 0.85,
|
||||
suggestedId: String(entityId),
|
||||
reasoning: 'Detected from structured data',
|
||||
alternativeTypes: []
|
||||
});
|
||||
// Detect relationships from references
|
||||
await this.detectRelationships(item, entityId, detectedRelationships);
|
||||
}
|
||||
}
|
||||
// Generate insights
|
||||
if (detectedEntities.length > 10) {
|
||||
insights.push({
|
||||
type: 'pattern',
|
||||
description: `Large dataset with ${detectedEntities.length} entities detected`,
|
||||
confidence: 0.9,
|
||||
affectedEntities: detectedEntities.slice(0, 5).map(e => e.suggestedId),
|
||||
recommendation: 'Consider batch processing for optimal performance'
|
||||
});
|
||||
}
|
||||
// Look for clusters
|
||||
const typeGroups = this.groupByType(detectedEntities);
|
||||
if (Object.keys(typeGroups).length > 1) {
|
||||
insights.push({
|
||||
type: 'cluster',
|
||||
description: `Multiple entity types detected: ${Object.keys(typeGroups).join(', ')}`,
|
||||
confidence: 0.8,
|
||||
affectedEntities: [],
|
||||
recommendation: 'Data contains diverse entity types suitable for graph analysis'
|
||||
});
|
||||
}
|
||||
return {
|
||||
detectedEntities,
|
||||
detectedRelationships,
|
||||
confidence: detectedEntities.length > 0 ? 0.85 : 0.5,
|
||||
insights
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Infer noun type from object structure using intelligent type matching
|
||||
*/
|
||||
async inferNounType(obj) {
|
||||
if (!this.typeMatcher) {
|
||||
// Initialize type matcher if not available
|
||||
this.typeMatcher = await getBrainyTypes();
|
||||
}
|
||||
const result = await this.typeMatcher.matchNounType(obj);
|
||||
// Log if confidence is low for debugging
|
||||
if (result.confidence < 0.5) {
|
||||
this.log(`Low confidence (${result.confidence.toFixed(2)}) for noun type: ${result.type}`, 'warn');
|
||||
}
|
||||
return result.type;
|
||||
}
|
||||
/**
|
||||
* Detect relationships from object references
|
||||
*/
|
||||
async detectRelationships(obj, sourceId, relationships) {
|
||||
// Look for reference patterns
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (key.endsWith('Id') || key.endsWith('_id') || key === 'parentId' || key === 'userId') {
|
||||
relationships.push({
|
||||
sourceId,
|
||||
targetId: String(value),
|
||||
verbType: await this.inferVerbType(key, obj, { id: value }),
|
||||
confidence: 0.75,
|
||||
weight: 1,
|
||||
reasoning: `Reference detected in field: ${key}`,
|
||||
context: key
|
||||
});
|
||||
}
|
||||
// Array of IDs
|
||||
if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') {
|
||||
if (key.endsWith('Ids') || key.endsWith('_ids')) {
|
||||
for (const targetId of value) {
|
||||
relationships.push({
|
||||
sourceId,
|
||||
targetId: String(targetId),
|
||||
verbType: await this.inferVerbType(key, obj, { id: targetId }),
|
||||
confidence: 0.7,
|
||||
weight: 1,
|
||||
reasoning: `Array reference in field: ${key}`,
|
||||
context: key
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Infer verb type from field name using intelligent type matching
|
||||
*/
|
||||
async inferVerbType(fieldName, sourceObj, targetObj) {
|
||||
if (!this.typeMatcher) {
|
||||
// Initialize type matcher if not available
|
||||
this.typeMatcher = await getBrainyTypes();
|
||||
}
|
||||
const result = await this.typeMatcher.matchVerbType(sourceObj, targetObj, fieldName);
|
||||
// Log if confidence is low for debugging
|
||||
if (result.confidence < 0.5) {
|
||||
this.log(`Low confidence (${result.confidence.toFixed(2)}) for verb type: ${result.type}`, 'warn');
|
||||
}
|
||||
return result.type;
|
||||
}
|
||||
/**
|
||||
* Group entities by type
|
||||
*/
|
||||
groupByType(entities) {
|
||||
const groups = {};
|
||||
for (const entity of entities) {
|
||||
if (!groups[entity.nounType]) {
|
||||
groups[entity.nounType] = [];
|
||||
}
|
||||
groups[entity.nounType].push(entity);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
/**
|
||||
* Store neural analysis results
|
||||
*/
|
||||
async storeNeuralAnalysis(analysis) {
|
||||
// Cache the analysis for potential later use
|
||||
const key = `analysis_${Date.now()}`;
|
||||
this.analysisCache.set(key, analysis);
|
||||
// Limit cache size
|
||||
if (this.analysisCache.size > 100) {
|
||||
const firstKey = this.analysisCache.keys().next().value;
|
||||
if (firstKey) {
|
||||
this.analysisCache.delete(firstKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper to get data type from file path
|
||||
*/
|
||||
getDataTypeFromPath(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
switch (ext) {
|
||||
case '.json': return 'json';
|
||||
case '.csv': return 'csv';
|
||||
case '.txt': return 'text';
|
||||
case '.yaml':
|
||||
case '.yml': return 'yaml';
|
||||
default: return 'text';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* PUBLIC API: Process raw data (for external use, like Synapses)
|
||||
* This maintains compatibility with code that wants to use Neural Import directly
|
||||
*/
|
||||
async processRawData(rawData, dataType, options) {
|
||||
try {
|
||||
const analysis = await this.getNeuralAnalysis(rawData, dataType);
|
||||
// Convert to legacy format for compatibility
|
||||
const nouns = analysis.detectedEntities.map(e => e.suggestedId);
|
||||
const verbs = analysis.detectedRelationships.map(r => `${r.sourceId}->${r.verbType}->${r.targetId}`);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
nouns,
|
||||
verbs,
|
||||
confidence: analysis.confidence,
|
||||
insights: analysis.insights.map(i => ({
|
||||
type: i.type,
|
||||
description: i.description,
|
||||
confidence: i.confidence
|
||||
})),
|
||||
metadata: {
|
||||
detectedEntities: analysis.detectedEntities.length,
|
||||
detectedRelationships: analysis.detectedRelationships.length,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: { nouns: [], verbs: [] },
|
||||
error: error instanceof Error ? error.message : 'Neural analysis failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=neuralImport.js.map
|
||||
File diff suppressed because one or more lines are too long
82
.recovery-workspace/dist-backup-20250910-141917/augmentations/rateLimitAugmentation.d.ts
vendored
Normal file
82
.recovery-workspace/dist-backup-20250910-141917/augmentations/rateLimitAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* Rate Limiting Augmentation
|
||||
* Provides configurable rate limiting for Brainy operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { AugmentationManifest } from './manifest.js';
|
||||
export interface RateLimitConfig {
|
||||
enabled?: boolean;
|
||||
limits?: {
|
||||
searches?: number;
|
||||
writes?: number;
|
||||
reads?: number;
|
||||
deletes?: number;
|
||||
};
|
||||
windowMs?: number;
|
||||
skipSuccessfulRequests?: boolean;
|
||||
skipFailedRequests?: boolean;
|
||||
keyGenerator?: (context: any) => string;
|
||||
}
|
||||
/**
|
||||
* Rate Limit Augmentation
|
||||
*/
|
||||
export declare class RateLimitAugmentation extends BaseAugmentation {
|
||||
readonly name = "rateLimiter";
|
||||
readonly timing: "before";
|
||||
readonly metadata: "none";
|
||||
operations: any;
|
||||
readonly priority = 10;
|
||||
readonly category: "core";
|
||||
readonly description = "Provides rate limiting for Brainy operations";
|
||||
private limiters;
|
||||
private windowMs;
|
||||
constructor(config?: RateLimitConfig);
|
||||
getManifest(): AugmentationManifest;
|
||||
/**
|
||||
* Initialize rate limiters for each operation type
|
||||
*/
|
||||
private initializeLimiters;
|
||||
/**
|
||||
* Default key generator (could be IP, user ID, etc.)
|
||||
*/
|
||||
private defaultKeyGenerator;
|
||||
/**
|
||||
* Check if request should be rate limited
|
||||
*/
|
||||
private checkRateLimit;
|
||||
/**
|
||||
* Get remaining requests for an operation
|
||||
*/
|
||||
private getRemainingRequests;
|
||||
/**
|
||||
* Get time until reset
|
||||
*/
|
||||
private getResetTime;
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
/**
|
||||
* Execute augmentation - apply rate limiting
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Get rate limit statistics
|
||||
*/
|
||||
getStats(): {
|
||||
operations: Record<string, {
|
||||
activeKeys: number;
|
||||
totalRequests: number;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* Clear all rate limit entries
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Clear expired entries (cleanup)
|
||||
*/
|
||||
cleanup(): void;
|
||||
}
|
||||
/**
|
||||
* Create rate limit augmentation
|
||||
*/
|
||||
export declare function createRateLimitAugmentation(config?: RateLimitConfig): RateLimitAugmentation;
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
/**
|
||||
* Rate Limiting Augmentation
|
||||
* Provides configurable rate limiting for Brainy operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
/**
|
||||
* Rate Limit Augmentation
|
||||
*/
|
||||
export class RateLimitAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super(config);
|
||||
this.name = 'rateLimiter';
|
||||
this.timing = 'before';
|
||||
this.metadata = 'none';
|
||||
this.operations = ['search', 'find', 'add', 'update', 'delete', 'get'];
|
||||
this.priority = 10; // High priority, runs early
|
||||
// Augmentation metadata
|
||||
this.category = 'core'; // Use 'core' as security isn't a valid category
|
||||
this.description = 'Provides rate limiting for Brainy operations';
|
||||
this.limiters = new Map();
|
||||
// Merge with defaults
|
||||
this.config = {
|
||||
enabled: config.enabled ?? true,
|
||||
limits: {
|
||||
searches: config.limits?.searches ?? 1000,
|
||||
writes: config.limits?.writes ?? 100,
|
||||
reads: config.limits?.reads ?? 5000,
|
||||
deletes: config.limits?.deletes ?? 50
|
||||
},
|
||||
windowMs: config.windowMs ?? 60000, // 1 minute default
|
||||
skipSuccessfulRequests: config.skipSuccessfulRequests ?? false,
|
||||
skipFailedRequests: config.skipFailedRequests ?? true,
|
||||
keyGenerator: config.keyGenerator || this.defaultKeyGenerator
|
||||
};
|
||||
this.windowMs = this.config.windowMs;
|
||||
// Initialize operation limiters
|
||||
this.initializeLimiters();
|
||||
}
|
||||
getManifest() {
|
||||
return {
|
||||
id: 'rate-limiter',
|
||||
name: 'Rate Limiter',
|
||||
version: '1.0.0',
|
||||
description: 'Configurable rate limiting for API operations',
|
||||
longDescription: 'Provides per-operation rate limiting with configurable windows and limits. Helps prevent abuse and ensures fair resource usage.',
|
||||
category: 'core',
|
||||
configSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Enable or disable rate limiting'
|
||||
},
|
||||
limits: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
searches: {
|
||||
type: 'number',
|
||||
default: 1000,
|
||||
description: 'Search operations per minute'
|
||||
},
|
||||
writes: {
|
||||
type: 'number',
|
||||
default: 100,
|
||||
description: 'Write operations per minute'
|
||||
},
|
||||
reads: {
|
||||
type: 'number',
|
||||
default: 5000,
|
||||
description: 'Read operations per minute'
|
||||
},
|
||||
deletes: {
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'Delete operations per minute'
|
||||
}
|
||||
}
|
||||
},
|
||||
windowMs: {
|
||||
type: 'number',
|
||||
default: 60000,
|
||||
description: 'Time window in milliseconds'
|
||||
}
|
||||
}
|
||||
},
|
||||
configDefaults: {
|
||||
enabled: true,
|
||||
limits: {
|
||||
searches: 1000,
|
||||
writes: 100,
|
||||
reads: 5000,
|
||||
deletes: 50
|
||||
},
|
||||
windowMs: 60000
|
||||
},
|
||||
minBrainyVersion: '3.0.0',
|
||||
keywords: ['rate-limit', 'security', 'throttle'],
|
||||
documentation: 'https://docs.brainy.dev/augmentations/rate-limit',
|
||||
status: 'stable',
|
||||
performance: {
|
||||
memoryUsage: 'low',
|
||||
cpuUsage: 'low',
|
||||
networkUsage: 'none'
|
||||
},
|
||||
features: ['per-operation-limits', 'configurable-windows', 'key-based-limiting'],
|
||||
enhancedOperations: ['search', 'add', 'update', 'delete', 'get'],
|
||||
metrics: [
|
||||
{
|
||||
name: 'rate_limit_exceeded',
|
||||
type: 'counter',
|
||||
description: 'Number of rate limit violations'
|
||||
},
|
||||
{
|
||||
name: 'rate_limit_requests',
|
||||
type: 'counter',
|
||||
description: 'Total requests checked'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Initialize rate limiters for each operation type
|
||||
*/
|
||||
initializeLimiters() {
|
||||
const operations = ['searches', 'writes', 'reads', 'deletes'];
|
||||
for (const op of operations) {
|
||||
this.limiters.set(op, new Map());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Default key generator (could be IP, user ID, etc.)
|
||||
*/
|
||||
defaultKeyGenerator(_context) {
|
||||
// In a real implementation, this would extract IP or user ID
|
||||
return 'default';
|
||||
}
|
||||
/**
|
||||
* Check if request should be rate limited
|
||||
*/
|
||||
checkRateLimit(operation, key) {
|
||||
const limiter = this.limiters.get(operation);
|
||||
if (!limiter)
|
||||
return false;
|
||||
const limit = this.config.limits[operation];
|
||||
if (!limit)
|
||||
return false;
|
||||
const now = Date.now();
|
||||
let entry = limiter.get(key);
|
||||
// Initialize or reset entry
|
||||
if (!entry || now >= entry.resetTime) {
|
||||
entry = {
|
||||
count: 0,
|
||||
resetTime: now + this.windowMs
|
||||
};
|
||||
limiter.set(key, entry);
|
||||
}
|
||||
// Check if limit exceeded
|
||||
if (entry.count >= limit) {
|
||||
return true; // Rate limited
|
||||
}
|
||||
// Increment counter
|
||||
entry.count++;
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Get remaining requests for an operation
|
||||
*/
|
||||
getRemainingRequests(operation, key) {
|
||||
const limiter = this.limiters.get(operation);
|
||||
if (!limiter)
|
||||
return -1;
|
||||
const limit = this.config.limits[operation];
|
||||
if (!limit)
|
||||
return -1;
|
||||
const entry = limiter.get(key);
|
||||
if (!entry)
|
||||
return limit;
|
||||
const now = Date.now();
|
||||
if (now >= entry.resetTime)
|
||||
return limit;
|
||||
return Math.max(0, limit - entry.count);
|
||||
}
|
||||
/**
|
||||
* Get time until reset
|
||||
*/
|
||||
getResetTime(operation, key) {
|
||||
const limiter = this.limiters.get(operation);
|
||||
if (!limiter)
|
||||
return 0;
|
||||
const entry = limiter.get(key);
|
||||
if (!entry)
|
||||
return 0;
|
||||
const now = Date.now();
|
||||
return Math.max(0, entry.resetTime - now);
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('Rate limiter disabled by configuration');
|
||||
return;
|
||||
}
|
||||
this.log(`Rate limiter initialized (window: ${this.windowMs}ms)`);
|
||||
// Start cleanup timer
|
||||
setInterval(() => {
|
||||
this.cleanup();
|
||||
}, this.windowMs);
|
||||
}
|
||||
async onShutdown() {
|
||||
this.clear();
|
||||
this.log('Rate limiter shut down');
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - apply rate limiting
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// If rate limiting is disabled, just pass through
|
||||
if (!this.config.enabled) {
|
||||
return next();
|
||||
}
|
||||
// Map operations to rate limit categories
|
||||
let rateLimitOperation;
|
||||
switch (operation) {
|
||||
case 'search':
|
||||
case 'find':
|
||||
case 'similar':
|
||||
rateLimitOperation = 'searches';
|
||||
break;
|
||||
case 'add':
|
||||
case 'update':
|
||||
rateLimitOperation = 'writes';
|
||||
break;
|
||||
case 'delete':
|
||||
rateLimitOperation = 'deletes';
|
||||
break;
|
||||
case 'get':
|
||||
rateLimitOperation = 'reads';
|
||||
break;
|
||||
default:
|
||||
return next(); // Don't rate limit unknown operations
|
||||
}
|
||||
const key = this.config.keyGenerator(params);
|
||||
if (this.checkRateLimit(rateLimitOperation, key)) {
|
||||
const error = new Error(`Rate limit exceeded for ${operation}`);
|
||||
error.statusCode = 429;
|
||||
error.retryAfter = this.getResetTime(rateLimitOperation, key);
|
||||
error.rateLimit = {
|
||||
limit: this.config.limits[rateLimitOperation],
|
||||
remaining: 0,
|
||||
reset: Date.now() + this.getResetTime(rateLimitOperation, key)
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
const result = await next();
|
||||
// Add rate limit info to result if possible
|
||||
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
||||
result._rateLimit = {
|
||||
limit: this.config.limits[rateLimitOperation],
|
||||
remaining: this.getRemainingRequests(rateLimitOperation, key),
|
||||
reset: Date.now() + this.getResetTime(rateLimitOperation, key)
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
// Optionally don't count failed requests
|
||||
if (this.config.skipFailedRequests) {
|
||||
const limiter = this.limiters.get(rateLimitOperation);
|
||||
const entry = limiter.get(key);
|
||||
if (entry && entry.count > 0)
|
||||
entry.count--;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get rate limit statistics
|
||||
*/
|
||||
getStats() {
|
||||
const stats = { operations: {} };
|
||||
for (const [operation, limiter] of this.limiters) {
|
||||
let totalRequests = 0;
|
||||
for (const entry of limiter.values()) {
|
||||
totalRequests += entry.count;
|
||||
}
|
||||
stats.operations[operation] = {
|
||||
activeKeys: limiter.size,
|
||||
totalRequests
|
||||
};
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
/**
|
||||
* Clear all rate limit entries
|
||||
*/
|
||||
clear() {
|
||||
for (const limiter of this.limiters.values()) {
|
||||
limiter.clear();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clear expired entries (cleanup)
|
||||
*/
|
||||
cleanup() {
|
||||
const now = Date.now();
|
||||
for (const limiter of this.limiters.values()) {
|
||||
for (const [key, entry] of limiter) {
|
||||
if (now >= entry.resetTime) {
|
||||
limiter.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create rate limit augmentation
|
||||
*/
|
||||
export function createRateLimitAugmentation(config) {
|
||||
return new RateLimitAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=rateLimitAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Request Deduplicator Augmentation
|
||||
*
|
||||
* Prevents duplicate concurrent requests to improve performance by 3x
|
||||
* Automatically deduplicates identical operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
interface DeduplicatorConfig {
|
||||
enabled?: boolean;
|
||||
ttl?: number;
|
||||
maxSize?: number;
|
||||
}
|
||||
export declare class RequestDeduplicatorAugmentation extends BaseAugmentation {
|
||||
name: string;
|
||||
timing: "around";
|
||||
metadata: "none";
|
||||
operations: ("search" | "find" | "similar" | "searchText" | "searchByNounTypes" | "findSimilar" | "get")[];
|
||||
priority: number;
|
||||
private pendingRequests;
|
||||
protected config: Required<DeduplicatorConfig>;
|
||||
private cleanupInterval?;
|
||||
constructor(config?: DeduplicatorConfig);
|
||||
protected onInitialize(): Promise<void>;
|
||||
shouldExecute(operation: string, params: any): boolean;
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Create a unique key for the request based on operation and parameters
|
||||
*/
|
||||
private createRequestKey;
|
||||
/**
|
||||
* Serialize parameters to a consistent string
|
||||
*/
|
||||
private serializeParams;
|
||||
/**
|
||||
* Clean up expired requests
|
||||
*/
|
||||
private cleanup;
|
||||
/**
|
||||
* Get statistics about request deduplication
|
||||
*/
|
||||
getStats(): {
|
||||
activePendingRequests: number;
|
||||
totalDeduplicationHits: number;
|
||||
memoryUsage: string;
|
||||
efficiency: string;
|
||||
};
|
||||
/**
|
||||
* Force clear all pending requests (for testing)
|
||||
*/
|
||||
clear(): void;
|
||||
protected onShutdown(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* Request Deduplicator Augmentation
|
||||
*
|
||||
* Prevents duplicate concurrent requests to improve performance by 3x
|
||||
* Automatically deduplicates identical operations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
export class RequestDeduplicatorAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.name = 'RequestDeduplicator';
|
||||
this.timing = 'around';
|
||||
this.metadata = 'none'; // Doesn't access metadata
|
||||
this.operations = ['search', 'find', 'similar', 'searchText', 'searchByNounTypes', 'findSimilar', 'get'];
|
||||
this.priority = 50; // Performance optimization
|
||||
this.pendingRequests = new Map();
|
||||
this.config = {
|
||||
enabled: config.enabled ?? true,
|
||||
ttl: config.ttl ?? 5000, // 5 second default
|
||||
maxSize: config.maxSize ?? 1000
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (this.config.enabled) {
|
||||
this.log('Request deduplicator initialized for 3x performance boost');
|
||||
// Start cleanup interval
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.cleanup();
|
||||
}, this.config.ttl);
|
||||
}
|
||||
else {
|
||||
this.log('Request deduplicator disabled');
|
||||
}
|
||||
}
|
||||
shouldExecute(operation, params) {
|
||||
// Only execute if enabled and for read operations that benefit from deduplication
|
||||
return this.config.enabled && (operation === 'search' ||
|
||||
operation === 'searchText' ||
|
||||
operation === 'searchByNounTypes' ||
|
||||
operation === 'findSimilar' ||
|
||||
operation === 'get');
|
||||
}
|
||||
async execute(operation, params, next) {
|
||||
if (!this.config.enabled) {
|
||||
return next();
|
||||
}
|
||||
// Create a unique key for this request
|
||||
const key = this.createRequestKey(operation, params);
|
||||
// Check if we already have this request pending
|
||||
const existing = this.pendingRequests.get(key);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
this.log(`Deduplicating request: ${key} (${existing.count} total)`);
|
||||
return existing.promise;
|
||||
}
|
||||
// Execute the request and cache the promise
|
||||
const promise = next();
|
||||
this.pendingRequests.set(key, {
|
||||
promise,
|
||||
timestamp: Date.now(),
|
||||
count: 1
|
||||
});
|
||||
// Clean up when done
|
||||
promise.finally(() => {
|
||||
// Use setTimeout to allow other concurrent requests to use the result
|
||||
setTimeout(() => {
|
||||
this.pendingRequests.delete(key);
|
||||
}, 100);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
/**
|
||||
* Create a unique key for the request based on operation and parameters
|
||||
*/
|
||||
createRequestKey(operation, params) {
|
||||
// Create a stable string representation of the operation and params
|
||||
const paramsKey = this.serializeParams(params);
|
||||
return `${operation}:${paramsKey}`;
|
||||
}
|
||||
/**
|
||||
* Serialize parameters to a consistent string
|
||||
*/
|
||||
serializeParams(params) {
|
||||
if (!params)
|
||||
return 'null';
|
||||
if (typeof params === 'string' || typeof params === 'number') {
|
||||
return String(params);
|
||||
}
|
||||
if (Array.isArray(params)) {
|
||||
// For arrays, create a hash-like representation
|
||||
if (params.length > 100) {
|
||||
// For large arrays (like vectors), use length + first/last elements
|
||||
return `[${params.length}:${params[0]}...${params[params.length - 1]}]`;
|
||||
}
|
||||
return `[${params.join(',')}]`;
|
||||
}
|
||||
if (typeof params === 'object') {
|
||||
// Sort keys for consistent serialization
|
||||
const keys = Object.keys(params).sort();
|
||||
const keyValues = keys.map(key => `${key}:${this.serializeParams(params[key])}`);
|
||||
return `{${keyValues.join(',')}}`;
|
||||
}
|
||||
return String(params);
|
||||
}
|
||||
/**
|
||||
* Clean up expired requests
|
||||
*/
|
||||
cleanup() {
|
||||
const now = Date.now();
|
||||
const expired = [];
|
||||
for (const [key, request] of this.pendingRequests) {
|
||||
if (now - request.timestamp > this.config.ttl) {
|
||||
expired.push(key);
|
||||
}
|
||||
}
|
||||
for (const key of expired) {
|
||||
this.pendingRequests.delete(key);
|
||||
}
|
||||
// Also enforce max size
|
||||
if (this.pendingRequests.size > this.config.maxSize) {
|
||||
const entries = Array.from(this.pendingRequests.entries())
|
||||
.sort(([, a], [, b]) => a.timestamp - b.timestamp); // Oldest first
|
||||
// Remove oldest entries
|
||||
const toRemove = entries.slice(0, entries.length - this.config.maxSize);
|
||||
for (const [key] of toRemove) {
|
||||
this.pendingRequests.delete(key);
|
||||
}
|
||||
}
|
||||
if (expired.length > 0) {
|
||||
this.log(`Cleaned up ${expired.length} expired requests`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get statistics about request deduplication
|
||||
*/
|
||||
getStats() {
|
||||
const requests = Array.from(this.pendingRequests.values());
|
||||
const totalRequests = requests.reduce((sum, req) => sum + req.count, 0);
|
||||
const actualRequests = requests.length;
|
||||
const savedRequests = totalRequests - actualRequests;
|
||||
return {
|
||||
activePendingRequests: actualRequests,
|
||||
totalDeduplicationHits: savedRequests,
|
||||
memoryUsage: `${Math.round(JSON.stringify(requests).length / 1024)}KB`,
|
||||
efficiency: actualRequests > 0 ? `${Math.round((savedRequests / totalRequests) * 100)}% reduction` : '0%'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Force clear all pending requests (for testing)
|
||||
*/
|
||||
clear() {
|
||||
this.pendingRequests.clear();
|
||||
}
|
||||
async onShutdown() {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
}
|
||||
const stats = this.getStats();
|
||||
this.log(`Request deduplicator shutdown: ${stats.efficiency} efficiency achieved`);
|
||||
this.pendingRequests.clear();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=requestDeduplicatorAugmentation.js.map
|
||||
File diff suppressed because one or more lines are too long
190
.recovery-workspace/dist-backup-20250910-141917/augmentations/serverSearchAugmentations.d.ts
vendored
Normal file
190
.recovery-workspace/dist-backup-20250910-141917/augmentations/serverSearchAugmentations.d.ts
vendored
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
* Server Search Augmentations
|
||||
*
|
||||
* This file implements conduit and activation augmentations for browser-server search functionality.
|
||||
* It allows Brainy to search a server-hosted instance and store results locally.
|
||||
*/
|
||||
import { AugmentationResponse, WebSocketConnection } from '../types/augmentations.js';
|
||||
import { BaseAugmentation } from '../augmentations/brainyAugmentation.js';
|
||||
import { BrainyDataInterface } from '../types/brainyDataInterface.js';
|
||||
/**
|
||||
* ServerSearchConduitAugmentation
|
||||
*
|
||||
* A specialized conduit augmentation that provides functionality for searching
|
||||
* a server-hosted Brainy instance and storing results locally.
|
||||
*/
|
||||
export declare class ServerSearchConduitAugmentation extends BaseAugmentation {
|
||||
readonly name = "server-search-conduit";
|
||||
readonly timing: "after";
|
||||
readonly metadata: "readonly";
|
||||
operations: ("addNoun" | "delete" | "addVerb")[];
|
||||
readonly priority = 20;
|
||||
private localDb;
|
||||
constructor(name?: string);
|
||||
/**
|
||||
* Initialize the augmentation
|
||||
*/
|
||||
protected onInitialize(): Promise<void>;
|
||||
/**
|
||||
* Set the local Brainy instance
|
||||
* @param db The Brainy instance to use for local storage
|
||||
*/
|
||||
setLocalDb(db: BrainyDataInterface): void;
|
||||
/**
|
||||
* Stub method for performing search operations via WebSocket
|
||||
* TODO: Implement proper WebSocket communication
|
||||
*/
|
||||
private performSearch;
|
||||
/**
|
||||
* Stub method for performing write operations via WebSocket
|
||||
* TODO: Implement proper WebSocket communication
|
||||
*/
|
||||
private performWrite;
|
||||
/**
|
||||
* Get the local Brainy instance
|
||||
* @returns The local Brainy instance
|
||||
*/
|
||||
getLocalDb(): BrainyDataInterface | null;
|
||||
/**
|
||||
* Execute method - required by BaseAugmentation
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Search the server-hosted Brainy instance and store results locally
|
||||
* @param connectionId The ID of the established connection
|
||||
* @param query The search query
|
||||
* @param limit Maximum number of results to return
|
||||
* @returns Search results
|
||||
*/
|
||||
searchServer(connectionId: string, query: string, limit?: number): Promise<AugmentationResponse<unknown>>;
|
||||
/**
|
||||
* Search the local Brainy instance
|
||||
* @param query The search query
|
||||
* @param limit Maximum number of results to return
|
||||
* @returns Search results
|
||||
*/
|
||||
searchLocal(query: string, limit?: number): Promise<AugmentationResponse<unknown>>;
|
||||
/**
|
||||
* Search both server and local instances, combine results, and store server results locally
|
||||
* @param connectionId The ID of the established connection
|
||||
* @param query The search query
|
||||
* @param limit Maximum number of results to return
|
||||
* @returns Combined search results
|
||||
*/
|
||||
searchCombined(connectionId: string, query: string, limit?: number): Promise<AugmentationResponse<unknown>>;
|
||||
/**
|
||||
* Add data to both local and server instances
|
||||
* @param connectionId The ID of the established connection
|
||||
* @param data Text or vector to add
|
||||
* @param metadata Metadata for the data
|
||||
* @returns ID of the added data
|
||||
*/
|
||||
addToBoth(connectionId: string, data: string | any[], metadata?: any): Promise<AugmentationResponse<string>>;
|
||||
/**
|
||||
* Establish connection to remote server
|
||||
* @param serverUrl Server URL to connect to
|
||||
* @param options Connection options
|
||||
* @returns Connection promise
|
||||
*/
|
||||
establishConnection(serverUrl: string, options?: any): Promise<any>;
|
||||
/**
|
||||
* Close WebSocket connection
|
||||
* @param connectionId Connection ID to close
|
||||
* @returns Promise that resolves when connection is closed
|
||||
*/
|
||||
closeWebSocket(connectionId: string): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* ServerSearchActivationAugmentation
|
||||
*
|
||||
* An activation augmentation that provides actions for server search functionality.
|
||||
*/
|
||||
export declare class ServerSearchActivationAugmentation extends BaseAugmentation {
|
||||
readonly name = "server-search-activation";
|
||||
readonly timing: "after";
|
||||
readonly metadata: "readonly";
|
||||
operations: ("search" | "addNoun")[];
|
||||
readonly priority = 20;
|
||||
private conduitAugmentation;
|
||||
private connections;
|
||||
constructor(name?: string);
|
||||
protected onInitialize(): Promise<void>;
|
||||
protected onShutdown(): Promise<void>;
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Set the conduit augmentation to use for server search
|
||||
* @param conduit The ServerSearchConduitAugmentation to use
|
||||
*/
|
||||
setConduitAugmentation(conduit: ServerSearchConduitAugmentation): void;
|
||||
/**
|
||||
* Store a connection for later use
|
||||
* @param connectionId The ID to use for the connection
|
||||
* @param connection The WebSocket connection
|
||||
*/
|
||||
storeConnection(connectionId: string, connection: WebSocketConnection): void;
|
||||
/**
|
||||
* Get a stored connection
|
||||
* @param connectionId The ID of the connection to retrieve
|
||||
* @returns The WebSocket connection
|
||||
*/
|
||||
getConnection(connectionId: string): WebSocketConnection | undefined;
|
||||
/**
|
||||
* Trigger an action based on a processed command or internal state
|
||||
* @param actionName The name of the action to trigger
|
||||
* @param parameters Optional parameters for the action
|
||||
*/
|
||||
triggerAction(actionName: string, parameters?: Record<string, unknown>): AugmentationResponse<unknown>;
|
||||
/**
|
||||
* Handle the connectToServer action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
private handleConnectToServer;
|
||||
/**
|
||||
* Handle the searchServer action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
private handleSearchServer;
|
||||
/**
|
||||
* Handle the searchLocal action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
private handleSearchLocal;
|
||||
/**
|
||||
* Handle the searchCombined action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
private handleSearchCombined;
|
||||
/**
|
||||
* Handle the addToBoth action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
private handleAddToBoth;
|
||||
/**
|
||||
* Generates an expressive output or response from Brainy
|
||||
* @param knowledgeId The identifier of the knowledge to express
|
||||
* @param format The desired output format (e.g., 'text', 'json')
|
||||
*/
|
||||
generateOutput(knowledgeId: string, format: string): AugmentationResponse<string | Record<string, unknown>>;
|
||||
/**
|
||||
* Interacts with an external system or API
|
||||
* @param systemId The identifier of the external system
|
||||
* @param payload The data to send to the external system
|
||||
*/
|
||||
interactExternal(systemId: string, payload: Record<string, unknown>): AugmentationResponse<unknown>;
|
||||
}
|
||||
/**
|
||||
* Factory function to create server search augmentations
|
||||
* @param serverUrl The URL of the server to connect to
|
||||
* @param options Additional options
|
||||
* @returns An object containing the created augmentations
|
||||
*/
|
||||
export declare function createServerSearchAugmentations(serverUrl: string, options?: {
|
||||
conduitName?: string;
|
||||
activationName?: string;
|
||||
protocols?: string | string[];
|
||||
localDb?: BrainyDataInterface;
|
||||
}): Promise<{
|
||||
conduit: ServerSearchConduitAugmentation;
|
||||
activation: ServerSearchActivationAugmentation;
|
||||
connection: WebSocketConnection;
|
||||
}>;
|
||||
|
|
@ -0,0 +1,586 @@
|
|||
/**
|
||||
* Server Search Augmentations
|
||||
*
|
||||
* This file implements conduit and activation augmentations for browser-server search functionality.
|
||||
* It allows Brainy to search a server-hosted instance and store results locally.
|
||||
*/
|
||||
import { BaseAugmentation } from '../augmentations/brainyAugmentation.js';
|
||||
/**
|
||||
* ServerSearchConduitAugmentation
|
||||
*
|
||||
* A specialized conduit augmentation that provides functionality for searching
|
||||
* a server-hosted Brainy instance and storing results locally.
|
||||
*/
|
||||
export class ServerSearchConduitAugmentation extends BaseAugmentation {
|
||||
constructor(name) {
|
||||
super();
|
||||
this.name = 'server-search-conduit';
|
||||
this.timing = 'after';
|
||||
this.metadata = 'readonly'; // Reads metadata to sync with server
|
||||
this.operations = ['addNoun', 'delete', 'addVerb'];
|
||||
this.priority = 20;
|
||||
this.localDb = null;
|
||||
if (name) {
|
||||
// Override name if provided (though it's readonly, this won't work)
|
||||
// Keep constructor parameter for API compatibility but ignore it
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Initialize the augmentation
|
||||
*/
|
||||
async onInitialize() {
|
||||
// Local DB must be set before initialization
|
||||
if (!this.localDb) {
|
||||
this.log('Local database not set. Call setLocalDb before using server search.', 'warn');
|
||||
return;
|
||||
}
|
||||
this.log('Server search conduit initialized');
|
||||
}
|
||||
/**
|
||||
* Set the local Brainy instance
|
||||
* @param db The Brainy instance to use for local storage
|
||||
*/
|
||||
setLocalDb(db) {
|
||||
this.localDb = db;
|
||||
}
|
||||
/**
|
||||
* Stub method for performing search operations via WebSocket
|
||||
* TODO: Implement proper WebSocket communication
|
||||
*/
|
||||
async performSearch(params) {
|
||||
this.log('Search operation not yet implemented - returning empty results', 'warn');
|
||||
return {
|
||||
success: true,
|
||||
data: []
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Stub method for performing write operations via WebSocket
|
||||
* TODO: Implement proper WebSocket communication
|
||||
*/
|
||||
async performWrite(params) {
|
||||
this.log('Write operation not yet implemented', 'warn');
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'Write operation not implemented'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the local Brainy instance
|
||||
* @returns The local Brainy instance
|
||||
*/
|
||||
getLocalDb() {
|
||||
return this.localDb;
|
||||
}
|
||||
/**
|
||||
* Execute method - required by BaseAugmentation
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
// Just pass through for now - server search operations are handled by the activation augmentation
|
||||
return next();
|
||||
}
|
||||
/**
|
||||
* Search the server-hosted Brainy instance and store results locally
|
||||
* @param connectionId The ID of the established connection
|
||||
* @param query The search query
|
||||
* @param limit Maximum number of results to return
|
||||
* @returns Search results
|
||||
*/
|
||||
async searchServer(connectionId, query, limit = 10) {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('ServerSearchConduitAugmentation not initialized');
|
||||
}
|
||||
try {
|
||||
// Create a search request (TODO: Implement proper WebSocket communication)
|
||||
const readResult = await this.performSearch({
|
||||
connectionId,
|
||||
query: {
|
||||
type: 'search',
|
||||
query,
|
||||
limit
|
||||
}
|
||||
});
|
||||
if (readResult.success && readResult.data) {
|
||||
const searchResults = readResult.data;
|
||||
// Store the results in the local Brainy instance
|
||||
if (this.localDb) {
|
||||
for (const result of searchResults) {
|
||||
// Check if the noun already exists in the local database
|
||||
const existingNoun = await this.localDb.getNoun(result.id);
|
||||
if (!existingNoun) {
|
||||
// Add the noun to the local database
|
||||
await this.localDb.addNoun(result.vector, result.metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: searchResults
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: readResult.error || 'Unknown error searching server'
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error searching server:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: `Error searching server: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Search the local Brainy instance
|
||||
* @param query The search query
|
||||
* @param limit Maximum number of results to return
|
||||
* @returns Search results
|
||||
*/
|
||||
async searchLocal(query, limit = 10) {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('ServerSearchConduitAugmentation not initialized');
|
||||
}
|
||||
try {
|
||||
if (!this.localDb) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'Local database not initialized'
|
||||
};
|
||||
}
|
||||
const results = await this.localDb.searchText(query, limit);
|
||||
return {
|
||||
success: true,
|
||||
data: results
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error searching local database:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: `Error searching local database: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Search both server and local instances, combine results, and store server results locally
|
||||
* @param connectionId The ID of the established connection
|
||||
* @param query The search query
|
||||
* @param limit Maximum number of results to return
|
||||
* @returns Combined search results
|
||||
*/
|
||||
async searchCombined(connectionId, query, limit = 10) {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('ServerSearchConduitAugmentation not initialized');
|
||||
}
|
||||
try {
|
||||
// Search local first
|
||||
const localSearchResult = await this.searchLocal(query, limit);
|
||||
if (!localSearchResult.success) {
|
||||
return localSearchResult;
|
||||
}
|
||||
const localResults = localSearchResult.data;
|
||||
// If we have enough local results, return them
|
||||
if (localResults.length >= limit) {
|
||||
return localSearchResult;
|
||||
}
|
||||
// Otherwise, search server for additional results
|
||||
const serverSearchResult = await this.searchServer(connectionId, query, limit - localResults.length);
|
||||
if (!serverSearchResult.success) {
|
||||
// If server search fails, return local results
|
||||
return localSearchResult;
|
||||
}
|
||||
const serverResults = serverSearchResult.data;
|
||||
// Combine results, removing duplicates
|
||||
const combinedResults = [...localResults];
|
||||
const localIds = new Set(localResults.map((r) => r.id));
|
||||
for (const result of serverResults) {
|
||||
if (!localIds.has(result.id)) {
|
||||
combinedResults.push(result);
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: combinedResults
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error performing combined search:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: `Error performing combined search: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add data to both local and server instances
|
||||
* @param connectionId The ID of the established connection
|
||||
* @param data Text or vector to add
|
||||
* @param metadata Metadata for the data
|
||||
* @returns ID of the added data
|
||||
*/
|
||||
async addToBoth(connectionId, data, metadata = {}) {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('ServerSearchConduitAugmentation not initialized');
|
||||
}
|
||||
try {
|
||||
if (!this.localDb) {
|
||||
return {
|
||||
success: false,
|
||||
data: '',
|
||||
error: 'Local database not initialized'
|
||||
};
|
||||
}
|
||||
// Add to local first - addNoun handles both strings and vectors automatically
|
||||
const id = await this.localDb.addNoun(data, metadata);
|
||||
// Get the vector and metadata
|
||||
const noun = (await this.localDb.getNoun(id));
|
||||
if (!noun) {
|
||||
return {
|
||||
success: false,
|
||||
data: '',
|
||||
error: 'Failed to retrieve newly created noun'
|
||||
};
|
||||
}
|
||||
// Add to server (TODO: Implement proper WebSocket communication)
|
||||
const writeResult = await this.performWrite({
|
||||
connectionId,
|
||||
data: {
|
||||
type: 'addNoun',
|
||||
vector: noun.vector,
|
||||
metadata: noun.metadata
|
||||
}
|
||||
});
|
||||
if (!writeResult.success) {
|
||||
return {
|
||||
success: true,
|
||||
data: id,
|
||||
error: `Added locally but failed to add to server: ${writeResult.error}`
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: id
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error adding data to both:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: '',
|
||||
error: `Error adding data to both: ${error}`
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Establish connection to remote server
|
||||
* @param serverUrl Server URL to connect to
|
||||
* @param options Connection options
|
||||
* @returns Connection promise
|
||||
*/
|
||||
establishConnection(serverUrl, options) {
|
||||
// Stub implementation - remote connection functionality not yet fully implemented in 2.0
|
||||
console.warn('establishConnection: Remote server connections not yet fully implemented in Brainy 2.0');
|
||||
return Promise.resolve({ connected: false, reason: 'Not implemented' });
|
||||
}
|
||||
/**
|
||||
* Close WebSocket connection
|
||||
* @param connectionId Connection ID to close
|
||||
* @returns Promise that resolves when connection is closed
|
||||
*/
|
||||
async closeWebSocket(connectionId) {
|
||||
// Stub implementation - WebSocket functionality not yet fully implemented in 2.0
|
||||
console.warn(`closeWebSocket: WebSocket functionality not yet fully implemented in Brainy 2.0 (connectionId: ${connectionId})`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ServerSearchActivationAugmentation
|
||||
*
|
||||
* An activation augmentation that provides actions for server search functionality.
|
||||
*/
|
||||
export class ServerSearchActivationAugmentation extends BaseAugmentation {
|
||||
constructor(name) {
|
||||
super();
|
||||
this.name = 'server-search-activation';
|
||||
this.timing = 'after';
|
||||
this.metadata = 'readonly'; // Reads metadata for server activation
|
||||
this.operations = ['search', 'addNoun'];
|
||||
this.priority = 20;
|
||||
this.conduitAugmentation = null;
|
||||
this.connections = new Map();
|
||||
if (name) {
|
||||
// Keep constructor parameter for API compatibility but ignore it
|
||||
}
|
||||
}
|
||||
async onInitialize() {
|
||||
// Initialization logic if needed
|
||||
}
|
||||
async onShutdown() {
|
||||
// Cleanup connections
|
||||
this.connections.clear();
|
||||
}
|
||||
async execute(operation, params, next) {
|
||||
// Execute the operation first
|
||||
const result = await next();
|
||||
// Handle server search operations
|
||||
if (operation === 'search' && this.conduitAugmentation) {
|
||||
// Trigger server search when local search happens
|
||||
const connectionId = this.connections.keys().next().value;
|
||||
if (connectionId && params.query) {
|
||||
await this.conduitAugmentation.searchServer(connectionId, params.query, params.limit || 10);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Set the conduit augmentation to use for server search
|
||||
* @param conduit The ServerSearchConduitAugmentation to use
|
||||
*/
|
||||
setConduitAugmentation(conduit) {
|
||||
this.conduitAugmentation = conduit;
|
||||
}
|
||||
/**
|
||||
* Store a connection for later use
|
||||
* @param connectionId The ID to use for the connection
|
||||
* @param connection The WebSocket connection
|
||||
*/
|
||||
storeConnection(connectionId, connection) {
|
||||
this.connections.set(connectionId, connection);
|
||||
}
|
||||
/**
|
||||
* Get a stored connection
|
||||
* @param connectionId The ID of the connection to retrieve
|
||||
* @returns The WebSocket connection
|
||||
*/
|
||||
getConnection(connectionId) {
|
||||
return this.connections.get(connectionId);
|
||||
}
|
||||
/**
|
||||
* Trigger an action based on a processed command or internal state
|
||||
* @param actionName The name of the action to trigger
|
||||
* @param parameters Optional parameters for the action
|
||||
*/
|
||||
triggerAction(actionName, parameters) {
|
||||
if (!this.conduitAugmentation) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'Conduit augmentation not set'
|
||||
};
|
||||
}
|
||||
// Handle different actions
|
||||
switch (actionName) {
|
||||
case 'connectToServer':
|
||||
return this.handleConnectToServer(parameters || {});
|
||||
case 'searchServer':
|
||||
return this.handleSearchServer(parameters || {});
|
||||
case 'searchLocal':
|
||||
return this.handleSearchLocal(parameters || {});
|
||||
case 'searchCombined':
|
||||
return this.handleSearchCombined(parameters || {});
|
||||
case 'addToBoth':
|
||||
return this.handleAddToBoth(parameters || {});
|
||||
default:
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: `Unknown action: ${actionName}`
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle the connectToServer action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
handleConnectToServer(parameters) {
|
||||
const serverUrl = parameters.serverUrl;
|
||||
const protocols = parameters.protocols;
|
||||
if (!serverUrl) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'serverUrl parameter is required'
|
||||
};
|
||||
}
|
||||
// Return a promise that will be resolved when the connection is established
|
||||
return {
|
||||
success: true,
|
||||
data: this.conduitAugmentation.establishConnection(serverUrl, {
|
||||
protocols
|
||||
})
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Handle the searchServer action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
handleSearchServer(parameters) {
|
||||
const connectionId = parameters.connectionId;
|
||||
const query = parameters.query;
|
||||
const limit = parameters.limit || 10;
|
||||
if (!connectionId) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'connectionId parameter is required'
|
||||
};
|
||||
}
|
||||
if (!query) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'query parameter is required'
|
||||
};
|
||||
}
|
||||
// Return a promise that will be resolved when the search is complete
|
||||
return {
|
||||
success: true,
|
||||
data: this.conduitAugmentation.searchServer(connectionId, query, limit)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Handle the searchLocal action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
handleSearchLocal(parameters) {
|
||||
const query = parameters.query;
|
||||
const limit = parameters.limit || 10;
|
||||
if (!query) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'query parameter is required'
|
||||
};
|
||||
}
|
||||
// Return a promise that will be resolved when the search is complete
|
||||
return {
|
||||
success: true,
|
||||
data: this.conduitAugmentation.searchLocal(query, limit)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Handle the searchCombined action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
handleSearchCombined(parameters) {
|
||||
const connectionId = parameters.connectionId;
|
||||
const query = parameters.query;
|
||||
const limit = parameters.limit || 10;
|
||||
if (!connectionId) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'connectionId parameter is required'
|
||||
};
|
||||
}
|
||||
if (!query) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'query parameter is required'
|
||||
};
|
||||
}
|
||||
// Return a promise that will be resolved when the search is complete
|
||||
return {
|
||||
success: true,
|
||||
data: this.conduitAugmentation.searchCombined(connectionId, query, limit)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Handle the addToBoth action
|
||||
* @param parameters Action parameters
|
||||
*/
|
||||
handleAddToBoth(parameters) {
|
||||
const connectionId = parameters.connectionId;
|
||||
const data = parameters.data;
|
||||
const metadata = parameters.metadata || {};
|
||||
if (!connectionId) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'connectionId parameter is required'
|
||||
};
|
||||
}
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'data parameter is required'
|
||||
};
|
||||
}
|
||||
// Return a promise that will be resolved when the add is complete
|
||||
return {
|
||||
success: true,
|
||||
data: this.conduitAugmentation.addToBoth(connectionId, data, metadata)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Generates an expressive output or response from Brainy
|
||||
* @param knowledgeId The identifier of the knowledge to express
|
||||
* @param format The desired output format (e.g., 'text', 'json')
|
||||
*/
|
||||
generateOutput(knowledgeId, format) {
|
||||
// This method is not used for server search functionality
|
||||
return {
|
||||
success: false,
|
||||
data: '',
|
||||
error: 'generateOutput is not implemented for ServerSearchActivationAugmentation'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Interacts with an external system or API
|
||||
* @param systemId The identifier of the external system
|
||||
* @param payload The data to send to the external system
|
||||
*/
|
||||
interactExternal(systemId, payload) {
|
||||
// This method is not used for server search functionality
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: 'interactExternal is not implemented for ServerSearchActivationAugmentation'
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Factory function to create server search augmentations
|
||||
* @param serverUrl The URL of the server to connect to
|
||||
* @param options Additional options
|
||||
* @returns An object containing the created augmentations
|
||||
*/
|
||||
export async function createServerSearchAugmentations(serverUrl, options = {}) {
|
||||
// Create the conduit augmentation
|
||||
const conduit = new ServerSearchConduitAugmentation(options.conduitName);
|
||||
// Set the local database if provided
|
||||
if (options.localDb) {
|
||||
conduit.setLocalDb(options.localDb);
|
||||
}
|
||||
// Create the activation augmentation
|
||||
const activation = new ServerSearchActivationAugmentation(options.activationName);
|
||||
// Note: Augmentations will be initialized when added to BrainyData
|
||||
// Link the augmentations
|
||||
activation.setConduitAugmentation(conduit);
|
||||
// TODO: Connect to the server (stub implementation for now)
|
||||
const connection = {
|
||||
connectionId: `stub-connection-${Date.now()}`,
|
||||
url: serverUrl,
|
||||
status: 'connected',
|
||||
close: async () => { },
|
||||
send: async (data) => { }
|
||||
};
|
||||
// Store the connection in the activation augmentation
|
||||
activation.storeConnection(connection.connectionId, connection);
|
||||
return {
|
||||
conduit,
|
||||
activation,
|
||||
connection
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=serverSearchAugmentations.js.map
|
||||
File diff suppressed because one or more lines are too long
55
.recovery-workspace/dist-backup-20250910-141917/augmentations/storageAugmentation.d.ts
vendored
Normal file
55
.recovery-workspace/dist-backup-20250910-141917/augmentations/storageAugmentation.d.ts
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Storage Augmentation Base Classes
|
||||
*
|
||||
* Unifies storage adapters and augmentations into a single system.
|
||||
* All storage backends are now augmentations for consistency and extensibility.
|
||||
*/
|
||||
import { BaseAugmentation, BrainyAugmentation, AugmentationContext } from './brainyAugmentation.js';
|
||||
import { StorageAdapter } from '../coreTypes.js';
|
||||
/**
|
||||
* Base class for all storage augmentations
|
||||
* Provides the storage adapter to the brain during initialization
|
||||
*/
|
||||
export declare abstract class StorageAugmentation extends BaseAugmentation implements BrainyAugmentation {
|
||||
readonly timing: "replace";
|
||||
readonly metadata: "none";
|
||||
operations: ("storage")[];
|
||||
readonly priority = 100;
|
||||
protected storageAdapter: StorageAdapter | null;
|
||||
constructor(config?: any);
|
||||
/**
|
||||
* Provide the storage adapter before full initialization
|
||||
* This is called during the storage resolution phase
|
||||
*/
|
||||
abstract provideStorage(): Promise<StorageAdapter>;
|
||||
/**
|
||||
* Initialize the augmentation with context
|
||||
* Called after storage has been resolved
|
||||
*/
|
||||
initialize(context: AugmentationContext): Promise<void>;
|
||||
/**
|
||||
* Execute storage operations
|
||||
* For storage augmentations, this replaces the default storage
|
||||
*/
|
||||
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Shutdown and cleanup
|
||||
*/
|
||||
shutdown(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Dynamic storage augmentation that wraps any storage adapter
|
||||
* Used for backward compatibility and zero-config
|
||||
*/
|
||||
export declare class DynamicStorageAugmentation extends StorageAugmentation {
|
||||
private adapter;
|
||||
readonly name = "dynamic-storage";
|
||||
constructor(adapter: StorageAdapter);
|
||||
provideStorage(): Promise<StorageAdapter>;
|
||||
protected onInitialize(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Create a storage augmentation from configuration
|
||||
* Maintains backward compatibility with existing storage config
|
||||
*/
|
||||
export declare function createStorageAugmentationFromConfig(config: any): Promise<StorageAugmentation | null>;
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Storage Augmentation Base Classes
|
||||
*
|
||||
* Unifies storage adapters and augmentations into a single system.
|
||||
* All storage backends are now augmentations for consistency and extensibility.
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
/**
|
||||
* Base class for all storage augmentations
|
||||
* Provides the storage adapter to the brain during initialization
|
||||
*/
|
||||
export class StorageAugmentation extends BaseAugmentation {
|
||||
// Storage augmentations must provide their name via readonly property
|
||||
constructor(config) {
|
||||
super(config);
|
||||
this.timing = 'replace';
|
||||
this.metadata = 'none'; // Storage doesn't directly access metadata
|
||||
this.operations = ['storage']; // Make mutable for TypeScript compatibility
|
||||
this.priority = 100; // High priority for storage
|
||||
this.storageAdapter = null;
|
||||
}
|
||||
/**
|
||||
* Initialize the augmentation with context
|
||||
* Called after storage has been resolved
|
||||
*/
|
||||
async initialize(context) {
|
||||
await super.initialize(context);
|
||||
// Storage adapter should already be provided
|
||||
if (!this.storageAdapter) {
|
||||
this.storageAdapter = await this.provideStorage();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Execute storage operations
|
||||
* For storage augmentations, this replaces the default storage
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
if (operation === 'storage') {
|
||||
// Return our storage adapter
|
||||
return this.storageAdapter;
|
||||
}
|
||||
// Pass through all other operations
|
||||
return next();
|
||||
}
|
||||
/**
|
||||
* Shutdown and cleanup
|
||||
*/
|
||||
async shutdown() {
|
||||
// Cleanup storage adapter if needed
|
||||
if (this.storageAdapter && typeof this.storageAdapter.close === 'function') {
|
||||
await this.storageAdapter.close();
|
||||
}
|
||||
await super.shutdown();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Dynamic storage augmentation that wraps any storage adapter
|
||||
* Used for backward compatibility and zero-config
|
||||
*/
|
||||
export class DynamicStorageAugmentation extends StorageAugmentation {
|
||||
constructor(adapter) {
|
||||
super();
|
||||
this.adapter = adapter;
|
||||
this.name = 'dynamic-storage';
|
||||
this.storageAdapter = adapter;
|
||||
}
|
||||
async provideStorage() {
|
||||
return this.adapter;
|
||||
}
|
||||
async onInitialize() {
|
||||
// Adapter is already provided in constructor
|
||||
await this.adapter.init();
|
||||
this.log(`${this.name} initialized`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a storage augmentation from configuration
|
||||
* Maintains backward compatibility with existing storage config
|
||||
*/
|
||||
export async function createStorageAugmentationFromConfig(config) {
|
||||
// Import storage factory dynamically to avoid circular deps
|
||||
const { createStorage } = await import('../storage/storageFactory.js');
|
||||
try {
|
||||
// Create storage adapter from config
|
||||
const adapter = await createStorage(config);
|
||||
// Wrap in augmentation
|
||||
return new DynamicStorageAugmentation(adapter);
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to create storage augmentation from config:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=storageAugmentation.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"storageAugmentation.js","sourceRoot":"","sources":["../../src/augmentations/storageAugmentation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAA2C,MAAM,yBAAyB,CAAA;AAGnG;;;GAGG;AACH,MAAM,OAAgB,mBAAoB,SAAQ,gBAAgB;IAQhE,sEAAsE;IACtE,YAAY,MAAY;QACtB,KAAK,CAAC,MAAM,CAAC,CAAA;QATN,WAAM,GAAG,SAAkB,CAAA;QAC3B,aAAQ,GAAG,MAAe,CAAA,CAAE,2CAA2C;QAChF,eAAU,GAAG,CAAC,SAAS,CAAkB,CAAA,CAAE,4CAA4C;QAC9E,aAAQ,GAAG,GAAG,CAAA,CAAC,4BAA4B;QAE1C,mBAAc,GAA0B,IAAI,CAAA;IAKtD,CAAC;IAQD;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,OAA4B;QAC3C,MAAM,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;QAC/B,6CAA6C;QAC7C,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAA;QACnD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CACX,SAAiB,EACjB,MAAW,EACX,IAAsB;QAEtB,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,6BAA6B;YAC7B,OAAO,IAAI,CAAC,cAA0B,CAAA;QACxC,CAAC;QAED,oCAAoC;QACpC,OAAO,IAAI,EAAE,CAAA;IACf,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ;QACZ,oCAAoC;QACpC,IAAI,IAAI,CAAC,cAAc,IAAI,OAAQ,IAAI,CAAC,cAAsB,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YACpF,MAAO,IAAI,CAAC,cAAsB,CAAC,KAAK,EAAE,CAAA;QAC5C,CAAC;QACD,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAA;IACxB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,0BAA2B,SAAQ,mBAAmB;IAGjE,YAAoB,OAAuB;QACzC,KAAK,EAAE,CAAA;QADW,YAAO,GAAP,OAAO,CAAgB;QAFlC,SAAI,GAAG,iBAAiB,CAAA;QAI/B,IAAI,CAAC,cAAc,GAAG,OAAO,CAAA;IAC/B,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAES,KAAK,CAAC,YAAY;QAC1B,6CAA6C;QAC7C,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;QACzB,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,cAAc,CAAC,CAAA;IACtC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mCAAmC,CACvD,MAAW;IAEX,4DAA4D;IAC5D,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAA;IAEtE,IAAI,CAAC;QACH,qCAAqC;QACrC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAA;QAE3C,uBAAuB;QACvB,OAAO,IAAI,0BAA0B,CAAC,OAAO,CAAC,CAAA;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,oDAAoD,EAAE,KAAK,CAAC,CAAA;QACzE,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
|
||||
125
.recovery-workspace/dist-backup-20250910-141917/augmentations/storageAugmentations.d.ts
vendored
Normal file
125
.recovery-workspace/dist-backup-20250910-141917/augmentations/storageAugmentations.d.ts
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Storage Augmentations - Concrete Implementations
|
||||
*
|
||||
* These augmentations provide different storage backends for Brainy.
|
||||
* Each wraps an existing storage adapter for backward compatibility.
|
||||
*/
|
||||
import { StorageAugmentation } from './storageAugmentation.js';
|
||||
import { StorageAdapter } from '../coreTypes.js';
|
||||
import { AugmentationManifest } from './manifest.js';
|
||||
/**
|
||||
* Memory Storage Augmentation - Fast in-memory storage
|
||||
*/
|
||||
export declare class MemoryStorageAugmentation extends StorageAugmentation {
|
||||
readonly name = "memory-storage";
|
||||
readonly category: "core";
|
||||
readonly description = "High-performance in-memory storage for development and testing";
|
||||
constructor(config?: any);
|
||||
getManifest(): AugmentationManifest;
|
||||
provideStorage(): Promise<StorageAdapter>;
|
||||
protected onInitialize(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* FileSystem Storage Augmentation - Node.js persistent storage
|
||||
*/
|
||||
export declare class FileSystemStorageAugmentation extends StorageAugmentation {
|
||||
readonly name = "filesystem-storage";
|
||||
readonly category: "core";
|
||||
readonly description = "Persistent file-based storage for Node.js environments";
|
||||
constructor(config?: any);
|
||||
getManifest(): AugmentationManifest;
|
||||
provideStorage(): Promise<StorageAdapter>;
|
||||
protected onInitialize(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* OPFS Storage Augmentation - Browser persistent storage
|
||||
*/
|
||||
export declare class OPFSStorageAugmentation extends StorageAugmentation {
|
||||
readonly name = "opfs-storage";
|
||||
readonly category: "core";
|
||||
readonly description = "Persistent browser storage using Origin Private File System";
|
||||
constructor(config?: any);
|
||||
getManifest(): AugmentationManifest;
|
||||
provideStorage(): Promise<StorageAdapter>;
|
||||
protected onInitialize(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* S3 Storage Augmentation - Amazon S3 cloud storage
|
||||
*/
|
||||
export declare class S3StorageAugmentation extends StorageAugmentation {
|
||||
readonly name = "s3-storage";
|
||||
protected config: {
|
||||
bucketName: string;
|
||||
region?: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
sessionToken?: string;
|
||||
cacheConfig?: any;
|
||||
operationConfig?: any;
|
||||
};
|
||||
constructor(config: {
|
||||
bucketName: string;
|
||||
region?: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
sessionToken?: string;
|
||||
cacheConfig?: any;
|
||||
operationConfig?: any;
|
||||
});
|
||||
provideStorage(): Promise<StorageAdapter>;
|
||||
protected onInitialize(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* R2 Storage Augmentation - Cloudflare R2 storage
|
||||
*/
|
||||
export declare class R2StorageAugmentation extends StorageAugmentation {
|
||||
readonly name = "r2-storage";
|
||||
protected config: {
|
||||
bucketName: string;
|
||||
accountId: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
cacheConfig?: any;
|
||||
};
|
||||
constructor(config: {
|
||||
bucketName: string;
|
||||
accountId: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
cacheConfig?: any;
|
||||
});
|
||||
provideStorage(): Promise<StorageAdapter>;
|
||||
protected onInitialize(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* GCS Storage Augmentation - Google Cloud Storage
|
||||
*/
|
||||
export declare class GCSStorageAugmentation extends StorageAugmentation {
|
||||
readonly name = "gcs-storage";
|
||||
protected config: {
|
||||
bucketName: string;
|
||||
region?: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
endpoint?: string;
|
||||
cacheConfig?: any;
|
||||
};
|
||||
constructor(config: {
|
||||
bucketName: string;
|
||||
region?: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
endpoint?: string;
|
||||
cacheConfig?: any;
|
||||
});
|
||||
provideStorage(): Promise<StorageAdapter>;
|
||||
protected onInitialize(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Auto-select the best storage augmentation for the environment
|
||||
* Maintains zero-config philosophy
|
||||
*/
|
||||
export declare function createAutoStorageAugmentation(options?: {
|
||||
rootDirectory?: string;
|
||||
requestPersistentStorage?: boolean;
|
||||
}): Promise<StorageAugmentation>;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue