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
29
.recovery-workspace/dist-backup-20250910-141917/utils/BoundedRegistry.d.ts
vendored
Normal file
29
.recovery-workspace/dist-backup-20250910-141917/utils/BoundedRegistry.d.ts
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Bounded Registry with LRU eviction
|
||||
* Prevents unbounded memory growth in production
|
||||
*/
|
||||
export declare class BoundedRegistry<T> {
|
||||
private items;
|
||||
private readonly maxSize;
|
||||
constructor(maxSize?: number);
|
||||
/**
|
||||
* Add item to registry, evicting oldest if at capacity
|
||||
*/
|
||||
add(item: T): void;
|
||||
/**
|
||||
* Check if item exists
|
||||
*/
|
||||
has(item: T): boolean;
|
||||
/**
|
||||
* Get all items
|
||||
*/
|
||||
getAll(): T[];
|
||||
/**
|
||||
* Get size
|
||||
*/
|
||||
get size(): number;
|
||||
/**
|
||||
* Clear all items
|
||||
*/
|
||||
clear(): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Bounded Registry with LRU eviction
|
||||
* Prevents unbounded memory growth in production
|
||||
*/
|
||||
export class BoundedRegistry {
|
||||
constructor(maxSize = 10000) {
|
||||
this.items = new Map(); // item -> last accessed timestamp
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
/**
|
||||
* Add item to registry, evicting oldest if at capacity
|
||||
*/
|
||||
add(item) {
|
||||
// Update timestamp if already exists
|
||||
if (this.items.has(item)) {
|
||||
this.items.delete(item); // Remove to re-add at end
|
||||
this.items.set(item, Date.now());
|
||||
return;
|
||||
}
|
||||
// Evict oldest if at capacity
|
||||
if (this.items.size >= this.maxSize) {
|
||||
const oldest = this.items.entries().next().value;
|
||||
if (oldest) {
|
||||
this.items.delete(oldest[0]);
|
||||
}
|
||||
}
|
||||
this.items.set(item, Date.now());
|
||||
}
|
||||
/**
|
||||
* Check if item exists
|
||||
*/
|
||||
has(item) {
|
||||
return this.items.has(item);
|
||||
}
|
||||
/**
|
||||
* Get all items
|
||||
*/
|
||||
getAll() {
|
||||
return Array.from(this.items.keys());
|
||||
}
|
||||
/**
|
||||
* Get size
|
||||
*/
|
||||
get size() {
|
||||
return this.items.size;
|
||||
}
|
||||
/**
|
||||
* Clear all items
|
||||
*/
|
||||
clear() {
|
||||
this.items.clear();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=BoundedRegistry.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"BoundedRegistry.js","sourceRoot":"","sources":["../../src/utils/BoundedRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,eAAe;IAI1B,YAAY,UAAkB,KAAK;QAH3B,UAAK,GAAG,IAAI,GAAG,EAAa,CAAA,CAAC,kCAAkC;QAIrE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,IAAO;QACT,qCAAqC;QACrC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA,CAAC,0BAA0B;YAClD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;YAChC,OAAM;QACR,CAAC;QAED,8BAA8B;QAC9B,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAChD,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;YAC9B,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAClC,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,IAAO;QACT,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IACtC,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;IACxB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;CACF"}
|
||||
103
.recovery-workspace/dist-backup-20250910-141917/utils/adaptiveBackpressure.d.ts
vendored
Normal file
103
.recovery-workspace/dist-backup-20250910-141917/utils/adaptiveBackpressure.d.ts
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Adaptive Backpressure System
|
||||
* Automatically manages request flow and prevents system overload
|
||||
* Self-healing with pattern learning for optimal throughput
|
||||
*/
|
||||
interface BackpressureMetrics {
|
||||
queueDepth: number;
|
||||
processingRate: number;
|
||||
errorRate: number;
|
||||
latency: number;
|
||||
throughput: number;
|
||||
}
|
||||
interface BackpressureConfig {
|
||||
maxQueueDepth: number;
|
||||
targetLatency: number;
|
||||
minThroughput: number;
|
||||
adaptationRate: number;
|
||||
}
|
||||
/**
|
||||
* Self-healing backpressure manager that learns from load patterns
|
||||
*/
|
||||
export declare class AdaptiveBackpressure {
|
||||
private logger;
|
||||
private queue;
|
||||
private activeOperations;
|
||||
private maxConcurrent;
|
||||
private metrics;
|
||||
private config;
|
||||
private patterns;
|
||||
private circuitState;
|
||||
private circuitOpenTime;
|
||||
private circuitFailures;
|
||||
private circuitThreshold;
|
||||
private circuitTimeout;
|
||||
private operationTimes;
|
||||
private completedOps;
|
||||
private errorOps;
|
||||
private lastAdaptation;
|
||||
/**
|
||||
* Request permission to proceed with an operation
|
||||
*/
|
||||
requestPermission(operationId: string, priority?: number): Promise<void>;
|
||||
/**
|
||||
* Release permission after operation completes
|
||||
*/
|
||||
releasePermission(operationId: string, success?: boolean): void;
|
||||
/**
|
||||
* Check if circuit breaker is open
|
||||
*/
|
||||
private isCircuitOpen;
|
||||
/**
|
||||
* Open the circuit breaker
|
||||
*/
|
||||
private openCircuit;
|
||||
/**
|
||||
* Close the circuit breaker
|
||||
*/
|
||||
private closeCircuit;
|
||||
/**
|
||||
* Adapt configuration based on metrics
|
||||
*/
|
||||
private adaptIfNeeded;
|
||||
/**
|
||||
* Update current metrics
|
||||
*/
|
||||
private updateMetrics;
|
||||
/**
|
||||
* Learn from current load patterns
|
||||
*/
|
||||
private learnPattern;
|
||||
/**
|
||||
* Calculate optimal concurrency based on Little's Law
|
||||
*/
|
||||
private calculateOptimalConcurrency;
|
||||
/**
|
||||
* Adapt configuration based on metrics and patterns
|
||||
*/
|
||||
private adaptConfiguration;
|
||||
/**
|
||||
* Predict future load based on patterns
|
||||
*/
|
||||
predictLoad(futureSeconds?: number): number;
|
||||
/**
|
||||
* Get current configuration and metrics
|
||||
*/
|
||||
getStatus(): {
|
||||
config: BackpressureConfig;
|
||||
metrics: BackpressureMetrics;
|
||||
circuit: string;
|
||||
maxConcurrent: number;
|
||||
activeOps: number;
|
||||
queueLength: number;
|
||||
};
|
||||
/**
|
||||
* Reset to default state
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
/**
|
||||
* Get the global backpressure instance
|
||||
*/
|
||||
export declare function getGlobalBackpressure(): AdaptiveBackpressure;
|
||||
export {};
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
/**
|
||||
* Adaptive Backpressure System
|
||||
* Automatically manages request flow and prevents system overload
|
||||
* Self-healing with pattern learning for optimal throughput
|
||||
*/
|
||||
import { createModuleLogger } from './logger.js';
|
||||
/**
|
||||
* Self-healing backpressure manager that learns from load patterns
|
||||
*/
|
||||
export class AdaptiveBackpressure {
|
||||
constructor() {
|
||||
this.logger = createModuleLogger('AdaptiveBackpressure');
|
||||
// Queue management
|
||||
this.queue = [];
|
||||
// Active operations tracking
|
||||
this.activeOperations = new Set();
|
||||
this.maxConcurrent = 100;
|
||||
// Metrics tracking
|
||||
this.metrics = {
|
||||
queueDepth: 0,
|
||||
processingRate: 0,
|
||||
errorRate: 0,
|
||||
latency: 0,
|
||||
throughput: 0
|
||||
};
|
||||
// Configuration that adapts over time
|
||||
this.config = {
|
||||
maxQueueDepth: 1000,
|
||||
targetLatency: 1000, // 1 second target
|
||||
minThroughput: 10, // Minimum 10 ops/sec
|
||||
adaptationRate: 0.1 // How quickly to adapt
|
||||
};
|
||||
// Historical patterns for learning
|
||||
this.patterns = [];
|
||||
// Circuit breaker state
|
||||
this.circuitState = 'closed';
|
||||
this.circuitOpenTime = 0;
|
||||
this.circuitFailures = 0;
|
||||
this.circuitThreshold = 5;
|
||||
this.circuitTimeout = 30000; // 30 seconds
|
||||
// Performance tracking
|
||||
this.operationTimes = new Map();
|
||||
this.completedOps = [];
|
||||
this.errorOps = 0;
|
||||
this.lastAdaptation = Date.now();
|
||||
}
|
||||
/**
|
||||
* Request permission to proceed with an operation
|
||||
*/
|
||||
async requestPermission(operationId, priority = 1) {
|
||||
// Check circuit breaker
|
||||
if (this.isCircuitOpen()) {
|
||||
throw new Error('Circuit breaker is open - system is recovering');
|
||||
}
|
||||
// Fast path for low load
|
||||
if (this.activeOperations.size < this.maxConcurrent * 0.5 && this.queue.length === 0) {
|
||||
this.activeOperations.add(operationId);
|
||||
this.operationTimes.set(operationId, Date.now());
|
||||
return;
|
||||
}
|
||||
// Check if we need to queue
|
||||
if (this.activeOperations.size >= this.maxConcurrent) {
|
||||
// Check queue depth
|
||||
if (this.queue.length >= this.config.maxQueueDepth) {
|
||||
throw new Error('Backpressure queue is full - try again later');
|
||||
}
|
||||
// Add to queue and wait
|
||||
return new Promise((resolve) => {
|
||||
this.queue.push({
|
||||
id: operationId,
|
||||
priority,
|
||||
timestamp: Date.now(),
|
||||
resolve
|
||||
});
|
||||
// Sort queue by priority (higher priority first)
|
||||
this.queue.sort((a, b) => b.priority - a.priority);
|
||||
// Update metrics
|
||||
this.metrics.queueDepth = this.queue.length;
|
||||
});
|
||||
}
|
||||
// Add to active operations
|
||||
this.activeOperations.add(operationId);
|
||||
this.operationTimes.set(operationId, Date.now());
|
||||
}
|
||||
/**
|
||||
* Release permission after operation completes
|
||||
*/
|
||||
releasePermission(operationId, success = true) {
|
||||
// Remove from active operations
|
||||
this.activeOperations.delete(operationId);
|
||||
// Track completion time
|
||||
const startTime = this.operationTimes.get(operationId);
|
||||
if (startTime) {
|
||||
const duration = Date.now() - startTime;
|
||||
this.completedOps.push(duration);
|
||||
this.operationTimes.delete(operationId);
|
||||
// Keep array bounded
|
||||
if (this.completedOps.length > 1000) {
|
||||
this.completedOps = this.completedOps.slice(-500);
|
||||
}
|
||||
}
|
||||
// Track errors for circuit breaker
|
||||
if (!success) {
|
||||
this.errorOps++;
|
||||
this.circuitFailures++;
|
||||
// Check if we should open circuit
|
||||
if (this.circuitFailures >= this.circuitThreshold) {
|
||||
this.openCircuit();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Reset circuit failures on success
|
||||
if (this.circuitState === 'half-open') {
|
||||
this.closeCircuit();
|
||||
}
|
||||
}
|
||||
// Process queue if there are waiting operations
|
||||
if (this.queue.length > 0 && this.activeOperations.size < this.maxConcurrent) {
|
||||
const next = this.queue.shift();
|
||||
if (next) {
|
||||
this.activeOperations.add(next.id);
|
||||
this.operationTimes.set(next.id, Date.now());
|
||||
next.resolve();
|
||||
// Update metrics
|
||||
this.metrics.queueDepth = this.queue.length;
|
||||
}
|
||||
}
|
||||
// Adapt configuration periodically
|
||||
this.adaptIfNeeded();
|
||||
}
|
||||
/**
|
||||
* Check if circuit breaker is open
|
||||
*/
|
||||
isCircuitOpen() {
|
||||
if (this.circuitState === 'open') {
|
||||
// Check if timeout has passed
|
||||
if (Date.now() - this.circuitOpenTime > this.circuitTimeout) {
|
||||
this.circuitState = 'half-open';
|
||||
this.logger.info('Circuit breaker entering half-open state');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Open the circuit breaker
|
||||
*/
|
||||
openCircuit() {
|
||||
if (this.circuitState !== 'open') {
|
||||
this.circuitState = 'open';
|
||||
this.circuitOpenTime = Date.now();
|
||||
this.logger.warn('Circuit breaker opened due to high error rate');
|
||||
// Reduce load immediately
|
||||
this.maxConcurrent = Math.max(10, Math.floor(this.maxConcurrent * 0.3));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Close the circuit breaker
|
||||
*/
|
||||
closeCircuit() {
|
||||
this.circuitState = 'closed';
|
||||
this.circuitFailures = 0;
|
||||
this.logger.info('Circuit breaker closed - system recovered');
|
||||
// Gradually increase capacity
|
||||
this.maxConcurrent = Math.min(500, Math.floor(this.maxConcurrent * 1.5));
|
||||
}
|
||||
/**
|
||||
* Adapt configuration based on metrics
|
||||
*/
|
||||
adaptIfNeeded() {
|
||||
const now = Date.now();
|
||||
if (now - this.lastAdaptation < 5000) { // Adapt every 5 seconds
|
||||
return;
|
||||
}
|
||||
this.lastAdaptation = now;
|
||||
this.updateMetrics();
|
||||
// Learn from current patterns
|
||||
this.learnPattern();
|
||||
// Adapt based on metrics
|
||||
this.adaptConfiguration();
|
||||
}
|
||||
/**
|
||||
* Update current metrics
|
||||
*/
|
||||
updateMetrics() {
|
||||
// Calculate processing rate
|
||||
this.metrics.processingRate = this.completedOps.length > 0
|
||||
? 1000 / (this.completedOps.reduce((a, b) => a + b, 0) / this.completedOps.length)
|
||||
: 0;
|
||||
// Calculate error rate
|
||||
const totalOps = this.completedOps.length + this.errorOps;
|
||||
this.metrics.errorRate = totalOps > 0 ? this.errorOps / totalOps : 0;
|
||||
// Calculate average latency
|
||||
this.metrics.latency = this.completedOps.length > 0
|
||||
? this.completedOps.reduce((a, b) => a + b, 0) / this.completedOps.length
|
||||
: 0;
|
||||
// Calculate throughput
|
||||
this.metrics.throughput = this.activeOperations.size + this.metrics.processingRate;
|
||||
// Reset error counter periodically
|
||||
if (this.completedOps.length > 100) {
|
||||
this.errorOps = Math.floor(this.errorOps * 0.9); // Decay error count
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Learn from current load patterns
|
||||
*/
|
||||
learnPattern() {
|
||||
const currentLoad = this.activeOperations.size + this.queue.length;
|
||||
const optimalConcurrency = this.calculateOptimalConcurrency();
|
||||
this.patterns.push({
|
||||
timestamp: Date.now(),
|
||||
load: currentLoad,
|
||||
optimal: optimalConcurrency
|
||||
});
|
||||
// Keep patterns bounded
|
||||
if (this.patterns.length > 1000) {
|
||||
this.patterns = this.patterns.slice(-500);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate optimal concurrency based on Little's Law
|
||||
*/
|
||||
calculateOptimalConcurrency() {
|
||||
// Little's Law: L = λ * W
|
||||
// L = number of requests in system
|
||||
// λ = arrival rate
|
||||
// W = average time in system
|
||||
if (this.metrics.latency === 0 || this.metrics.processingRate === 0) {
|
||||
return this.maxConcurrent; // Keep current if no data
|
||||
}
|
||||
// Target: Keep latency under target while maximizing throughput
|
||||
const targetConcurrency = Math.ceil(this.metrics.processingRate * (this.config.targetLatency / 1000));
|
||||
// Adjust based on error rate
|
||||
const errorAdjustment = 1 - (this.metrics.errorRate * 2); // Reduce by up to 50% for errors
|
||||
// Apply adjustment
|
||||
const adjusted = Math.floor(targetConcurrency * errorAdjustment);
|
||||
// Apply bounds
|
||||
return Math.max(10, Math.min(500, adjusted));
|
||||
}
|
||||
/**
|
||||
* Adapt configuration based on metrics and patterns
|
||||
*/
|
||||
adaptConfiguration() {
|
||||
const optimal = this.calculateOptimalConcurrency();
|
||||
const current = this.maxConcurrent;
|
||||
// Smooth adaptation using exponential moving average
|
||||
const newConcurrency = Math.floor(current * (1 - this.config.adaptationRate) +
|
||||
optimal * this.config.adaptationRate);
|
||||
// Check if adaptation is needed
|
||||
if (Math.abs(newConcurrency - current) > current * 0.1) { // 10% threshold
|
||||
const oldValue = this.maxConcurrent;
|
||||
this.maxConcurrent = newConcurrency;
|
||||
this.logger.debug('Adapted concurrency', {
|
||||
from: oldValue,
|
||||
to: newConcurrency,
|
||||
metrics: this.metrics
|
||||
});
|
||||
}
|
||||
// Adapt queue depth based on throughput
|
||||
if (this.metrics.throughput > 0) {
|
||||
// Allow queue depth to be 10 seconds worth of throughput
|
||||
this.config.maxQueueDepth = Math.max(100, Math.min(10000, Math.floor(this.metrics.throughput * 10)));
|
||||
}
|
||||
// Adapt circuit breaker threshold based on error patterns
|
||||
if (this.metrics.errorRate < 0.01 && this.circuitThreshold > 5) {
|
||||
this.circuitThreshold = Math.max(5, this.circuitThreshold - 1);
|
||||
}
|
||||
else if (this.metrics.errorRate > 0.05 && this.circuitThreshold < 20) {
|
||||
this.circuitThreshold = Math.min(20, this.circuitThreshold + 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Predict future load based on patterns
|
||||
*/
|
||||
predictLoad(futureSeconds = 60) {
|
||||
if (this.patterns.length < 10) {
|
||||
return this.maxConcurrent; // Not enough data
|
||||
}
|
||||
// Simple linear regression on recent patterns
|
||||
const recentPatterns = this.patterns.slice(-50);
|
||||
const n = recentPatterns.length;
|
||||
// Calculate averages
|
||||
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
||||
const startTime = recentPatterns[0].timestamp;
|
||||
recentPatterns.forEach(p => {
|
||||
const x = (p.timestamp - startTime) / 1000; // Time in seconds
|
||||
const y = p.load;
|
||||
sumX += x;
|
||||
sumY += y;
|
||||
sumXY += x * y;
|
||||
sumX2 += x * x;
|
||||
});
|
||||
// Calculate slope and intercept
|
||||
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
|
||||
const intercept = (sumY - slope * sumX) / n;
|
||||
// Predict future load
|
||||
const currentTime = (Date.now() - startTime) / 1000;
|
||||
const predictedLoad = intercept + slope * (currentTime + futureSeconds);
|
||||
return Math.max(0, Math.min(this.config.maxQueueDepth, Math.floor(predictedLoad)));
|
||||
}
|
||||
/**
|
||||
* Get current configuration and metrics
|
||||
*/
|
||||
getStatus() {
|
||||
return {
|
||||
config: { ...this.config },
|
||||
metrics: { ...this.metrics },
|
||||
circuit: this.circuitState,
|
||||
maxConcurrent: this.maxConcurrent,
|
||||
activeOps: this.activeOperations.size,
|
||||
queueLength: this.queue.length
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Reset to default state
|
||||
*/
|
||||
reset() {
|
||||
this.queue = [];
|
||||
this.activeOperations.clear();
|
||||
this.operationTimes.clear();
|
||||
this.completedOps = [];
|
||||
this.errorOps = 0;
|
||||
this.patterns = [];
|
||||
this.circuitState = 'closed';
|
||||
this.circuitFailures = 0;
|
||||
this.maxConcurrent = 100;
|
||||
this.logger.info('Backpressure system reset to defaults');
|
||||
}
|
||||
}
|
||||
// Global singleton instance
|
||||
let globalBackpressure = null;
|
||||
/**
|
||||
* Get the global backpressure instance
|
||||
*/
|
||||
export function getGlobalBackpressure() {
|
||||
if (!globalBackpressure) {
|
||||
globalBackpressure = new AdaptiveBackpressure();
|
||||
}
|
||||
return globalBackpressure;
|
||||
}
|
||||
//# sourceMappingURL=adaptiveBackpressure.js.map
|
||||
File diff suppressed because one or more lines are too long
117
.recovery-workspace/dist-backup-20250910-141917/utils/adaptiveSocketManager.d.ts
vendored
Normal file
117
.recovery-workspace/dist-backup-20250910-141917/utils/adaptiveSocketManager.d.ts
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* Adaptive Socket Manager
|
||||
* Automatically manages socket pools and connection settings based on load patterns
|
||||
* Zero-configuration approach that learns and adapts to workload characteristics
|
||||
*/
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||
interface LoadMetrics {
|
||||
requestsPerSecond: number;
|
||||
pendingRequests: number;
|
||||
socketUtilization: number;
|
||||
errorRate: number;
|
||||
latencyP50: number;
|
||||
latencyP95: number;
|
||||
memoryUsage: number;
|
||||
}
|
||||
interface AdaptiveConfig {
|
||||
maxSockets: number;
|
||||
maxFreeSockets: number;
|
||||
keepAliveTimeout: number;
|
||||
connectionTimeout: number;
|
||||
socketTimeout: number;
|
||||
batchSize: number;
|
||||
}
|
||||
/**
|
||||
* Adaptive Socket Manager that automatically scales based on load patterns
|
||||
*/
|
||||
export declare class AdaptiveSocketManager {
|
||||
private logger;
|
||||
private config;
|
||||
private metrics;
|
||||
private history;
|
||||
private maxHistorySize;
|
||||
private lastAdaptationTime;
|
||||
private adaptationInterval;
|
||||
private consecutiveHighLoad;
|
||||
private consecutiveLowLoad;
|
||||
private requestStartTimes;
|
||||
private requestLatencies;
|
||||
private errorCount;
|
||||
private successCount;
|
||||
private lastMetricReset;
|
||||
private currentAgent;
|
||||
private currentHandler;
|
||||
/**
|
||||
* Get or create an optimized HTTP handler
|
||||
*/
|
||||
getHttpHandler(): NodeHttpHandler;
|
||||
/**
|
||||
* Get current batch size recommendation
|
||||
*/
|
||||
getBatchSize(): number;
|
||||
/**
|
||||
* Track request start
|
||||
*/
|
||||
trackRequestStart(requestId: string): void;
|
||||
/**
|
||||
* Track request completion
|
||||
*/
|
||||
trackRequestComplete(requestId: string, success: boolean): void;
|
||||
/**
|
||||
* Check if we should adapt configuration
|
||||
*/
|
||||
private adaptIfNeeded;
|
||||
/**
|
||||
* Update current metrics
|
||||
*/
|
||||
private updateMetrics;
|
||||
/**
|
||||
* Analyze metrics and adapt configuration
|
||||
*/
|
||||
private analyzeAndAdapt;
|
||||
/**
|
||||
* Detect high load conditions
|
||||
*/
|
||||
private detectHighLoad;
|
||||
/**
|
||||
* Detect low load conditions
|
||||
*/
|
||||
private detectLowLoad;
|
||||
/**
|
||||
* Scale up resources for high load
|
||||
*/
|
||||
private scaleUp;
|
||||
/**
|
||||
* Scale down resources for low load
|
||||
*/
|
||||
private scaleDown;
|
||||
/**
|
||||
* Handle error conditions by adjusting configuration
|
||||
*/
|
||||
private handleErrors;
|
||||
/**
|
||||
* Check if we should recreate the handler
|
||||
*/
|
||||
private shouldRecreateHandler;
|
||||
/**
|
||||
* Get current configuration (for monitoring)
|
||||
*/
|
||||
getConfig(): Readonly<AdaptiveConfig>;
|
||||
/**
|
||||
* Get current metrics (for monitoring)
|
||||
*/
|
||||
getMetrics(): Readonly<LoadMetrics>;
|
||||
/**
|
||||
* Predict optimal configuration based on historical data
|
||||
*/
|
||||
predictOptimalConfig(): AdaptiveConfig;
|
||||
/**
|
||||
* Reset to default configuration
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
/**
|
||||
* Get the global socket manager instance
|
||||
*/
|
||||
export declare function getGlobalSocketManager(): AdaptiveSocketManager;
|
||||
export {};
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
/**
|
||||
* Adaptive Socket Manager
|
||||
* Automatically manages socket pools and connection settings based on load patterns
|
||||
* Zero-configuration approach that learns and adapts to workload characteristics
|
||||
*/
|
||||
import { Agent as HttpsAgent } from 'https';
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||
import { createModuleLogger } from './logger.js';
|
||||
/**
|
||||
* Adaptive Socket Manager that automatically scales based on load patterns
|
||||
*/
|
||||
export class AdaptiveSocketManager {
|
||||
constructor() {
|
||||
this.logger = createModuleLogger('AdaptiveSocketManager');
|
||||
// Current configuration
|
||||
this.config = {
|
||||
maxSockets: 100, // Start conservative
|
||||
maxFreeSockets: 20,
|
||||
keepAliveTimeout: 60000,
|
||||
connectionTimeout: 10000,
|
||||
socketTimeout: 60000,
|
||||
batchSize: 10
|
||||
};
|
||||
// Performance tracking
|
||||
this.metrics = {
|
||||
requestsPerSecond: 0,
|
||||
pendingRequests: 0,
|
||||
socketUtilization: 0,
|
||||
errorRate: 0,
|
||||
latencyP50: 0,
|
||||
latencyP95: 0,
|
||||
memoryUsage: 0
|
||||
};
|
||||
// Historical data for learning
|
||||
this.history = [];
|
||||
this.maxHistorySize = 100;
|
||||
// Adaptation state
|
||||
this.lastAdaptationTime = 0;
|
||||
this.adaptationInterval = 5000; // Check every 5 seconds
|
||||
this.consecutiveHighLoad = 0;
|
||||
this.consecutiveLowLoad = 0;
|
||||
// Request tracking
|
||||
this.requestStartTimes = new Map();
|
||||
this.requestLatencies = [];
|
||||
this.errorCount = 0;
|
||||
this.successCount = 0;
|
||||
this.lastMetricReset = Date.now();
|
||||
// Socket pool instances
|
||||
this.currentAgent = null;
|
||||
this.currentHandler = null;
|
||||
}
|
||||
/**
|
||||
* Get or create an optimized HTTP handler
|
||||
*/
|
||||
getHttpHandler() {
|
||||
// Adapt configuration if needed
|
||||
this.adaptIfNeeded();
|
||||
// Create new handler if configuration changed
|
||||
if (!this.currentHandler || this.shouldRecreateHandler()) {
|
||||
this.currentAgent = new HttpsAgent({
|
||||
keepAlive: true,
|
||||
maxSockets: this.config.maxSockets,
|
||||
maxFreeSockets: this.config.maxFreeSockets,
|
||||
timeout: this.config.keepAliveTimeout,
|
||||
scheduling: 'fifo' // Fair scheduling for high-volume scenarios
|
||||
});
|
||||
this.currentHandler = new NodeHttpHandler({
|
||||
httpsAgent: this.currentAgent,
|
||||
connectionTimeout: this.config.connectionTimeout,
|
||||
socketTimeout: this.config.socketTimeout
|
||||
});
|
||||
this.logger.debug('Created new HTTP handler with config:', this.config);
|
||||
}
|
||||
return this.currentHandler;
|
||||
}
|
||||
/**
|
||||
* Get current batch size recommendation
|
||||
*/
|
||||
getBatchSize() {
|
||||
this.adaptIfNeeded();
|
||||
return this.config.batchSize;
|
||||
}
|
||||
/**
|
||||
* Track request start
|
||||
*/
|
||||
trackRequestStart(requestId) {
|
||||
this.requestStartTimes.set(requestId, Date.now());
|
||||
this.metrics.pendingRequests++;
|
||||
}
|
||||
/**
|
||||
* Track request completion
|
||||
*/
|
||||
trackRequestComplete(requestId, success) {
|
||||
const startTime = this.requestStartTimes.get(requestId);
|
||||
if (startTime) {
|
||||
const latency = Date.now() - startTime;
|
||||
this.requestLatencies.push(latency);
|
||||
this.requestStartTimes.delete(requestId);
|
||||
// Keep latency array bounded
|
||||
if (this.requestLatencies.length > 1000) {
|
||||
this.requestLatencies = this.requestLatencies.slice(-500);
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
this.successCount++;
|
||||
}
|
||||
else {
|
||||
this.errorCount++;
|
||||
}
|
||||
this.metrics.pendingRequests = Math.max(0, this.metrics.pendingRequests - 1);
|
||||
}
|
||||
/**
|
||||
* Check if we should adapt configuration
|
||||
*/
|
||||
adaptIfNeeded() {
|
||||
const now = Date.now();
|
||||
if (now - this.lastAdaptationTime < this.adaptationInterval) {
|
||||
return;
|
||||
}
|
||||
this.lastAdaptationTime = now;
|
||||
this.updateMetrics();
|
||||
this.analyzeAndAdapt();
|
||||
}
|
||||
/**
|
||||
* Update current metrics
|
||||
*/
|
||||
updateMetrics() {
|
||||
const now = Date.now();
|
||||
const timeSinceReset = (now - this.lastMetricReset) / 1000;
|
||||
// Calculate requests per second
|
||||
const totalRequests = this.successCount + this.errorCount;
|
||||
this.metrics.requestsPerSecond = timeSinceReset > 0
|
||||
? totalRequests / timeSinceReset
|
||||
: 0;
|
||||
// Calculate error rate
|
||||
this.metrics.errorRate = totalRequests > 0
|
||||
? this.errorCount / totalRequests
|
||||
: 0;
|
||||
// Calculate latency percentiles
|
||||
if (this.requestLatencies.length > 0) {
|
||||
const sorted = [...this.requestLatencies].sort((a, b) => a - b);
|
||||
const p50Index = Math.floor(sorted.length * 0.5);
|
||||
const p95Index = Math.floor(sorted.length * 0.95);
|
||||
this.metrics.latencyP50 = sorted[p50Index] || 0;
|
||||
this.metrics.latencyP95 = sorted[p95Index] || 0;
|
||||
}
|
||||
// Calculate socket utilization
|
||||
this.metrics.socketUtilization = this.metrics.pendingRequests / this.config.maxSockets;
|
||||
// Memory usage
|
||||
if (typeof process !== 'undefined' && process.memoryUsage) {
|
||||
const memUsage = process.memoryUsage();
|
||||
this.metrics.memoryUsage = memUsage.heapUsed / memUsage.heapTotal;
|
||||
}
|
||||
// Add to history
|
||||
this.history.push({ ...this.metrics });
|
||||
if (this.history.length > this.maxHistorySize) {
|
||||
this.history.shift();
|
||||
}
|
||||
// Reset counters periodically
|
||||
if (timeSinceReset > 60) {
|
||||
this.lastMetricReset = now;
|
||||
this.successCount = 0;
|
||||
this.errorCount = 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Analyze metrics and adapt configuration
|
||||
*/
|
||||
analyzeAndAdapt() {
|
||||
const wasConfig = { ...this.config };
|
||||
// Detect high load conditions
|
||||
const isHighLoad = this.detectHighLoad();
|
||||
const isLowLoad = this.detectLowLoad();
|
||||
const hasErrors = this.metrics.errorRate > 0.01; // More than 1% errors
|
||||
if (isHighLoad) {
|
||||
this.consecutiveHighLoad++;
|
||||
this.consecutiveLowLoad = 0;
|
||||
if (this.consecutiveHighLoad >= 2) { // Wait for 2 consecutive high load readings
|
||||
this.scaleUp();
|
||||
}
|
||||
}
|
||||
else if (isLowLoad) {
|
||||
this.consecutiveLowLoad++;
|
||||
this.consecutiveHighLoad = 0;
|
||||
if (this.consecutiveLowLoad >= 6) { // Wait longer before scaling down
|
||||
this.scaleDown();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Reset counters if load is normal
|
||||
this.consecutiveHighLoad = Math.max(0, this.consecutiveHighLoad - 1);
|
||||
this.consecutiveLowLoad = Math.max(0, this.consecutiveLowLoad - 1);
|
||||
}
|
||||
// Handle error conditions
|
||||
if (hasErrors) {
|
||||
this.handleErrors();
|
||||
}
|
||||
// Log significant changes
|
||||
if (JSON.stringify(wasConfig) !== JSON.stringify(this.config)) {
|
||||
this.logger.info('Adapted configuration', {
|
||||
from: wasConfig,
|
||||
to: this.config,
|
||||
metrics: this.metrics
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Detect high load conditions
|
||||
*/
|
||||
detectHighLoad() {
|
||||
return (this.metrics.socketUtilization > 0.7 || // Sockets heavily used
|
||||
this.metrics.pendingRequests > this.config.maxSockets * 0.8 || // Many pending requests
|
||||
this.metrics.latencyP95 > 5000 || // High latency
|
||||
this.metrics.requestsPerSecond > 100 // High request rate
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Detect low load conditions
|
||||
*/
|
||||
detectLowLoad() {
|
||||
return (this.metrics.socketUtilization < 0.2 && // Sockets barely used
|
||||
this.metrics.pendingRequests < 5 && // Few pending requests
|
||||
this.metrics.latencyP95 < 1000 && // Low latency
|
||||
this.metrics.requestsPerSecond < 10 && // Low request rate
|
||||
this.metrics.memoryUsage < 0.5 // Low memory usage
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Scale up resources for high load
|
||||
*/
|
||||
scaleUp() {
|
||||
// Increase socket limits progressively
|
||||
const scaleFactor = this.metrics.errorRate > 0.05 ? 1.5 : 2.0; // Scale more aggressively if no errors
|
||||
this.config.maxSockets = Math.min(2000, // Hard limit to prevent resource exhaustion
|
||||
Math.ceil(this.config.maxSockets * scaleFactor));
|
||||
this.config.maxFreeSockets = Math.min(200, Math.ceil(this.config.maxSockets * 0.1) // Keep 10% as free sockets
|
||||
);
|
||||
// Increase batch size for better throughput
|
||||
this.config.batchSize = Math.min(100, Math.ceil(this.config.batchSize * 1.5));
|
||||
// Adjust timeouts for high load
|
||||
this.config.keepAliveTimeout = 120000; // Keep connections alive longer
|
||||
this.config.connectionTimeout = 15000; // Allow more time for connections
|
||||
this.config.socketTimeout = 90000; // Allow more time for responses
|
||||
this.logger.debug('Scaled up for high load', {
|
||||
sockets: this.config.maxSockets,
|
||||
batchSize: this.config.batchSize
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Scale down resources for low load
|
||||
*/
|
||||
scaleDown() {
|
||||
// Only scale down if memory pressure is low
|
||||
if (this.metrics.memoryUsage > 0.7) {
|
||||
return;
|
||||
}
|
||||
// Decrease socket limits conservatively
|
||||
this.config.maxSockets = Math.max(50, // Minimum sockets
|
||||
Math.floor(this.config.maxSockets * 0.7));
|
||||
this.config.maxFreeSockets = Math.max(10, Math.floor(this.config.maxSockets * 0.2) // Keep 20% as free sockets
|
||||
);
|
||||
// Decrease batch size
|
||||
this.config.batchSize = Math.max(5, Math.floor(this.config.batchSize * 0.7));
|
||||
// Adjust timeouts for low load
|
||||
this.config.keepAliveTimeout = 60000;
|
||||
this.config.connectionTimeout = 10000;
|
||||
this.config.socketTimeout = 60000;
|
||||
this.logger.debug('Scaled down for low load', {
|
||||
sockets: this.config.maxSockets,
|
||||
batchSize: this.config.batchSize
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handle error conditions by adjusting configuration
|
||||
*/
|
||||
handleErrors() {
|
||||
const errorRate = this.metrics.errorRate;
|
||||
if (errorRate > 0.1) { // More than 10% errors
|
||||
// Severe errors - back off aggressively
|
||||
this.config.maxSockets = Math.max(50, Math.floor(this.config.maxSockets * 0.5));
|
||||
this.config.batchSize = Math.max(1, Math.floor(this.config.batchSize * 0.3));
|
||||
this.config.connectionTimeout = Math.min(30000, this.config.connectionTimeout * 2);
|
||||
this.config.socketTimeout = Math.min(120000, this.config.socketTimeout * 2);
|
||||
this.logger.warn('High error rate detected, backing off', {
|
||||
errorRate,
|
||||
newConfig: this.config
|
||||
});
|
||||
}
|
||||
else if (errorRate > 0.05) { // More than 5% errors
|
||||
// Moderate errors - reduce load slightly
|
||||
this.config.batchSize = Math.max(1, Math.floor(this.config.batchSize * 0.7));
|
||||
this.config.connectionTimeout = Math.min(20000, this.config.connectionTimeout * 1.2);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if we should recreate the handler
|
||||
*/
|
||||
shouldRecreateHandler() {
|
||||
if (!this.currentAgent)
|
||||
return true;
|
||||
// Recreate if socket configuration changed significantly
|
||||
const currentMaxSockets = this.currentAgent.maxSockets;
|
||||
const socketsDiff = Math.abs(currentMaxSockets - this.config.maxSockets);
|
||||
return socketsDiff > currentMaxSockets * 0.5; // 50% change threshold
|
||||
}
|
||||
/**
|
||||
* Get current configuration (for monitoring)
|
||||
*/
|
||||
getConfig() {
|
||||
return { ...this.config };
|
||||
}
|
||||
/**
|
||||
* Get current metrics (for monitoring)
|
||||
*/
|
||||
getMetrics() {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
/**
|
||||
* Predict optimal configuration based on historical data
|
||||
*/
|
||||
predictOptimalConfig() {
|
||||
if (this.history.length < 10) {
|
||||
return this.config; // Not enough data to predict
|
||||
}
|
||||
// Analyze recent history
|
||||
const recentHistory = this.history.slice(-20);
|
||||
const avgRPS = recentHistory.reduce((sum, m) => sum + m.requestsPerSecond, 0) / recentHistory.length;
|
||||
const maxRPS = Math.max(...recentHistory.map(m => m.requestsPerSecond));
|
||||
const avgLatency = recentHistory.reduce((sum, m) => sum + m.latencyP95, 0) / recentHistory.length;
|
||||
// Predict optimal socket count based on request patterns
|
||||
const optimalSockets = Math.min(2000, Math.max(50, Math.ceil(maxRPS * 2)));
|
||||
// Predict optimal batch size based on latency
|
||||
const optimalBatchSize = avgLatency < 1000 ? 50 : avgLatency < 3000 ? 20 : 10;
|
||||
return {
|
||||
maxSockets: optimalSockets,
|
||||
maxFreeSockets: Math.ceil(optimalSockets * 0.15),
|
||||
keepAliveTimeout: avgRPS > 50 ? 120000 : 60000,
|
||||
connectionTimeout: avgLatency > 3000 ? 20000 : 10000,
|
||||
socketTimeout: avgLatency > 3000 ? 90000 : 60000,
|
||||
batchSize: optimalBatchSize
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Reset to default configuration
|
||||
*/
|
||||
reset() {
|
||||
this.config = {
|
||||
maxSockets: 100,
|
||||
maxFreeSockets: 20,
|
||||
keepAliveTimeout: 60000,
|
||||
connectionTimeout: 10000,
|
||||
socketTimeout: 60000,
|
||||
batchSize: 10
|
||||
};
|
||||
this.consecutiveHighLoad = 0;
|
||||
this.consecutiveLowLoad = 0;
|
||||
this.history = [];
|
||||
this.requestLatencies = [];
|
||||
this.errorCount = 0;
|
||||
this.successCount = 0;
|
||||
// Force recreation of handler
|
||||
this.currentAgent = null;
|
||||
this.currentHandler = null;
|
||||
this.logger.info('Reset to default configuration');
|
||||
}
|
||||
}
|
||||
// Global singleton instance
|
||||
let globalSocketManager = null;
|
||||
/**
|
||||
* Get the global socket manager instance
|
||||
*/
|
||||
export function getGlobalSocketManager() {
|
||||
if (!globalSocketManager) {
|
||||
globalSocketManager = new AdaptiveSocketManager();
|
||||
}
|
||||
return globalSocketManager;
|
||||
}
|
||||
//# sourceMappingURL=adaptiveSocketManager.js.map
|
||||
File diff suppressed because one or more lines are too long
125
.recovery-workspace/dist-backup-20250910-141917/utils/autoConfiguration.d.ts
vendored
Normal file
125
.recovery-workspace/dist-backup-20250910-141917/utils/autoConfiguration.d.ts
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Automatic Configuration System for Brainy Vector Database
|
||||
* Detects environment, resources, and data patterns to provide optimal settings
|
||||
*/
|
||||
export interface AutoConfigResult {
|
||||
environment: 'browser' | 'nodejs' | 'serverless' | 'unknown';
|
||||
availableMemory: number;
|
||||
cpuCores: number;
|
||||
threadingAvailable: boolean;
|
||||
persistentStorageAvailable: boolean;
|
||||
s3StorageDetected: boolean;
|
||||
recommendedConfig: {
|
||||
expectedDatasetSize: number;
|
||||
maxMemoryUsage: number;
|
||||
targetSearchLatency: number;
|
||||
enablePartitioning: boolean;
|
||||
enableCompression: boolean;
|
||||
enableDistributedSearch: boolean;
|
||||
enablePredictiveCaching: boolean;
|
||||
partitionStrategy: 'semantic' | 'hash';
|
||||
maxNodesPerPartition: number;
|
||||
semanticClusters: number;
|
||||
};
|
||||
optimizationFlags: {
|
||||
useMemoryMapping: boolean;
|
||||
aggressiveCaching: boolean;
|
||||
backgroundOptimization: boolean;
|
||||
compressionLevel: 'none' | 'light' | 'aggressive';
|
||||
};
|
||||
}
|
||||
export interface DatasetAnalysis {
|
||||
estimatedSize: number;
|
||||
vectorDimension?: number;
|
||||
growthRate?: number;
|
||||
accessPatterns?: 'read-heavy' | 'write-heavy' | 'balanced';
|
||||
}
|
||||
/**
|
||||
* Automatic configuration system that detects environment and optimizes settings
|
||||
*/
|
||||
export declare class AutoConfiguration {
|
||||
private static instance;
|
||||
private cachedConfig;
|
||||
private datasetStats;
|
||||
private constructor();
|
||||
static getInstance(): AutoConfiguration;
|
||||
/**
|
||||
* Detect environment and generate optimal configuration
|
||||
*/
|
||||
detectAndConfigure(hints?: {
|
||||
expectedDataSize?: number;
|
||||
s3Available?: boolean;
|
||||
memoryBudget?: number;
|
||||
}): Promise<AutoConfigResult>;
|
||||
/**
|
||||
* Update configuration based on runtime dataset analysis
|
||||
*/
|
||||
adaptToDataset(analysis: DatasetAnalysis): Promise<AutoConfigResult>;
|
||||
/**
|
||||
* Learn from performance metrics and adjust configuration
|
||||
*/
|
||||
learnFromPerformance(metrics: {
|
||||
averageSearchTime: number;
|
||||
memoryUsage: number;
|
||||
cacheHitRate: number;
|
||||
errorRate: number;
|
||||
}): Promise<Partial<AutoConfigResult['recommendedConfig']>>;
|
||||
/**
|
||||
* Get minimal configuration for quick setup
|
||||
*/
|
||||
getQuickSetupConfig(scenario: 'small' | 'medium' | 'large' | 'enterprise'): Promise<{
|
||||
expectedDatasetSize: number;
|
||||
maxMemoryUsage: number;
|
||||
targetSearchLatency: number;
|
||||
s3Required: boolean;
|
||||
}>;
|
||||
/**
|
||||
* Detect the current runtime environment
|
||||
*/
|
||||
private detectEnvironment;
|
||||
/**
|
||||
* Detect available system resources
|
||||
*/
|
||||
private detectResources;
|
||||
/**
|
||||
* Detect available storage capabilities
|
||||
*/
|
||||
private detectStorageCapabilities;
|
||||
/**
|
||||
* Generate recommended configuration based on detected environment and resources
|
||||
*/
|
||||
private generateRecommendedConfig;
|
||||
/**
|
||||
* Generate optimization flags based on environment and resources
|
||||
*/
|
||||
private generateOptimizationFlags;
|
||||
/**
|
||||
* Adapt configuration based on actual dataset analysis
|
||||
*/
|
||||
private adaptConfigurationToData;
|
||||
/**
|
||||
* Estimate dataset size if not provided
|
||||
*/
|
||||
private estimateDatasetSize;
|
||||
/**
|
||||
* Reset cached configuration (for testing or manual refresh)
|
||||
*/
|
||||
resetCache(): void;
|
||||
}
|
||||
/**
|
||||
* Convenience function for quick auto-configuration
|
||||
*/
|
||||
export declare function autoConfigureBrainy(hints?: {
|
||||
expectedDataSize?: number;
|
||||
s3Available?: boolean;
|
||||
memoryBudget?: number;
|
||||
}): Promise<AutoConfigResult>;
|
||||
/**
|
||||
* Get quick setup configuration for common scenarios
|
||||
*/
|
||||
export declare function getQuickSetup(scenario: 'small' | 'medium' | 'large' | 'enterprise'): Promise<{
|
||||
expectedDatasetSize: number;
|
||||
maxMemoryUsage: number;
|
||||
targetSearchLatency: number;
|
||||
s3Required: boolean;
|
||||
}>;
|
||||
|
|
@ -0,0 +1,341 @@
|
|||
/**
|
||||
* Automatic Configuration System for Brainy Vector Database
|
||||
* Detects environment, resources, and data patterns to provide optimal settings
|
||||
*/
|
||||
import { isBrowser, isNode, isThreadingAvailable } from './environment.js';
|
||||
/**
|
||||
* Automatic configuration system that detects environment and optimizes settings
|
||||
*/
|
||||
export class AutoConfiguration {
|
||||
constructor() {
|
||||
this.cachedConfig = null;
|
||||
this.datasetStats = { estimatedSize: 0 };
|
||||
}
|
||||
static getInstance() {
|
||||
if (!AutoConfiguration.instance) {
|
||||
AutoConfiguration.instance = new AutoConfiguration();
|
||||
}
|
||||
return AutoConfiguration.instance;
|
||||
}
|
||||
/**
|
||||
* Detect environment and generate optimal configuration
|
||||
*/
|
||||
async detectAndConfigure(hints) {
|
||||
if (this.cachedConfig && !hints) {
|
||||
return this.cachedConfig;
|
||||
}
|
||||
const environment = this.detectEnvironment();
|
||||
const resources = await this.detectResources();
|
||||
const storage = await this.detectStorageCapabilities(hints?.s3Available);
|
||||
const config = {
|
||||
environment,
|
||||
...resources,
|
||||
...storage,
|
||||
recommendedConfig: this.generateRecommendedConfig(environment, resources, hints),
|
||||
optimizationFlags: this.generateOptimizationFlags(environment, resources)
|
||||
};
|
||||
this.cachedConfig = config;
|
||||
return config;
|
||||
}
|
||||
/**
|
||||
* Update configuration based on runtime dataset analysis
|
||||
*/
|
||||
async adaptToDataset(analysis) {
|
||||
this.datasetStats = analysis;
|
||||
// Regenerate configuration with dataset insights
|
||||
const currentConfig = await this.detectAndConfigure();
|
||||
const adaptedConfig = this.adaptConfigurationToData(currentConfig, analysis);
|
||||
this.cachedConfig = adaptedConfig;
|
||||
return adaptedConfig;
|
||||
}
|
||||
/**
|
||||
* Learn from performance metrics and adjust configuration
|
||||
*/
|
||||
async learnFromPerformance(metrics) {
|
||||
const adjustments = {};
|
||||
// Learn from search performance
|
||||
if (metrics.averageSearchTime > 200) {
|
||||
// Too slow - optimize for speed
|
||||
adjustments.enableDistributedSearch = true;
|
||||
adjustments.maxNodesPerPartition = Math.max(10000, (this.cachedConfig?.recommendedConfig.maxNodesPerPartition || 50000) * 0.8);
|
||||
}
|
||||
else if (metrics.averageSearchTime < 50) {
|
||||
// Very fast - can optimize for quality
|
||||
adjustments.maxNodesPerPartition = Math.min(100000, (this.cachedConfig?.recommendedConfig.maxNodesPerPartition || 50000) * 1.2);
|
||||
}
|
||||
// Learn from memory usage
|
||||
if (metrics.memoryUsage > (this.cachedConfig?.recommendedConfig.maxMemoryUsage || 0) * 0.9) {
|
||||
// High memory usage - enable compression
|
||||
adjustments.enableCompression = true;
|
||||
}
|
||||
// Learn from cache performance
|
||||
if (metrics.cacheHitRate < 0.7) {
|
||||
// Poor cache performance - enable predictive caching
|
||||
adjustments.enablePredictiveCaching = true;
|
||||
}
|
||||
// Update cached config with learned adjustments
|
||||
if (this.cachedConfig) {
|
||||
this.cachedConfig.recommendedConfig = {
|
||||
...this.cachedConfig.recommendedConfig,
|
||||
...adjustments
|
||||
};
|
||||
}
|
||||
return adjustments;
|
||||
}
|
||||
/**
|
||||
* Get minimal configuration for quick setup
|
||||
*/
|
||||
async getQuickSetupConfig(scenario) {
|
||||
const environment = this.detectEnvironment();
|
||||
const resources = await this.detectResources();
|
||||
switch (scenario) {
|
||||
case 'small':
|
||||
return {
|
||||
expectedDatasetSize: 10000,
|
||||
maxMemoryUsage: Math.min(resources.availableMemory * 0.3, 1024 * 1024 * 1024), // 1GB max
|
||||
targetSearchLatency: 100,
|
||||
s3Required: false
|
||||
};
|
||||
case 'medium':
|
||||
return {
|
||||
expectedDatasetSize: 100000,
|
||||
maxMemoryUsage: Math.min(resources.availableMemory * 0.5, 4 * 1024 * 1024 * 1024), // 4GB max
|
||||
targetSearchLatency: 150,
|
||||
s3Required: environment === 'serverless'
|
||||
};
|
||||
case 'large':
|
||||
return {
|
||||
expectedDatasetSize: 1000000,
|
||||
maxMemoryUsage: Math.min(resources.availableMemory * 0.7, 8 * 1024 * 1024 * 1024), // 8GB max
|
||||
targetSearchLatency: 200,
|
||||
s3Required: true
|
||||
};
|
||||
case 'enterprise':
|
||||
return {
|
||||
expectedDatasetSize: 10000000,
|
||||
maxMemoryUsage: Math.min(resources.availableMemory * 0.8, 32 * 1024 * 1024 * 1024), // 32GB max
|
||||
targetSearchLatency: 300,
|
||||
s3Required: true
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Detect the current runtime environment
|
||||
*/
|
||||
detectEnvironment() {
|
||||
if (isBrowser()) {
|
||||
return 'browser';
|
||||
}
|
||||
if (isNode()) {
|
||||
// Check for serverless environment indicators
|
||||
if (process.env.AWS_LAMBDA_FUNCTION_NAME ||
|
||||
process.env.VERCEL ||
|
||||
process.env.NETLIFY ||
|
||||
process.env.CLOUDFLARE_WORKERS) {
|
||||
return 'serverless';
|
||||
}
|
||||
return 'nodejs';
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
/**
|
||||
* Detect available system resources
|
||||
*/
|
||||
async detectResources() {
|
||||
let availableMemory = 2 * 1024 * 1024 * 1024; // Default 2GB
|
||||
let cpuCores = 4; // Default 4 cores
|
||||
// Browser memory detection
|
||||
if (isBrowser()) {
|
||||
// @ts-ignore - navigator.deviceMemory is experimental
|
||||
if (navigator.deviceMemory) {
|
||||
// @ts-ignore
|
||||
availableMemory = navigator.deviceMemory * 1024 * 1024 * 1024 * 0.3; // Use 30% of device memory
|
||||
}
|
||||
else {
|
||||
availableMemory = 512 * 1024 * 1024; // Conservative 512MB for browsers
|
||||
}
|
||||
cpuCores = navigator.hardwareConcurrency || 4;
|
||||
}
|
||||
// Node.js memory detection
|
||||
if (isNode()) {
|
||||
try {
|
||||
const os = await import('os');
|
||||
availableMemory = os.totalmem() * 0.7; // Use 70% of total memory
|
||||
cpuCores = os.cpus().length;
|
||||
}
|
||||
catch (error) {
|
||||
// Fallback to defaults
|
||||
}
|
||||
}
|
||||
return {
|
||||
availableMemory,
|
||||
cpuCores,
|
||||
threadingAvailable: isThreadingAvailable()
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Detect available storage capabilities
|
||||
*/
|
||||
async detectStorageCapabilities(s3Hint) {
|
||||
let persistentStorageAvailable = false;
|
||||
let s3StorageDetected = s3Hint || false;
|
||||
if (isBrowser()) {
|
||||
// Check for OPFS support
|
||||
persistentStorageAvailable = 'navigator' in globalThis &&
|
||||
'storage' in navigator &&
|
||||
'getDirectory' in navigator.storage;
|
||||
}
|
||||
if (isNode()) {
|
||||
persistentStorageAvailable = true; // Always available in Node.js
|
||||
// Check for AWS SDK or S3 environment variables
|
||||
s3StorageDetected = s3Hint ||
|
||||
!!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||
|
||||
!!(process.env.S3_BUCKET_NAME);
|
||||
}
|
||||
return {
|
||||
persistentStorageAvailable,
|
||||
s3StorageDetected
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Generate recommended configuration based on detected environment and resources
|
||||
*/
|
||||
generateRecommendedConfig(environment, resources, hints) {
|
||||
const datasetSize = hints?.expectedDataSize || this.estimateDatasetSize();
|
||||
const memoryBudget = hints?.memoryBudget || Math.floor(resources.availableMemory * 0.6);
|
||||
// Base configuration
|
||||
let config = {
|
||||
expectedDatasetSize: datasetSize,
|
||||
maxMemoryUsage: memoryBudget,
|
||||
targetSearchLatency: 150,
|
||||
enablePartitioning: datasetSize > 25000,
|
||||
enableCompression: environment === 'browser' || memoryBudget < 2 * 1024 * 1024 * 1024,
|
||||
enableDistributedSearch: resources.cpuCores > 2 && datasetSize > 50000,
|
||||
enablePredictiveCaching: true,
|
||||
partitionStrategy: 'semantic',
|
||||
maxNodesPerPartition: 50000,
|
||||
semanticClusters: 8
|
||||
};
|
||||
// Environment-specific adjustments
|
||||
switch (environment) {
|
||||
case 'browser':
|
||||
config = {
|
||||
...config,
|
||||
maxMemoryUsage: Math.min(memoryBudget, 1024 * 1024 * 1024), // Cap at 1GB
|
||||
targetSearchLatency: 200, // More lenient for browsers
|
||||
enableCompression: true, // Always enable for browsers
|
||||
maxNodesPerPartition: 25000, // Smaller partitions
|
||||
semanticClusters: 4 // Fewer clusters to save memory
|
||||
};
|
||||
break;
|
||||
case 'serverless':
|
||||
config = {
|
||||
...config,
|
||||
targetSearchLatency: 500, // Account for cold starts
|
||||
enablePredictiveCaching: false, // Avoid background processes
|
||||
maxNodesPerPartition: 30000 // Moderate partition size
|
||||
};
|
||||
break;
|
||||
case 'nodejs':
|
||||
config = {
|
||||
...config,
|
||||
targetSearchLatency: 100, // Aggressive for Node.js
|
||||
maxNodesPerPartition: Math.min(100000, Math.floor(datasetSize / 10)), // Larger partitions
|
||||
semanticClusters: Math.min(16, Math.max(4, Math.floor(datasetSize / 50000))) // Scale clusters with data
|
||||
};
|
||||
break;
|
||||
}
|
||||
// Dataset size adjustments
|
||||
if (datasetSize > 1000000) {
|
||||
config.semanticClusters = Math.min(32, Math.floor(datasetSize / 100000));
|
||||
config.maxNodesPerPartition = 100000;
|
||||
}
|
||||
else if (datasetSize < 10000) {
|
||||
config.enablePartitioning = false;
|
||||
config.enableDistributedSearch = false;
|
||||
config.partitionStrategy = 'semantic'; // Keep semantic but disable partitioning
|
||||
}
|
||||
return config;
|
||||
}
|
||||
/**
|
||||
* Generate optimization flags based on environment and resources
|
||||
*/
|
||||
generateOptimizationFlags(environment, resources) {
|
||||
return {
|
||||
useMemoryMapping: environment === 'nodejs' && resources.availableMemory > 4 * 1024 * 1024 * 1024,
|
||||
aggressiveCaching: resources.availableMemory > 2 * 1024 * 1024 * 1024,
|
||||
backgroundOptimization: environment !== 'serverless' && resources.cpuCores > 2,
|
||||
compressionLevel: resources.availableMemory < 1024 * 1024 * 1024 ? 'aggressive' :
|
||||
resources.availableMemory < 4 * 1024 * 1024 * 1024 ? 'light' : 'none'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Adapt configuration based on actual dataset analysis
|
||||
*/
|
||||
adaptConfigurationToData(baseConfig, analysis) {
|
||||
const updatedConfig = { ...baseConfig };
|
||||
// Adjust based on actual dataset size
|
||||
if (analysis.estimatedSize !== baseConfig.recommendedConfig.expectedDatasetSize) {
|
||||
const sizeRatio = analysis.estimatedSize / baseConfig.recommendedConfig.expectedDatasetSize;
|
||||
updatedConfig.recommendedConfig.expectedDatasetSize = analysis.estimatedSize;
|
||||
// Scale partition size with dataset
|
||||
if (sizeRatio > 2) {
|
||||
updatedConfig.recommendedConfig.maxNodesPerPartition = Math.min(100000, Math.floor(updatedConfig.recommendedConfig.maxNodesPerPartition * 1.5));
|
||||
updatedConfig.recommendedConfig.semanticClusters = Math.min(32, Math.floor(updatedConfig.recommendedConfig.semanticClusters * 1.5));
|
||||
}
|
||||
}
|
||||
// Adjust based on vector dimension
|
||||
if (analysis.vectorDimension) {
|
||||
if (analysis.vectorDimension > 1024) {
|
||||
// High-dimensional vectors - optimize for compression
|
||||
updatedConfig.recommendedConfig.enableCompression = true;
|
||||
updatedConfig.optimizationFlags.compressionLevel = 'aggressive';
|
||||
}
|
||||
}
|
||||
// Adjust based on access patterns
|
||||
if (analysis.accessPatterns === 'read-heavy') {
|
||||
updatedConfig.recommendedConfig.enablePredictiveCaching = true;
|
||||
updatedConfig.optimizationFlags.aggressiveCaching = true;
|
||||
}
|
||||
else if (analysis.accessPatterns === 'write-heavy') {
|
||||
updatedConfig.recommendedConfig.enablePredictiveCaching = false;
|
||||
updatedConfig.optimizationFlags.backgroundOptimization = false;
|
||||
}
|
||||
return updatedConfig;
|
||||
}
|
||||
/**
|
||||
* Estimate dataset size if not provided
|
||||
*/
|
||||
estimateDatasetSize() {
|
||||
// Start with conservative estimate
|
||||
const environment = this.detectEnvironment();
|
||||
switch (environment) {
|
||||
case 'browser': return 10000;
|
||||
case 'serverless': return 50000;
|
||||
case 'nodejs': return 100000;
|
||||
default: return 25000;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reset cached configuration (for testing or manual refresh)
|
||||
*/
|
||||
resetCache() {
|
||||
this.cachedConfig = null;
|
||||
this.datasetStats = { estimatedSize: 0 };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Convenience function for quick auto-configuration
|
||||
*/
|
||||
export async function autoConfigureBrainy(hints) {
|
||||
const autoConfig = AutoConfiguration.getInstance();
|
||||
return autoConfig.detectAndConfigure(hints);
|
||||
}
|
||||
/**
|
||||
* Get quick setup configuration for common scenarios
|
||||
*/
|
||||
export async function getQuickSetup(scenario) {
|
||||
const autoConfig = AutoConfiguration.getInstance();
|
||||
return autoConfig.getQuickSetupConfig(scenario);
|
||||
}
|
||||
//# sourceMappingURL=autoConfiguration.js.map
|
||||
File diff suppressed because one or more lines are too long
217
.recovery-workspace/dist-backup-20250910-141917/utils/brainyTypes.d.ts
vendored
Normal file
217
.recovery-workspace/dist-backup-20250910-141917/utils/brainyTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/**
|
||||
* BrainyTypes - Complete type management for Brainy
|
||||
*
|
||||
* Provides type lists, validation, and intelligent suggestions
|
||||
* for nouns and verbs using semantic embeddings.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { BrainyTypes } from '@soulcraft/brainy'
|
||||
*
|
||||
* // Get all available types
|
||||
* const nounTypes = BrainyTypes.nouns // ['Person', 'Organization', ...]
|
||||
* const verbTypes = BrainyTypes.verbs // ['Contains', 'Creates', ...]
|
||||
*
|
||||
* // Validate types
|
||||
* BrainyTypes.isValidNoun('Person') // true
|
||||
* BrainyTypes.isValidVerb('Unknown') // false
|
||||
*
|
||||
* // Get intelligent suggestions
|
||||
* const personData = {
|
||||
* name: 'John Doe',
|
||||
* email: 'john@example.com'
|
||||
* }
|
||||
* const suggestion = await BrainyTypes.suggestNoun(personData)
|
||||
* console.log(suggestion.type) // 'Person'
|
||||
* console.log(suggestion.confidence) // 0.92
|
||||
* ```
|
||||
*/
|
||||
import { NounType, VerbType } from '../types/graphTypes.js';
|
||||
/**
|
||||
* Type suggestion result
|
||||
*/
|
||||
export interface TypeSuggestion {
|
||||
/** The suggested type */
|
||||
type: NounType | VerbType;
|
||||
/** Confidence score between 0 and 1 */
|
||||
confidence: number;
|
||||
/** Human-readable explanation */
|
||||
reason?: string;
|
||||
/** Alternative suggestions */
|
||||
alternatives?: Array<{
|
||||
type: NounType | VerbType;
|
||||
confidence: number;
|
||||
}>;
|
||||
}
|
||||
/**
|
||||
* BrainyTypes - Complete type management for Brainy
|
||||
*
|
||||
* Static class providing type lists, validation, and intelligent suggestions.
|
||||
* No instantiation needed - all methods are static.
|
||||
*/
|
||||
export declare class BrainyTypes {
|
||||
private static instance;
|
||||
private static initialized;
|
||||
/**
|
||||
* All available noun types
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.nouns.forEach(type => console.log(type))
|
||||
* // 'Person', 'Organization', 'Location', ...
|
||||
* ```
|
||||
*/
|
||||
static readonly nouns: readonly NounType[];
|
||||
/**
|
||||
* All available verb types
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.verbs.forEach(type => console.log(type))
|
||||
* // 'Contains', 'Creates', 'RelatedTo', ...
|
||||
* ```
|
||||
*/
|
||||
static readonly verbs: readonly VerbType[];
|
||||
/**
|
||||
* Get or create the internal matcher instance
|
||||
*/
|
||||
private static getInternalMatcher;
|
||||
/**
|
||||
* Check if a string is a valid noun type
|
||||
*
|
||||
* @param type The type string to check
|
||||
* @returns True if valid noun type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.isValidNoun('Person') // true
|
||||
* BrainyTypes.isValidNoun('Unknown') // false
|
||||
* BrainyTypes.isValidNoun('Contains') // false (it's a verb)
|
||||
* ```
|
||||
*/
|
||||
static isValidNoun(type: string): type is NounType;
|
||||
/**
|
||||
* Check if a string is a valid verb type
|
||||
*
|
||||
* @param type The type string to check
|
||||
* @returns True if valid verb type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.isValidVerb('Contains') // true
|
||||
* BrainyTypes.isValidVerb('Unknown') // false
|
||||
* BrainyTypes.isValidVerb('Person') // false (it's a noun)
|
||||
* ```
|
||||
*/
|
||||
static isValidVerb(type: string): type is VerbType;
|
||||
/**
|
||||
* Suggest the most appropriate noun type for an object
|
||||
*
|
||||
* @param data The object or data to analyze
|
||||
* @returns Promise resolving to type suggestion with confidence score
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const data = {
|
||||
* title: 'Quarterly Report',
|
||||
* author: 'Jane Smith',
|
||||
* pages: 42
|
||||
* }
|
||||
* const suggestion = await BrainyTypes.suggestNoun(data)
|
||||
* console.log(suggestion.type) // 'Document'
|
||||
* console.log(suggestion.confidence) // 0.88
|
||||
*
|
||||
* // Check alternatives if confidence is low
|
||||
* if (suggestion.confidence < 0.8) {
|
||||
* console.log('Also consider:', suggestion.alternatives)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
static suggestNoun(data: any): Promise<TypeSuggestion>;
|
||||
/**
|
||||
* Suggest the most appropriate verb type for a relationship
|
||||
*
|
||||
* @param source The source entity
|
||||
* @param target The target entity
|
||||
* @param hint Optional hint about the relationship
|
||||
* @returns Promise resolving to type suggestion with confidence score
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const source = { type: 'Person', name: 'Alice' }
|
||||
* const target = { type: 'Document', title: 'Research Paper' }
|
||||
*
|
||||
* const suggestion = await BrainyTypes.suggestVerb(source, target, 'authored')
|
||||
* console.log(suggestion.type) // 'CreatedBy'
|
||||
* console.log(suggestion.confidence) // 0.91
|
||||
*
|
||||
* // Without hint
|
||||
* const suggestion2 = await BrainyTypes.suggestVerb(source, target)
|
||||
* console.log(suggestion2.type) // 'RelatedTo' (more generic)
|
||||
* ```
|
||||
*/
|
||||
static suggestVerb(source: any, target: any, hint?: string): Promise<TypeSuggestion>;
|
||||
/**
|
||||
* Get a noun type by name (with validation)
|
||||
*
|
||||
* @param name The noun type name
|
||||
* @returns The NounType enum value
|
||||
* @throws Error if invalid noun type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const type = BrainyTypes.getNoun('Person') // NounType.Person
|
||||
* const bad = BrainyTypes.getNoun('Unknown') // throws Error
|
||||
* ```
|
||||
*/
|
||||
static getNoun(name: string): NounType;
|
||||
/**
|
||||
* Get a verb type by name (with validation)
|
||||
*
|
||||
* @param name The verb type name
|
||||
* @returns The VerbType enum value
|
||||
* @throws Error if invalid verb type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const type = BrainyTypes.getVerb('Contains') // VerbType.Contains
|
||||
* const bad = BrainyTypes.getVerb('Unknown') // throws Error
|
||||
* ```
|
||||
*/
|
||||
static getVerb(name: string): VerbType;
|
||||
/**
|
||||
* Clear the internal cache
|
||||
* Useful when processing many different types of data
|
||||
*/
|
||||
static clearCache(): void;
|
||||
/**
|
||||
* Dispose of resources
|
||||
* Call when completely done using BrainyTypes
|
||||
*/
|
||||
static dispose(): Promise<void>;
|
||||
/**
|
||||
* Get noun types as a plain object (for iteration)
|
||||
* @returns Object with noun type names as keys
|
||||
*/
|
||||
static getNounMap(): Record<string, NounType>;
|
||||
/**
|
||||
* Get verb types as a plain object (for iteration)
|
||||
* @returns Object with verb type names as keys
|
||||
*/
|
||||
static getVerbMap(): Record<string, VerbType>;
|
||||
}
|
||||
export { NounType, VerbType };
|
||||
/**
|
||||
* Helper function to validate and suggest types in one call
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { suggestType } from '@soulcraft/brainy'
|
||||
*
|
||||
* // For nouns
|
||||
* const nounSuggestion = await suggestType('noun', data)
|
||||
*
|
||||
* // For verbs
|
||||
* const verbSuggestion = await suggestType('verb', source, target)
|
||||
* ```
|
||||
*/
|
||||
export declare function suggestType(kind: 'noun', data: any): Promise<TypeSuggestion>;
|
||||
export declare function suggestType(kind: 'verb', source: any, target: any, hint?: string): Promise<TypeSuggestion>;
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
/**
|
||||
* BrainyTypes - Complete type management for Brainy
|
||||
*
|
||||
* Provides type lists, validation, and intelligent suggestions
|
||||
* for nouns and verbs using semantic embeddings.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { BrainyTypes } from '@soulcraft/brainy'
|
||||
*
|
||||
* // Get all available types
|
||||
* const nounTypes = BrainyTypes.nouns // ['Person', 'Organization', ...]
|
||||
* const verbTypes = BrainyTypes.verbs // ['Contains', 'Creates', ...]
|
||||
*
|
||||
* // Validate types
|
||||
* BrainyTypes.isValidNoun('Person') // true
|
||||
* BrainyTypes.isValidVerb('Unknown') // false
|
||||
*
|
||||
* // Get intelligent suggestions
|
||||
* const personData = {
|
||||
* name: 'John Doe',
|
||||
* email: 'john@example.com'
|
||||
* }
|
||||
* const suggestion = await BrainyTypes.suggestNoun(personData)
|
||||
* console.log(suggestion.type) // 'Person'
|
||||
* console.log(suggestion.confidence) // 0.92
|
||||
* ```
|
||||
*/
|
||||
import { NounType, VerbType } from '../types/graphTypes.js';
|
||||
import { BrainyTypes as InternalBrainyTypes } from '../augmentations/typeMatching/brainyTypes.js';
|
||||
/**
|
||||
* BrainyTypes - Complete type management for Brainy
|
||||
*
|
||||
* Static class providing type lists, validation, and intelligent suggestions.
|
||||
* No instantiation needed - all methods are static.
|
||||
*/
|
||||
export class BrainyTypes {
|
||||
/**
|
||||
* Get or create the internal matcher instance
|
||||
*/
|
||||
static async getInternalMatcher() {
|
||||
if (!this.instance) {
|
||||
this.instance = new InternalBrainyTypes();
|
||||
await this.instance.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
/**
|
||||
* Check if a string is a valid noun type
|
||||
*
|
||||
* @param type The type string to check
|
||||
* @returns True if valid noun type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.isValidNoun('Person') // true
|
||||
* BrainyTypes.isValidNoun('Unknown') // false
|
||||
* BrainyTypes.isValidNoun('Contains') // false (it's a verb)
|
||||
* ```
|
||||
*/
|
||||
static isValidNoun(type) {
|
||||
return this.nouns.includes(type);
|
||||
}
|
||||
/**
|
||||
* Check if a string is a valid verb type
|
||||
*
|
||||
* @param type The type string to check
|
||||
* @returns True if valid verb type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.isValidVerb('Contains') // true
|
||||
* BrainyTypes.isValidVerb('Unknown') // false
|
||||
* BrainyTypes.isValidVerb('Person') // false (it's a noun)
|
||||
* ```
|
||||
*/
|
||||
static isValidVerb(type) {
|
||||
return this.verbs.includes(type);
|
||||
}
|
||||
/**
|
||||
* Suggest the most appropriate noun type for an object
|
||||
*
|
||||
* @param data The object or data to analyze
|
||||
* @returns Promise resolving to type suggestion with confidence score
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const data = {
|
||||
* title: 'Quarterly Report',
|
||||
* author: 'Jane Smith',
|
||||
* pages: 42
|
||||
* }
|
||||
* const suggestion = await BrainyTypes.suggestNoun(data)
|
||||
* console.log(suggestion.type) // 'Document'
|
||||
* console.log(suggestion.confidence) // 0.88
|
||||
*
|
||||
* // Check alternatives if confidence is low
|
||||
* if (suggestion.confidence < 0.8) {
|
||||
* console.log('Also consider:', suggestion.alternatives)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
static async suggestNoun(data) {
|
||||
const matcher = await this.getInternalMatcher();
|
||||
const result = await matcher.matchNounType(data);
|
||||
return {
|
||||
type: result.type,
|
||||
confidence: result.confidence,
|
||||
reason: result.reasoning,
|
||||
alternatives: result.alternatives?.map(alt => ({
|
||||
type: alt.type,
|
||||
confidence: alt.confidence
|
||||
}))
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Suggest the most appropriate verb type for a relationship
|
||||
*
|
||||
* @param source The source entity
|
||||
* @param target The target entity
|
||||
* @param hint Optional hint about the relationship
|
||||
* @returns Promise resolving to type suggestion with confidence score
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const source = { type: 'Person', name: 'Alice' }
|
||||
* const target = { type: 'Document', title: 'Research Paper' }
|
||||
*
|
||||
* const suggestion = await BrainyTypes.suggestVerb(source, target, 'authored')
|
||||
* console.log(suggestion.type) // 'CreatedBy'
|
||||
* console.log(suggestion.confidence) // 0.91
|
||||
*
|
||||
* // Without hint
|
||||
* const suggestion2 = await BrainyTypes.suggestVerb(source, target)
|
||||
* console.log(suggestion2.type) // 'RelatedTo' (more generic)
|
||||
* ```
|
||||
*/
|
||||
static async suggestVerb(source, target, hint) {
|
||||
const matcher = await this.getInternalMatcher();
|
||||
const result = await matcher.matchVerbType(source, target, hint);
|
||||
return {
|
||||
type: result.type,
|
||||
confidence: result.confidence,
|
||||
reason: result.reasoning,
|
||||
alternatives: result.alternatives?.map(alt => ({
|
||||
type: alt.type,
|
||||
confidence: alt.confidence
|
||||
}))
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get a noun type by name (with validation)
|
||||
*
|
||||
* @param name The noun type name
|
||||
* @returns The NounType enum value
|
||||
* @throws Error if invalid noun type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const type = BrainyTypes.getNoun('Person') // NounType.Person
|
||||
* const bad = BrainyTypes.getNoun('Unknown') // throws Error
|
||||
* ```
|
||||
*/
|
||||
static getNoun(name) {
|
||||
if (!this.isValidNoun(name)) {
|
||||
throw new Error(`Invalid noun type: '${name}'. Valid types are: ${this.nouns.join(', ')}`);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
/**
|
||||
* Get a verb type by name (with validation)
|
||||
*
|
||||
* @param name The verb type name
|
||||
* @returns The VerbType enum value
|
||||
* @throws Error if invalid verb type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const type = BrainyTypes.getVerb('Contains') // VerbType.Contains
|
||||
* const bad = BrainyTypes.getVerb('Unknown') // throws Error
|
||||
* ```
|
||||
*/
|
||||
static getVerb(name) {
|
||||
if (!this.isValidVerb(name)) {
|
||||
throw new Error(`Invalid verb type: '${name}'. Valid types are: ${this.verbs.join(', ')}`);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
/**
|
||||
* Clear the internal cache
|
||||
* Useful when processing many different types of data
|
||||
*/
|
||||
static clearCache() {
|
||||
this.instance?.clearCache();
|
||||
}
|
||||
/**
|
||||
* Dispose of resources
|
||||
* Call when completely done using BrainyTypes
|
||||
*/
|
||||
static async dispose() {
|
||||
if (this.instance) {
|
||||
await this.instance.dispose();
|
||||
this.instance = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get noun types as a plain object (for iteration)
|
||||
* @returns Object with noun type names as keys
|
||||
*/
|
||||
static getNounMap() {
|
||||
const map = {};
|
||||
for (const noun of this.nouns) {
|
||||
map[noun] = noun;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
/**
|
||||
* Get verb types as a plain object (for iteration)
|
||||
* @returns Object with verb type names as keys
|
||||
*/
|
||||
static getVerbMap() {
|
||||
const map = {};
|
||||
for (const verb of this.verbs) {
|
||||
map[verb] = verb;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
BrainyTypes.instance = null;
|
||||
BrainyTypes.initialized = false;
|
||||
/**
|
||||
* All available noun types
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.nouns.forEach(type => console.log(type))
|
||||
* // 'Person', 'Organization', 'Location', ...
|
||||
* ```
|
||||
*/
|
||||
BrainyTypes.nouns = Object.freeze(Object.values(NounType));
|
||||
/**
|
||||
* All available verb types
|
||||
* @example
|
||||
* ```typescript
|
||||
* BrainyTypes.verbs.forEach(type => console.log(type))
|
||||
* // 'Contains', 'Creates', 'RelatedTo', ...
|
||||
* ```
|
||||
*/
|
||||
BrainyTypes.verbs = Object.freeze(Object.values(VerbType));
|
||||
// Re-export the enums for convenience
|
||||
export { NounType, VerbType };
|
||||
export async function suggestType(kind, ...args) {
|
||||
if (kind === 'noun') {
|
||||
return BrainyTypes.suggestNoun(args[0]);
|
||||
}
|
||||
else {
|
||||
return BrainyTypes.suggestVerb(args[0], args[1], args[2]);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=brainyTypes.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"brainyTypes.js","sourceRoot":"","sources":["../../src/utils/brainyTypes.ts"],"names":[],"mappings":"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"}
|
||||
63
.recovery-workspace/dist-backup-20250910-141917/utils/cacheAutoConfig.d.ts
vendored
Normal file
63
.recovery-workspace/dist-backup-20250910-141917/utils/cacheAutoConfig.d.ts
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Intelligent cache auto-configuration system
|
||||
* Adapts cache settings based on environment, usage patterns, and storage type
|
||||
*/
|
||||
import { SearchCacheConfig } from './searchCache.js';
|
||||
import { BrainyConfig } from '../brainy.js';
|
||||
export interface CacheUsageStats {
|
||||
totalQueries: number;
|
||||
repeatQueries: number;
|
||||
avgQueryTime: number;
|
||||
memoryPressure: number;
|
||||
storageType: 'memory' | 'opfs' | 's3' | 'filesystem';
|
||||
isDistributed: boolean;
|
||||
changeFrequency: number;
|
||||
readWriteRatio: number;
|
||||
}
|
||||
export interface AutoConfigResult {
|
||||
cacheConfig: SearchCacheConfig;
|
||||
realtimeConfig: NonNullable<BrainyConfig['realtimeUpdates']>;
|
||||
reasoning: string[];
|
||||
}
|
||||
export declare class CacheAutoConfigurator {
|
||||
private stats;
|
||||
private configHistory;
|
||||
private lastOptimization;
|
||||
/**
|
||||
* Auto-detect optimal cache configuration based on current conditions
|
||||
*/
|
||||
autoDetectOptimalConfig(storageConfig?: BrainyConfig['storage'], currentStats?: Partial<CacheUsageStats>): AutoConfigResult;
|
||||
/**
|
||||
* Dynamically adjust configuration based on runtime performance
|
||||
*/
|
||||
adaptConfiguration(currentConfig: SearchCacheConfig, performanceMetrics: {
|
||||
hitRate: number;
|
||||
avgResponseTime: number;
|
||||
memoryUsage: number;
|
||||
externalChangesDetected: number;
|
||||
timeSinceLastChange: number;
|
||||
}): AutoConfigResult | null;
|
||||
/**
|
||||
* Get recommended configuration for specific use case
|
||||
*/
|
||||
getRecommendedConfig(useCase: 'high-consistency' | 'balanced' | 'performance-first'): AutoConfigResult;
|
||||
/**
|
||||
* Learn from usage patterns and improve recommendations
|
||||
*/
|
||||
learnFromUsage(usageData: {
|
||||
queryPatterns: string[];
|
||||
responseTime: number;
|
||||
cacheHits: number;
|
||||
totalQueries: number;
|
||||
dataChanges: number;
|
||||
timeWindow: number;
|
||||
}): void;
|
||||
private detectEnvironment;
|
||||
private generateOptimalConfig;
|
||||
private calculateRealtimeConfig;
|
||||
private detectMemoryConstraints;
|
||||
/**
|
||||
* Get human-readable explanation of current configuration
|
||||
*/
|
||||
getConfigExplanation(config: AutoConfigResult): string;
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
/**
|
||||
* Intelligent cache auto-configuration system
|
||||
* Adapts cache settings based on environment, usage patterns, and storage type
|
||||
*/
|
||||
export class CacheAutoConfigurator {
|
||||
constructor() {
|
||||
this.stats = {
|
||||
totalQueries: 0,
|
||||
repeatQueries: 0,
|
||||
avgQueryTime: 50,
|
||||
memoryPressure: 0,
|
||||
storageType: 'memory',
|
||||
isDistributed: false,
|
||||
changeFrequency: 0,
|
||||
readWriteRatio: 10,
|
||||
};
|
||||
this.configHistory = [];
|
||||
this.lastOptimization = 0;
|
||||
}
|
||||
/**
|
||||
* Auto-detect optimal cache configuration based on current conditions
|
||||
*/
|
||||
autoDetectOptimalConfig(storageConfig, currentStats) {
|
||||
// Update stats with current information
|
||||
if (currentStats) {
|
||||
this.stats = { ...this.stats, ...currentStats };
|
||||
}
|
||||
// Detect environment characteristics
|
||||
this.detectEnvironment(storageConfig);
|
||||
// Generate optimal configuration
|
||||
const result = this.generateOptimalConfig();
|
||||
// Store for learning
|
||||
this.configHistory.push(result);
|
||||
this.lastOptimization = Date.now();
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Dynamically adjust configuration based on runtime performance
|
||||
*/
|
||||
adaptConfiguration(currentConfig, performanceMetrics) {
|
||||
const reasoning = [];
|
||||
let needsUpdate = false;
|
||||
// Check if we should update (don't over-optimize)
|
||||
if (Date.now() - this.lastOptimization < 60000) {
|
||||
return null; // Wait at least 1 minute between optimizations
|
||||
}
|
||||
// Analyze performance patterns
|
||||
const adaptations = {};
|
||||
// Low hit rate → adjust cache size or TTL
|
||||
if (performanceMetrics.hitRate < 0.3) {
|
||||
if (performanceMetrics.externalChangesDetected > 5) {
|
||||
// Too many external changes → shorter TTL
|
||||
adaptations.maxAge = Math.max(60000, currentConfig.maxAge * 0.7);
|
||||
reasoning.push('Reduced cache TTL due to frequent external changes');
|
||||
needsUpdate = true;
|
||||
}
|
||||
else {
|
||||
// Expand cache size for better hit rate
|
||||
adaptations.maxSize = Math.min(500, (currentConfig.maxSize || 100) * 1.5);
|
||||
reasoning.push('Increased cache size due to low hit rate');
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
// High hit rate but slow responses → might need cache warming
|
||||
if (performanceMetrics.hitRate > 0.8 && performanceMetrics.avgResponseTime > 100) {
|
||||
reasoning.push('High hit rate but slow responses - consider cache warming');
|
||||
}
|
||||
// Memory pressure → reduce cache size
|
||||
if (performanceMetrics.memoryUsage > 100 * 1024 * 1024) { // 100MB
|
||||
adaptations.maxSize = Math.max(20, (currentConfig.maxSize || 100) * 0.7);
|
||||
reasoning.push('Reduced cache size due to memory pressure');
|
||||
needsUpdate = true;
|
||||
}
|
||||
// Recent external changes → adaptive TTL
|
||||
if (performanceMetrics.timeSinceLastChange < 30000) { // 30 seconds
|
||||
adaptations.maxAge = Math.max(30000, currentConfig.maxAge * 0.8);
|
||||
reasoning.push('Shortened TTL due to recent external changes');
|
||||
needsUpdate = true;
|
||||
}
|
||||
if (!needsUpdate) {
|
||||
return null;
|
||||
}
|
||||
const newCacheConfig = {
|
||||
...currentConfig,
|
||||
...adaptations
|
||||
};
|
||||
const newRealtimeConfig = this.calculateRealtimeConfig();
|
||||
return {
|
||||
cacheConfig: newCacheConfig,
|
||||
realtimeConfig: newRealtimeConfig,
|
||||
reasoning
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get recommended configuration for specific use case
|
||||
*/
|
||||
getRecommendedConfig(useCase) {
|
||||
const configs = {
|
||||
'high-consistency': {
|
||||
cache: { maxAge: 120000, maxSize: 50 },
|
||||
realtime: { interval: 15000, enabled: true },
|
||||
reasoning: ['Optimized for data consistency and real-time updates']
|
||||
},
|
||||
'balanced': {
|
||||
cache: { maxAge: 300000, maxSize: 100 },
|
||||
realtime: { interval: 30000, enabled: true },
|
||||
reasoning: ['Balanced performance and consistency']
|
||||
},
|
||||
'performance-first': {
|
||||
cache: { maxAge: 600000, maxSize: 200 },
|
||||
realtime: { interval: 60000, enabled: true },
|
||||
reasoning: ['Optimized for maximum cache performance']
|
||||
}
|
||||
};
|
||||
const config = configs[useCase];
|
||||
return {
|
||||
cacheConfig: {
|
||||
enabled: true,
|
||||
...config.cache
|
||||
},
|
||||
realtimeConfig: {
|
||||
updateIndex: true,
|
||||
updateStatistics: true,
|
||||
...config.realtime
|
||||
},
|
||||
reasoning: config.reasoning
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Learn from usage patterns and improve recommendations
|
||||
*/
|
||||
learnFromUsage(usageData) {
|
||||
// Update internal stats for better future recommendations
|
||||
this.stats.totalQueries += usageData.totalQueries;
|
||||
this.stats.repeatQueries += usageData.cacheHits;
|
||||
this.stats.avgQueryTime = (this.stats.avgQueryTime + usageData.responseTime) / 2;
|
||||
this.stats.changeFrequency = usageData.dataChanges / (usageData.timeWindow / 60000);
|
||||
// Calculate read/write ratio
|
||||
const writes = usageData.dataChanges;
|
||||
const reads = usageData.totalQueries;
|
||||
this.stats.readWriteRatio = reads > 0 ? reads / Math.max(writes, 1) : 10;
|
||||
}
|
||||
detectEnvironment(storageConfig) {
|
||||
// Detect storage type
|
||||
if (storageConfig?.s3Storage || storageConfig?.customS3Storage) {
|
||||
this.stats.storageType = 's3';
|
||||
this.stats.isDistributed = true;
|
||||
}
|
||||
else if (storageConfig?.forceFileSystemStorage) {
|
||||
this.stats.storageType = 'filesystem';
|
||||
}
|
||||
else if (storageConfig?.forceMemoryStorage) {
|
||||
this.stats.storageType = 'memory';
|
||||
}
|
||||
else {
|
||||
// Auto-detect browser vs Node.js
|
||||
this.stats.storageType = typeof window !== 'undefined' ? 'opfs' : 'filesystem';
|
||||
}
|
||||
// Detect distributed mode indicators
|
||||
this.stats.isDistributed = this.stats.isDistributed ||
|
||||
Boolean(storageConfig?.s3Storage || storageConfig?.customS3Storage);
|
||||
}
|
||||
generateOptimalConfig() {
|
||||
const reasoning = [];
|
||||
// Base configuration
|
||||
let cacheConfig = {
|
||||
enabled: true,
|
||||
maxSize: 100,
|
||||
maxAge: 300000, // 5 minutes
|
||||
hitCountWeight: 0.3
|
||||
};
|
||||
let realtimeConfig = {
|
||||
enabled: false,
|
||||
interval: 60000,
|
||||
updateIndex: true,
|
||||
updateStatistics: true
|
||||
};
|
||||
// Adjust for storage type
|
||||
if (this.stats.storageType === 's3' || this.stats.isDistributed) {
|
||||
cacheConfig.maxAge = 180000; // 3 minutes for distributed
|
||||
realtimeConfig.enabled = true;
|
||||
realtimeConfig.interval = 30000; // 30 seconds
|
||||
reasoning.push('Distributed storage detected - enabled real-time updates');
|
||||
reasoning.push('Reduced cache TTL for distributed consistency');
|
||||
}
|
||||
// Adjust for read/write patterns
|
||||
if (this.stats.readWriteRatio > 20) {
|
||||
// Read-heavy workload
|
||||
cacheConfig.maxSize = Math.min(300, (cacheConfig.maxSize || 100) * 2);
|
||||
cacheConfig.maxAge = Math.min(900000, (cacheConfig.maxAge || 300000) * 1.5); // Up to 15 minutes
|
||||
reasoning.push('Read-heavy workload detected - increased cache size and TTL');
|
||||
}
|
||||
else if (this.stats.readWriteRatio < 5) {
|
||||
// Write-heavy workload
|
||||
cacheConfig.maxSize = Math.max(50, (cacheConfig.maxSize || 100) * 0.7);
|
||||
cacheConfig.maxAge = Math.max(60000, (cacheConfig.maxAge || 300000) * 0.6);
|
||||
reasoning.push('Write-heavy workload detected - reduced cache size and TTL');
|
||||
}
|
||||
// Adjust for change frequency
|
||||
if (this.stats.changeFrequency > 10) { // More than 10 changes per minute
|
||||
realtimeConfig.interval = Math.max(10000, realtimeConfig.interval * 0.5);
|
||||
cacheConfig.maxAge = Math.max(30000, (cacheConfig.maxAge || 300000) * 0.5);
|
||||
reasoning.push('High change frequency detected - increased update frequency');
|
||||
}
|
||||
// Memory constraints
|
||||
if (this.detectMemoryConstraints()) {
|
||||
cacheConfig.maxSize = Math.max(20, (cacheConfig.maxSize || 100) * 0.6);
|
||||
reasoning.push('Memory constraints detected - reduced cache size');
|
||||
}
|
||||
// Performance optimization
|
||||
if (this.stats.avgQueryTime > 200) {
|
||||
cacheConfig.maxSize = Math.min(500, (cacheConfig.maxSize || 100) * 1.5);
|
||||
reasoning.push('Slow queries detected - increased cache size');
|
||||
}
|
||||
return {
|
||||
cacheConfig,
|
||||
realtimeConfig,
|
||||
reasoning
|
||||
};
|
||||
}
|
||||
calculateRealtimeConfig() {
|
||||
return {
|
||||
enabled: this.stats.isDistributed || this.stats.changeFrequency > 1,
|
||||
interval: this.stats.isDistributed ? 30000 : 60000,
|
||||
updateIndex: true,
|
||||
updateStatistics: true
|
||||
};
|
||||
}
|
||||
detectMemoryConstraints() {
|
||||
// Simple heuristic for memory constraints
|
||||
try {
|
||||
if (typeof performance !== 'undefined' && 'memory' in performance) {
|
||||
const memInfo = performance.memory;
|
||||
return memInfo.usedJSHeapSize > memInfo.jsHeapSizeLimit * 0.8;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
// Default assumption for constrained environments
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Get human-readable explanation of current configuration
|
||||
*/
|
||||
getConfigExplanation(config) {
|
||||
const lines = [
|
||||
'🤖 Brainy Auto-Configuration:',
|
||||
'',
|
||||
`📊 Cache: ${config.cacheConfig.maxSize} queries, ${config.cacheConfig.maxAge / 1000}s TTL`,
|
||||
`🔄 Updates: ${config.realtimeConfig.enabled ? `Every ${(config.realtimeConfig.interval || 30000) / 1000}s` : 'Disabled'}`,
|
||||
'',
|
||||
'🎯 Optimizations applied:'
|
||||
];
|
||||
config.reasoning.forEach(reason => {
|
||||
lines.push(` • ${reason}`);
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=cacheAutoConfig.js.map
|
||||
File diff suppressed because one or more lines are too long
25
.recovery-workspace/dist-backup-20250910-141917/utils/crypto.d.ts
vendored
Normal file
25
.recovery-workspace/dist-backup-20250910-141917/utils/crypto.d.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Cross-platform crypto utilities
|
||||
* Provides hashing functions that work in both Node.js and browser environments
|
||||
*/
|
||||
/**
|
||||
* Simple string hash function that works in all environments
|
||||
* Uses djb2 algorithm - fast and good distribution
|
||||
* @param str - String to hash
|
||||
* @returns Positive integer hash
|
||||
*/
|
||||
export declare function hashString(str: string): number;
|
||||
/**
|
||||
* Alternative: FNV-1a hash algorithm
|
||||
* Good distribution and fast
|
||||
* @param str - String to hash
|
||||
* @returns Positive integer hash
|
||||
*/
|
||||
export declare function fnv1aHash(str: string): number;
|
||||
/**
|
||||
* Generate a deterministic hash for partitioning
|
||||
* Uses the most appropriate algorithm for the environment
|
||||
* @param input - Input string to hash
|
||||
* @returns Positive integer hash suitable for modulo operations
|
||||
*/
|
||||
export declare function getPartitionHash(input: string): number;
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Cross-platform crypto utilities
|
||||
* Provides hashing functions that work in both Node.js and browser environments
|
||||
*/
|
||||
/**
|
||||
* Simple string hash function that works in all environments
|
||||
* Uses djb2 algorithm - fast and good distribution
|
||||
* @param str - String to hash
|
||||
* @returns Positive integer hash
|
||||
*/
|
||||
export function hashString(str) {
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) + hash) + char; // hash * 33 + char
|
||||
}
|
||||
// Ensure positive number
|
||||
return Math.abs(hash);
|
||||
}
|
||||
/**
|
||||
* Alternative: FNV-1a hash algorithm
|
||||
* Good distribution and fast
|
||||
* @param str - String to hash
|
||||
* @returns Positive integer hash
|
||||
*/
|
||||
export function fnv1aHash(str) {
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash ^= str.charCodeAt(i);
|
||||
hash = (hash * 16777619) >>> 0;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
/**
|
||||
* Generate a deterministic hash for partitioning
|
||||
* Uses the most appropriate algorithm for the environment
|
||||
* @param input - Input string to hash
|
||||
* @returns Positive integer hash suitable for modulo operations
|
||||
*/
|
||||
export function getPartitionHash(input) {
|
||||
// Use djb2 by default as it's fast and has good distribution
|
||||
// This ensures consistent partitioning across all environments
|
||||
return hashString(input);
|
||||
}
|
||||
//# sourceMappingURL=crypto.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,IAAI,GAAG,IAAI,CAAA;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA,CAAC,mBAAmB;IACxD,CAAC;IACD,yBAAyB;IACzB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,IAAI,GAAG,UAAU,CAAA;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QACzB,IAAI,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,6DAA6D;IAC7D,+DAA+D;IAC/D,OAAO,UAAU,CAAC,KAAK,CAAC,CAAA;AAC1B,CAAC"}
|
||||
59
.recovery-workspace/dist-backup-20250910-141917/utils/deletedItemsIndex.d.ts
vendored
Normal file
59
.recovery-workspace/dist-backup-20250910-141917/utils/deletedItemsIndex.d.ts
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Dedicated index for tracking soft-deleted items
|
||||
* This is MUCH more efficient than checking every item in the database
|
||||
*
|
||||
* Performance characteristics:
|
||||
* - Add deleted item: O(1)
|
||||
* - Remove deleted item: O(1)
|
||||
* - Check if deleted: O(1)
|
||||
* - Get all deleted: O(d) where d = number of deleted items << total items
|
||||
*/
|
||||
export declare class DeletedItemsIndex {
|
||||
private deletedIds;
|
||||
private deletedCount;
|
||||
/**
|
||||
* Mark an item as deleted
|
||||
*/
|
||||
markDeleted(id: string): void;
|
||||
/**
|
||||
* Mark an item as not deleted (restored)
|
||||
*/
|
||||
markRestored(id: string): void;
|
||||
/**
|
||||
* Check if an item is deleted - O(1)
|
||||
*/
|
||||
isDeleted(id: string): boolean;
|
||||
/**
|
||||
* Get all deleted item IDs - O(d)
|
||||
*/
|
||||
getAllDeleted(): string[];
|
||||
/**
|
||||
* Filter out deleted items from results - O(k) where k = result count
|
||||
*/
|
||||
filterDeleted<T extends {
|
||||
id?: string;
|
||||
}>(items: T[]): T[];
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
getStats(): {
|
||||
deletedCount: number;
|
||||
memoryUsage: number;
|
||||
};
|
||||
/**
|
||||
* Clear all deleted items (for testing)
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Serialize for persistence
|
||||
*/
|
||||
serialize(): string;
|
||||
/**
|
||||
* Deserialize from persistence
|
||||
*/
|
||||
deserialize(data: string): void;
|
||||
}
|
||||
/**
|
||||
* Global singleton for deleted items tracking
|
||||
*/
|
||||
export declare const deletedItemsIndex: DeletedItemsIndex;
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Dedicated index for tracking soft-deleted items
|
||||
* This is MUCH more efficient than checking every item in the database
|
||||
*
|
||||
* Performance characteristics:
|
||||
* - Add deleted item: O(1)
|
||||
* - Remove deleted item: O(1)
|
||||
* - Check if deleted: O(1)
|
||||
* - Get all deleted: O(d) where d = number of deleted items << total items
|
||||
*/
|
||||
export class DeletedItemsIndex {
|
||||
constructor() {
|
||||
this.deletedIds = new Set();
|
||||
this.deletedCount = 0;
|
||||
}
|
||||
/**
|
||||
* Mark an item as deleted
|
||||
*/
|
||||
markDeleted(id) {
|
||||
if (!this.deletedIds.has(id)) {
|
||||
this.deletedIds.add(id);
|
||||
this.deletedCount++;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Mark an item as not deleted (restored)
|
||||
*/
|
||||
markRestored(id) {
|
||||
if (this.deletedIds.delete(id)) {
|
||||
this.deletedCount--;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if an item is deleted - O(1)
|
||||
*/
|
||||
isDeleted(id) {
|
||||
return this.deletedIds.has(id);
|
||||
}
|
||||
/**
|
||||
* Get all deleted item IDs - O(d)
|
||||
*/
|
||||
getAllDeleted() {
|
||||
return Array.from(this.deletedIds);
|
||||
}
|
||||
/**
|
||||
* Filter out deleted items from results - O(k) where k = result count
|
||||
*/
|
||||
filterDeleted(items) {
|
||||
if (this.deletedCount === 0) {
|
||||
// Fast path - no deleted items
|
||||
return items;
|
||||
}
|
||||
return items.filter(item => {
|
||||
const id = item.id;
|
||||
return id ? !this.deletedIds.has(id) : true;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
deletedCount: this.deletedCount,
|
||||
memoryUsage: this.deletedCount * 100 // Rough estimate: 100 bytes per ID
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Clear all deleted items (for testing)
|
||||
*/
|
||||
clear() {
|
||||
this.deletedIds.clear();
|
||||
this.deletedCount = 0;
|
||||
}
|
||||
/**
|
||||
* Serialize for persistence
|
||||
*/
|
||||
serialize() {
|
||||
return JSON.stringify(Array.from(this.deletedIds));
|
||||
}
|
||||
/**
|
||||
* Deserialize from persistence
|
||||
*/
|
||||
deserialize(data) {
|
||||
try {
|
||||
const ids = JSON.parse(data);
|
||||
this.deletedIds = new Set(ids);
|
||||
this.deletedCount = this.deletedIds.size;
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('Failed to deserialize deleted items index');
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Global singleton for deleted items tracking
|
||||
*/
|
||||
export const deletedItemsIndex = new DeletedItemsIndex();
|
||||
//# sourceMappingURL=deletedItemsIndex.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"deletedItemsIndex.js","sourceRoot":"","sources":["../../src/utils/deletedItemsIndex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,OAAO,iBAAiB;IAA9B;QACU,eAAU,GAAgB,IAAI,GAAG,EAAE,CAAA;QACnC,iBAAY,GAAW,CAAC,CAAA;IAuFlC,CAAC;IArFC;;OAEG;IACH,WAAW,CAAC,EAAU;QACpB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACvB,IAAI,CAAC,YAAY,EAAE,CAAA;QACrB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,EAAU;QACrB,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,YAAY,EAAE,CAAA;QACrB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,EAAU;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAChC,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACpC,CAAC;IAED;;OAEG;IACH,aAAa,CAA4B,KAAU;QACjD,IAAI,IAAI,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;YAC5B,+BAA+B;YAC/B,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;YACzB,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAA;YAClB,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC7C,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,WAAW,EAAE,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,mCAAmC;SACzE,CAAA;IACH,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAA;QACvB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAA;IACvB,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;IACpD,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,IAAY;QACtB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAA;QAC1C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,EAAE,CAAA"}
|
||||
42
.recovery-workspace/dist-backup-20250910-141917/utils/distance.d.ts
vendored
Normal file
42
.recovery-workspace/dist-backup-20250910-141917/utils/distance.d.ts
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Distance functions for vector similarity calculations
|
||||
* Optimized pure JavaScript implementations using enhanced array methods
|
||||
* Faster than GPU for small vectors (384 dims) due to no transfer overhead
|
||||
*/
|
||||
import { DistanceFunction, Vector } from '../coreTypes.js';
|
||||
/**
|
||||
* Calculates the Euclidean distance between two vectors
|
||||
* Lower values indicate higher similarity
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export declare const euclideanDistance: DistanceFunction;
|
||||
/**
|
||||
* Calculates the cosine distance between two vectors
|
||||
* Lower values indicate higher similarity
|
||||
* Range: 0 (identical) to 2 (opposite)
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export declare const cosineDistance: DistanceFunction;
|
||||
/**
|
||||
* Calculates the Manhattan (L1) distance between two vectors
|
||||
* Lower values indicate higher similarity
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export declare const manhattanDistance: DistanceFunction;
|
||||
/**
|
||||
* Calculates the dot product similarity between two vectors
|
||||
* Higher values indicate higher similarity
|
||||
* Converted to a distance metric (lower is better)
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export declare const dotProductDistance: DistanceFunction;
|
||||
/**
|
||||
* Batch distance calculation using optimized JavaScript
|
||||
* More efficient than GPU for small vectors due to no memory transfer overhead
|
||||
*
|
||||
* @param queryVector The query vector to compare against all vectors
|
||||
* @param vectors Array of vectors to compare against
|
||||
* @param distanceFunction The distance function to use
|
||||
* @returns Promise resolving to array of distances
|
||||
*/
|
||||
export declare function calculateDistancesBatch(queryVector: Vector, vectors: Vector[], distanceFunction?: DistanceFunction): Promise<number[]>;
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* Distance functions for vector similarity calculations
|
||||
* Optimized pure JavaScript implementations using enhanced array methods
|
||||
* Faster than GPU for small vectors (384 dims) due to no transfer overhead
|
||||
*/
|
||||
/**
|
||||
* Calculates the Euclidean distance between two vectors
|
||||
* Lower values indicate higher similarity
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export const euclideanDistance = (a, b) => {
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Vectors must have the same dimensions');
|
||||
}
|
||||
// Use array.reduce for better performance in Node.js 23.11+
|
||||
const sum = a.reduce((acc, val, i) => {
|
||||
const diff = val - b[i];
|
||||
return acc + diff * diff;
|
||||
}, 0);
|
||||
return Math.sqrt(sum);
|
||||
};
|
||||
/**
|
||||
* Calculates the cosine distance between two vectors
|
||||
* Lower values indicate higher similarity
|
||||
* Range: 0 (identical) to 2 (opposite)
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export const cosineDistance = (a, b) => {
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Vectors must have the same dimensions');
|
||||
}
|
||||
// Use array.reduce to calculate all values in a single pass
|
||||
const { dotProduct, normA, normB } = a.reduce((acc, val, i) => {
|
||||
return {
|
||||
dotProduct: acc.dotProduct + val * b[i],
|
||||
normA: acc.normA + val * val,
|
||||
normB: acc.normB + b[i] * b[i]
|
||||
};
|
||||
}, { dotProduct: 0, normA: 0, normB: 0 });
|
||||
if (normA === 0 || normB === 0) {
|
||||
return 2; // Maximum distance for zero vectors
|
||||
}
|
||||
const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
||||
// Convert cosine similarity (-1 to 1) to distance (0 to 2)
|
||||
return 1 - similarity;
|
||||
};
|
||||
/**
|
||||
* Calculates the Manhattan (L1) distance between two vectors
|
||||
* Lower values indicate higher similarity
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export const manhattanDistance = (a, b) => {
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Vectors must have the same dimensions');
|
||||
}
|
||||
// Use array.reduce for better performance in Node.js 23.11+
|
||||
return a.reduce((sum, val, i) => sum + Math.abs(val - b[i]), 0);
|
||||
};
|
||||
/**
|
||||
* Calculates the dot product similarity between two vectors
|
||||
* Higher values indicate higher similarity
|
||||
* Converted to a distance metric (lower is better)
|
||||
* Optimized using array methods for Node.js 23.11+
|
||||
*/
|
||||
export const dotProductDistance = (a, b) => {
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Vectors must have the same dimensions');
|
||||
}
|
||||
// Use array.reduce for better performance in Node.js 23.11+
|
||||
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
|
||||
// Convert to a distance metric (lower is better)
|
||||
return -dotProduct;
|
||||
};
|
||||
/**
|
||||
* Batch distance calculation using optimized JavaScript
|
||||
* More efficient than GPU for small vectors due to no memory transfer overhead
|
||||
*
|
||||
* @param queryVector The query vector to compare against all vectors
|
||||
* @param vectors Array of vectors to compare against
|
||||
* @param distanceFunction The distance function to use
|
||||
* @returns Promise resolving to array of distances
|
||||
*/
|
||||
export async function calculateDistancesBatch(queryVector, vectors, distanceFunction = euclideanDistance) {
|
||||
// For small batches, use the standard distance function
|
||||
if (vectors.length < 10) {
|
||||
return vectors.map((vector) => distanceFunction(queryVector, vector));
|
||||
}
|
||||
try {
|
||||
// Function for optimized batch distance calculation
|
||||
const distanceCalculator = (args) => {
|
||||
const { queryVector, vectors, distanceFnString } = args;
|
||||
// Optimized JavaScript implementations for different distance functions
|
||||
let distances;
|
||||
if (distanceFnString.includes('euclideanDistance')) {
|
||||
// Euclidean distance: sqrt(sum((a - b)^2))
|
||||
distances = vectors.map((vector) => {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < queryVector.length; i++) {
|
||||
const diff = queryVector[i] - vector[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
return Math.sqrt(sum);
|
||||
});
|
||||
}
|
||||
else if (distanceFnString.includes('cosineDistance')) {
|
||||
// Cosine distance: 1 - (a·b / (||a|| * ||b||))
|
||||
distances = vectors.map((vector) => {
|
||||
let dotProduct = 0;
|
||||
let queryNorm = 0;
|
||||
let vectorNorm = 0;
|
||||
for (let i = 0; i < queryVector.length; i++) {
|
||||
dotProduct += queryVector[i] * vector[i];
|
||||
queryNorm += queryVector[i] * queryVector[i];
|
||||
vectorNorm += vector[i] * vector[i];
|
||||
}
|
||||
queryNorm = Math.sqrt(queryNorm);
|
||||
vectorNorm = Math.sqrt(vectorNorm);
|
||||
if (queryNorm === 0 || vectorNorm === 0) {
|
||||
return 1; // Maximum distance for zero vectors
|
||||
}
|
||||
const cosineSimilarity = dotProduct / (queryNorm * vectorNorm);
|
||||
return 1 - cosineSimilarity;
|
||||
});
|
||||
}
|
||||
else if (distanceFnString.includes('manhattanDistance')) {
|
||||
// Manhattan distance: sum(|a - b|)
|
||||
distances = vectors.map((vector) => {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < queryVector.length; i++) {
|
||||
sum += Math.abs(queryVector[i] - vector[i]);
|
||||
}
|
||||
return sum;
|
||||
});
|
||||
}
|
||||
else if (distanceFnString.includes('dotProductDistance')) {
|
||||
// Dot product distance: -sum(a * b)
|
||||
distances = vectors.map((vector) => {
|
||||
let dotProduct = 0;
|
||||
for (let i = 0; i < queryVector.length; i++) {
|
||||
dotProduct += queryVector[i] * vector[i];
|
||||
}
|
||||
return -dotProduct;
|
||||
});
|
||||
}
|
||||
else {
|
||||
// For unknown distance functions, use the provided function
|
||||
const distanceFunction = new Function('return ' + distanceFnString)();
|
||||
distances = vectors.map((vector) => distanceFunction(queryVector, vector));
|
||||
}
|
||||
return { distances };
|
||||
};
|
||||
// Use the optimized distance calculator
|
||||
const result = distanceCalculator({
|
||||
queryVector,
|
||||
vectors,
|
||||
distanceFnString: distanceFunction.toString()
|
||||
});
|
||||
return result.distances;
|
||||
}
|
||||
catch (error) {
|
||||
// If anything fails, fall back to the standard distance function
|
||||
console.error('Batch distance calculation failed:', error);
|
||||
return vectors.map((vector) => distanceFunction(queryVector, vector));
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=distance.js.map
|
||||
File diff suppressed because one or more lines are too long
108
.recovery-workspace/dist-backup-20250910-141917/utils/embedding.d.ts
vendored
Normal file
108
.recovery-workspace/dist-backup-20250910-141917/utils/embedding.d.ts
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Embedding functions for converting data to vectors using Transformers.js
|
||||
* Complete rewrite to eliminate TensorFlow.js and use ONNX-based models
|
||||
*/
|
||||
import { EmbeddingFunction, EmbeddingModel, Vector } from '../coreTypes.js';
|
||||
/**
|
||||
* Detect the best available GPU device for the current environment
|
||||
*/
|
||||
export declare function detectBestDevice(): Promise<'cpu' | 'webgpu' | 'cuda'>;
|
||||
/**
|
||||
* Resolve device string to actual device configuration
|
||||
*/
|
||||
export declare function resolveDevice(device?: string): Promise<string>;
|
||||
/**
|
||||
* Transformers.js Sentence Encoder embedding model
|
||||
* Uses ONNX Runtime for fast, offline embeddings with smaller models
|
||||
* Default model: all-MiniLM-L6-v2 (384 dimensions, ~90MB)
|
||||
*/
|
||||
export interface TransformerEmbeddingOptions {
|
||||
/** Model name/path to use - defaults to all-MiniLM-L6-v2 */
|
||||
model?: string;
|
||||
/** Whether to enable verbose logging */
|
||||
verbose?: boolean;
|
||||
/** Custom cache directory for models */
|
||||
cacheDir?: string;
|
||||
/** Force local files only (no downloads) */
|
||||
localFilesOnly?: boolean;
|
||||
/** Model precision: 'q8' = 75% smaller quantized model, 'fp32' = full precision (default) */
|
||||
precision?: 'fp32' | 'q8';
|
||||
/** Device to run inference on - 'auto' detects best available */
|
||||
device?: 'auto' | 'cpu' | 'webgpu' | 'cuda' | 'gpu';
|
||||
}
|
||||
export declare class TransformerEmbedding implements EmbeddingModel {
|
||||
private extractor;
|
||||
private initialized;
|
||||
private verbose;
|
||||
private options;
|
||||
/**
|
||||
* Create a new TransformerEmbedding instance
|
||||
*/
|
||||
constructor(options?: TransformerEmbeddingOptions);
|
||||
/**
|
||||
* Get the default cache directory for models
|
||||
*/
|
||||
private getDefaultCacheDir;
|
||||
/**
|
||||
* Check if we're running in a test environment
|
||||
*/
|
||||
private isTestEnvironment;
|
||||
/**
|
||||
* Log message only if verbose mode is enabled
|
||||
*/
|
||||
private logger;
|
||||
/**
|
||||
* Generate mock embeddings for unit tests
|
||||
*/
|
||||
private getMockEmbedding;
|
||||
/**
|
||||
* Initialize the embedding model
|
||||
*/
|
||||
init(): Promise<void>;
|
||||
/**
|
||||
* Generate embeddings for text data
|
||||
*/
|
||||
embed(data: string | string[]): Promise<Vector>;
|
||||
/**
|
||||
* Dispose of the model and free resources
|
||||
*/
|
||||
dispose(): Promise<void>;
|
||||
/**
|
||||
* Get the dimension of embeddings produced by this model
|
||||
*/
|
||||
getDimension(): number;
|
||||
/**
|
||||
* Check if the model is initialized
|
||||
*/
|
||||
isInitialized(): boolean;
|
||||
}
|
||||
export declare const UniversalSentenceEncoder: typeof TransformerEmbedding;
|
||||
/**
|
||||
* Create a new embedding model instance
|
||||
*/
|
||||
export declare function createEmbeddingModel(options?: TransformerEmbeddingOptions): EmbeddingModel;
|
||||
/**
|
||||
* Default embedding function using the unified EmbeddingManager
|
||||
* Simple, clean, reliable - no more layers of indirection
|
||||
*/
|
||||
export declare const defaultEmbeddingFunction: EmbeddingFunction;
|
||||
/**
|
||||
* Create an embedding function with custom options
|
||||
* NOTE: Options are validated but the singleton EmbeddingManager is always used
|
||||
*/
|
||||
export declare function createEmbeddingFunction(options?: TransformerEmbeddingOptions): EmbeddingFunction;
|
||||
/**
|
||||
* Batch embedding function for processing multiple texts efficiently
|
||||
*/
|
||||
export declare function batchEmbed(texts: string[], options?: TransformerEmbeddingOptions): Promise<Vector[]>;
|
||||
/**
|
||||
* Embedding functions for specific model types
|
||||
*/
|
||||
export declare const embeddingFunctions: {
|
||||
/** Default lightweight model (all-MiniLM-L6-v2, 384 dimensions) */
|
||||
default: EmbeddingFunction;
|
||||
/** Create custom embedding function */
|
||||
create: typeof createEmbeddingFunction;
|
||||
/** Batch processing */
|
||||
batch: typeof batchEmbed;
|
||||
};
|
||||
|
|
@ -0,0 +1,501 @@
|
|||
/**
|
||||
* Embedding functions for converting data to vectors using Transformers.js
|
||||
* Complete rewrite to eliminate TensorFlow.js and use ONNX-based models
|
||||
*/
|
||||
import { isBrowser } from './environment.js';
|
||||
import { join } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
// @ts-ignore - Transformers.js is now the primary embedding library
|
||||
import { pipeline, env } from '@huggingface/transformers';
|
||||
// CRITICAL: Disable ONNX memory arena to prevent 4-8GB allocation
|
||||
// This is needed for BOTH production and testing - reduces memory by 50-75%
|
||||
if (typeof process !== 'undefined' && process.env) {
|
||||
process.env.ORT_DISABLE_MEMORY_ARENA = '1';
|
||||
process.env.ORT_DISABLE_MEMORY_PATTERN = '1';
|
||||
// Force single-threaded operation for maximum stability (Node.js 24 compatibility)
|
||||
process.env.ORT_INTRA_OP_NUM_THREADS = '1'; // Single thread for operators
|
||||
process.env.ORT_INTER_OP_NUM_THREADS = '1'; // Single thread for sessions
|
||||
process.env.ORT_NUM_THREADS = '1'; // Additional safety override
|
||||
}
|
||||
/**
|
||||
* Detect the best available GPU device for the current environment
|
||||
*/
|
||||
export async function detectBestDevice() {
|
||||
// Browser environment - check for WebGPU support
|
||||
if (isBrowser()) {
|
||||
if (typeof navigator !== 'undefined' && 'gpu' in navigator) {
|
||||
try {
|
||||
const adapter = await navigator.gpu?.requestAdapter();
|
||||
if (adapter) {
|
||||
return 'webgpu';
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// WebGPU not available or failed to initialize
|
||||
}
|
||||
}
|
||||
return 'cpu';
|
||||
}
|
||||
// Node.js environment - check for CUDA support
|
||||
try {
|
||||
// Check if ONNX Runtime GPU packages are available
|
||||
// This is a simple heuristic - in production you might want more sophisticated detection
|
||||
const hasGpu = process.env.CUDA_VISIBLE_DEVICES !== undefined ||
|
||||
process.env.ONNXRUNTIME_GPU_ENABLED === 'true';
|
||||
return hasGpu ? 'cuda' : 'cpu';
|
||||
}
|
||||
catch (error) {
|
||||
return 'cpu';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resolve device string to actual device configuration
|
||||
*/
|
||||
export async function resolveDevice(device = 'auto') {
|
||||
if (device === 'auto') {
|
||||
return await detectBestDevice();
|
||||
}
|
||||
// Map 'gpu' to appropriate GPU type for current environment
|
||||
if (device === 'gpu') {
|
||||
const detected = await detectBestDevice();
|
||||
return detected === 'cpu' ? 'cpu' : detected;
|
||||
}
|
||||
return device;
|
||||
}
|
||||
export class TransformerEmbedding {
|
||||
/**
|
||||
* Create a new TransformerEmbedding instance
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.extractor = null;
|
||||
this.initialized = false;
|
||||
this.verbose = true;
|
||||
this.verbose = options.verbose !== undefined ? options.verbose : true;
|
||||
// PRODUCTION-READY MODEL CONFIGURATION
|
||||
// Priority order: explicit option > environment variable > smart default
|
||||
let localFilesOnly;
|
||||
if (options.localFilesOnly !== undefined) {
|
||||
// 1. Explicit option takes highest priority
|
||||
localFilesOnly = options.localFilesOnly;
|
||||
}
|
||||
else if (process.env.BRAINY_ALLOW_REMOTE_MODELS === 'false') {
|
||||
// 2. Environment variable explicitly disables remote models (legacy support)
|
||||
localFilesOnly = true;
|
||||
}
|
||||
else if (process.env.NODE_ENV === 'development') {
|
||||
// 3. Development mode allows remote models
|
||||
localFilesOnly = false;
|
||||
}
|
||||
else if (isBrowser()) {
|
||||
// 4. Browser defaults to allowing remote models
|
||||
localFilesOnly = false;
|
||||
}
|
||||
else {
|
||||
// 5. Node.js production: try local first, but allow remote as fallback
|
||||
// This is the NEW production-friendly default
|
||||
localFilesOnly = false;
|
||||
}
|
||||
this.options = {
|
||||
model: options.model || 'Xenova/all-MiniLM-L6-v2',
|
||||
verbose: this.verbose,
|
||||
cacheDir: options.cacheDir || './models',
|
||||
localFilesOnly: localFilesOnly,
|
||||
precision: options.precision || 'fp32', // Clean and clear!
|
||||
device: options.device || 'auto'
|
||||
};
|
||||
// ULTRA-CAREFUL: Runtime warnings for q8 usage
|
||||
if (this.options.precision === 'q8') {
|
||||
const confirmed = process.env.BRAINY_Q8_CONFIRMED === 'true';
|
||||
if (!confirmed && this.verbose) {
|
||||
console.warn('🚨 Q8 MODEL WARNING:');
|
||||
console.warn(' • Q8 creates different embeddings than fp32');
|
||||
console.warn(' • Q8 is incompatible with existing fp32 data');
|
||||
console.warn(' • Only use q8 for new projects or when explicitly migrating');
|
||||
console.warn(' • Set BRAINY_Q8_CONFIRMED=true to silence this warning');
|
||||
console.warn(' • Q8 model is 75% smaller but may have slightly reduced accuracy');
|
||||
}
|
||||
}
|
||||
if (this.verbose) {
|
||||
this.logger('log', `Embedding config: precision=${this.options.precision}, localFilesOnly=${localFilesOnly}, model=${this.options.model}`);
|
||||
}
|
||||
// Configure transformers.js environment
|
||||
if (!isBrowser()) {
|
||||
// Set cache directory for Node.js
|
||||
env.cacheDir = this.options.cacheDir;
|
||||
// Prioritize local models for offline operation
|
||||
env.allowRemoteModels = !this.options.localFilesOnly;
|
||||
env.allowLocalModels = true;
|
||||
}
|
||||
else {
|
||||
// Browser configuration
|
||||
// Allow both local and remote models, but prefer local if available
|
||||
env.allowLocalModels = true;
|
||||
env.allowRemoteModels = true;
|
||||
// Force the configuration to ensure it's applied
|
||||
if (this.verbose) {
|
||||
this.logger('log', `Browser env config - allowLocalModels: ${env.allowLocalModels}, allowRemoteModels: ${env.allowRemoteModels}, localFilesOnly: ${this.options.localFilesOnly}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the default cache directory for models
|
||||
*/
|
||||
async getDefaultCacheDir() {
|
||||
if (isBrowser()) {
|
||||
return './models'; // Browser default
|
||||
}
|
||||
// Check for bundled models in the package
|
||||
const possiblePaths = [
|
||||
// In the installed package
|
||||
'./node_modules/@soulcraft/brainy/models',
|
||||
// In development/source
|
||||
'./models',
|
||||
'./dist/../models',
|
||||
// Alternative locations
|
||||
'../models',
|
||||
'../../models'
|
||||
];
|
||||
// Check if we're in Node.js and try to find the bundled models
|
||||
if (typeof process !== 'undefined' && process.versions?.node) {
|
||||
try {
|
||||
// Use dynamic import instead of require for ES modules compatibility
|
||||
const { createRequire } = await import('module');
|
||||
const require = createRequire(import.meta.url);
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
// Try to resolve the package location
|
||||
try {
|
||||
const brainyPackagePath = require.resolve('@soulcraft/brainy/package.json');
|
||||
const brainyPackageDir = path.dirname(brainyPackagePath);
|
||||
const bundledModelsPath = path.join(brainyPackageDir, 'models');
|
||||
if (fs.existsSync(bundledModelsPath)) {
|
||||
this.logger('log', `Using bundled models from package: ${bundledModelsPath}`);
|
||||
return bundledModelsPath;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// Not installed as package, continue
|
||||
}
|
||||
// Try relative paths from current location
|
||||
for (const relativePath of possiblePaths) {
|
||||
const fullPath = path.resolve(relativePath);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
this.logger('log', `Using bundled models from: ${fullPath}`);
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Silently fall back to default path if module detection fails
|
||||
}
|
||||
}
|
||||
// Fallback to default cache directory
|
||||
return './models';
|
||||
}
|
||||
/**
|
||||
* Check if we're running in a test environment
|
||||
*/
|
||||
isTestEnvironment() {
|
||||
// Always use real implementation - no more mocking
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Log message only if verbose mode is enabled
|
||||
*/
|
||||
logger(level, message, ...args) {
|
||||
if (level === 'error' || this.verbose) {
|
||||
console[level](`[TransformerEmbedding] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generate mock embeddings for unit tests
|
||||
*/
|
||||
getMockEmbedding(data) {
|
||||
// Use the same mock logic as setup-unit.ts for consistency
|
||||
const input = Array.isArray(data) ? data.join(' ') : data;
|
||||
const str = typeof input === 'string' ? input : JSON.stringify(input);
|
||||
const vector = new Array(384).fill(0);
|
||||
// Create semi-realistic embeddings based on text content
|
||||
for (let i = 0; i < Math.min(str.length, 384); i++) {
|
||||
vector[i] = (str.charCodeAt(i % str.length) % 256) / 256;
|
||||
}
|
||||
// Add position-based variation
|
||||
for (let i = 0; i < 384; i++) {
|
||||
vector[i] += Math.sin(i * 0.1 + str.length) * 0.1;
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
/**
|
||||
* Initialize the embedding model
|
||||
*/
|
||||
async init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
// In unit test mode, skip real model initialization to prevent ONNX conflicts
|
||||
if (process.env.BRAINY_UNIT_TEST === 'true' || globalThis.__BRAINY_UNIT_TEST__) {
|
||||
this.initialized = true;
|
||||
this.logger('log', '🧪 Using mocked embeddings for unit tests');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Resolve device configuration and cache directory
|
||||
const device = await resolveDevice(this.options.device);
|
||||
const cacheDir = this.options.cacheDir === './models'
|
||||
? await this.getDefaultCacheDir()
|
||||
: this.options.cacheDir;
|
||||
this.logger('log', `Loading Transformer model: ${this.options.model} on device: ${device}`);
|
||||
const startTime = Date.now();
|
||||
// Use the configured precision from EmbeddingManager
|
||||
const { embeddingManager } = await import('../embeddings/EmbeddingManager.js');
|
||||
let actualType = embeddingManager.getPrecision();
|
||||
// CRITICAL: Control which model precision transformers.js uses
|
||||
// Q8 models use quantized int8 weights for 75% size reduction
|
||||
// FP32 models use full precision floating point
|
||||
if (actualType === 'q8') {
|
||||
this.logger('log', '🎯 Selecting Q8 quantized model (75% smaller, 99% accuracy)');
|
||||
}
|
||||
else {
|
||||
this.logger('log', '📦 Using FP32 model (full precision, larger size)');
|
||||
}
|
||||
// Load the feature extraction pipeline with memory optimizations
|
||||
const pipelineOptions = {
|
||||
cache_dir: cacheDir,
|
||||
local_files_only: isBrowser() ? false : this.options.localFilesOnly,
|
||||
// CRITICAL: Specify dtype for model precision
|
||||
dtype: actualType === 'q8' ? 'q8' : 'fp32',
|
||||
// CRITICAL: For Q8, explicitly use quantized model
|
||||
quantized: actualType === 'q8',
|
||||
// CRITICAL: ONNX memory optimizations
|
||||
session_options: {
|
||||
enableCpuMemArena: false, // Disable pre-allocated memory arena
|
||||
enableMemPattern: false, // Disable memory pattern optimization
|
||||
interOpNumThreads: 1, // Force single thread for V8 stability
|
||||
intraOpNumThreads: 1, // Force single thread for V8 stability
|
||||
graphOptimizationLevel: 'disabled' // Disable threading optimizations
|
||||
}
|
||||
};
|
||||
// Add device configuration for GPU acceleration
|
||||
if (device !== 'cpu') {
|
||||
pipelineOptions.device = device;
|
||||
this.logger('log', `🚀 GPU acceleration enabled: ${device}`);
|
||||
}
|
||||
if (this.verbose) {
|
||||
this.logger('log', `Pipeline options: ${JSON.stringify(pipelineOptions)}`);
|
||||
}
|
||||
try {
|
||||
// For Q8 models, we need to explicitly specify the model file
|
||||
if (actualType === 'q8') {
|
||||
// Check if quantized model exists
|
||||
const modelPath = join(cacheDir, this.options.model, 'onnx', 'model_quantized.onnx');
|
||||
if (existsSync(modelPath)) {
|
||||
this.logger('log', '✅ Q8 model found locally');
|
||||
}
|
||||
else {
|
||||
this.logger('warn', '⚠️ Q8 model not found, will fall back to FP32');
|
||||
actualType = 'fp32'; // Fall back to fp32
|
||||
}
|
||||
}
|
||||
this.extractor = await pipeline('feature-extraction', this.options.model, pipelineOptions);
|
||||
}
|
||||
catch (gpuError) {
|
||||
// Fallback to CPU if GPU initialization fails
|
||||
if (device !== 'cpu') {
|
||||
this.logger('warn', `GPU initialization failed, falling back to CPU: ${gpuError?.message || gpuError}`);
|
||||
const cpuOptions = { ...pipelineOptions };
|
||||
delete cpuOptions.device;
|
||||
this.extractor = await pipeline('feature-extraction', this.options.model, cpuOptions);
|
||||
}
|
||||
else {
|
||||
// PRODUCTION-READY ERROR HANDLING
|
||||
// If local_files_only is true and models are missing, try enabling remote downloads
|
||||
if (pipelineOptions.local_files_only && gpuError?.message?.includes('local_files_only')) {
|
||||
this.logger('warn', 'Local models not found, attempting remote download as fallback...');
|
||||
try {
|
||||
const remoteOptions = { ...pipelineOptions, local_files_only: false };
|
||||
this.extractor = await pipeline('feature-extraction', this.options.model, remoteOptions);
|
||||
this.logger('log', '✅ Successfully downloaded and loaded model from remote');
|
||||
// Update the configuration to reflect what actually worked
|
||||
this.options.localFilesOnly = false;
|
||||
}
|
||||
catch (remoteError) {
|
||||
// Both local and remote failed - throw comprehensive error
|
||||
const errorMsg = `Failed to load embedding model "${this.options.model}". ` +
|
||||
`Local models not found and remote download failed. ` +
|
||||
`To fix: 1) Run "npm run download-models", ` +
|
||||
`2) Check your internet connection, or ` +
|
||||
`3) Use a custom embedding function.`;
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw gpuError;
|
||||
}
|
||||
}
|
||||
}
|
||||
const loadTime = Date.now() - startTime;
|
||||
this.logger('log', `✅ Model loaded successfully in ${loadTime}ms`);
|
||||
this.initialized = true;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger('error', 'Failed to initialize Transformer embedding model:', error);
|
||||
throw new Error(`Transformer embedding initialization failed: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generate embeddings for text data
|
||||
*/
|
||||
async embed(data) {
|
||||
// In unit test mode, return mock embeddings
|
||||
if (process.env.BRAINY_UNIT_TEST === 'true' || globalThis.__BRAINY_UNIT_TEST__) {
|
||||
return this.getMockEmbedding(data);
|
||||
}
|
||||
if (!this.initialized) {
|
||||
await this.init();
|
||||
}
|
||||
try {
|
||||
// Handle different input types
|
||||
let textToEmbed;
|
||||
if (typeof data === 'string') {
|
||||
// Handle empty string case
|
||||
if (data.trim() === '') {
|
||||
// Return a zero vector of 384 dimensions (all-MiniLM-L6-v2 standard)
|
||||
return new Array(384).fill(0);
|
||||
}
|
||||
textToEmbed = [data];
|
||||
}
|
||||
else if (Array.isArray(data) && data.every((item) => typeof item === 'string')) {
|
||||
// Handle empty array or array with empty strings
|
||||
if (data.length === 0 || data.every((item) => item.trim() === '')) {
|
||||
return new Array(384).fill(0);
|
||||
}
|
||||
// Filter out empty strings
|
||||
textToEmbed = data.filter((item) => item.trim() !== '');
|
||||
if (textToEmbed.length === 0) {
|
||||
return new Array(384).fill(0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('TransformerEmbedding only supports string or string[] data');
|
||||
}
|
||||
// Ensure the extractor is available
|
||||
if (!this.extractor) {
|
||||
throw new Error('Transformer embedding model is not available');
|
||||
}
|
||||
// Generate embeddings with mean pooling and normalization
|
||||
const result = await this.extractor(textToEmbed, {
|
||||
pooling: 'mean',
|
||||
normalize: true
|
||||
});
|
||||
// Extract the embedding data
|
||||
let embedding;
|
||||
if (textToEmbed.length === 1) {
|
||||
// Single text input - return first embedding
|
||||
embedding = Array.from(result.data.slice(0, 384));
|
||||
}
|
||||
else {
|
||||
// Multiple texts - return first embedding (maintain compatibility)
|
||||
embedding = Array.from(result.data.slice(0, 384));
|
||||
}
|
||||
// Validate embedding dimensions
|
||||
if (embedding.length !== 384) {
|
||||
this.logger('warn', `Unexpected embedding dimension: ${embedding.length}, expected 384`);
|
||||
// Pad or truncate to 384 dimensions
|
||||
if (embedding.length < 384) {
|
||||
embedding = [...embedding, ...new Array(384 - embedding.length).fill(0)];
|
||||
}
|
||||
else {
|
||||
embedding = embedding.slice(0, 384);
|
||||
}
|
||||
}
|
||||
return embedding;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger('error', 'Error generating embeddings:', error);
|
||||
throw new Error(`Failed to generate embeddings: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Dispose of the model and free resources
|
||||
*/
|
||||
async dispose() {
|
||||
if (this.extractor && typeof this.extractor.dispose === 'function') {
|
||||
await this.extractor.dispose();
|
||||
}
|
||||
this.extractor = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
/**
|
||||
* Get the dimension of embeddings produced by this model
|
||||
*/
|
||||
getDimension() {
|
||||
return 384;
|
||||
}
|
||||
/**
|
||||
* Check if the model is initialized
|
||||
*/
|
||||
isInitialized() {
|
||||
return this.initialized;
|
||||
}
|
||||
}
|
||||
// Legacy alias for backward compatibility
|
||||
export const UniversalSentenceEncoder = TransformerEmbedding;
|
||||
/**
|
||||
* Create a new embedding model instance
|
||||
*/
|
||||
export function createEmbeddingModel(options) {
|
||||
return new TransformerEmbedding(options);
|
||||
}
|
||||
/**
|
||||
* Default embedding function using the unified EmbeddingManager
|
||||
* Simple, clean, reliable - no more layers of indirection
|
||||
*/
|
||||
export const defaultEmbeddingFunction = async (data) => {
|
||||
const { embed } = await import('../embeddings/EmbeddingManager.js');
|
||||
return await embed(data);
|
||||
};
|
||||
/**
|
||||
* Create an embedding function with custom options
|
||||
* NOTE: Options are validated but the singleton EmbeddingManager is always used
|
||||
*/
|
||||
export function createEmbeddingFunction(options = {}) {
|
||||
return async (data) => {
|
||||
const { embeddingManager } = await import('../embeddings/EmbeddingManager.js');
|
||||
// Validate precision if specified
|
||||
if (options.precision) {
|
||||
embeddingManager.validatePrecision(options.precision);
|
||||
}
|
||||
return await embeddingManager.embed(data);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Batch embedding function for processing multiple texts efficiently
|
||||
*/
|
||||
export async function batchEmbed(texts, options = {}) {
|
||||
const embedder = new TransformerEmbedding(options);
|
||||
await embedder.init();
|
||||
const embeddings = [];
|
||||
// Process in batches for memory efficiency
|
||||
const batchSize = 32;
|
||||
for (let i = 0; i < texts.length; i += batchSize) {
|
||||
const batch = texts.slice(i, i + batchSize);
|
||||
for (const text of batch) {
|
||||
const embedding = await embedder.embed(text);
|
||||
embeddings.push(embedding);
|
||||
}
|
||||
}
|
||||
await embedder.dispose();
|
||||
return embeddings;
|
||||
}
|
||||
/**
|
||||
* Embedding functions for specific model types
|
||||
*/
|
||||
export const embeddingFunctions = {
|
||||
/** Default lightweight model (all-MiniLM-L6-v2, 384 dimensions) */
|
||||
default: defaultEmbeddingFunction,
|
||||
/** Create custom embedding function */
|
||||
create: createEmbeddingFunction,
|
||||
/** Batch processing */
|
||||
batch: batchEmbed
|
||||
};
|
||||
//# sourceMappingURL=embedding.js.map
|
||||
File diff suppressed because one or more lines are too long
38
.recovery-workspace/dist-backup-20250910-141917/utils/ensureDeleted.d.ts
vendored
Normal file
38
.recovery-workspace/dist-backup-20250910-141917/utils/ensureDeleted.d.ts
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Utility to ensure all metadata has the deleted field set properly
|
||||
* This is CRITICAL for O(1) soft delete filtering performance
|
||||
*
|
||||
* Uses _brainy namespace to avoid conflicts with user metadata
|
||||
*/
|
||||
/**
|
||||
* Ensure metadata has internal Brainy fields set
|
||||
* @param metadata The metadata object (could be null/undefined)
|
||||
* @param preserveExisting If true, preserve existing deleted value
|
||||
* @returns Metadata with internal fields guaranteed
|
||||
*/
|
||||
export declare function ensureDeletedField(metadata: any, preserveExisting?: boolean): any;
|
||||
/**
|
||||
* Mark an item as soft deleted
|
||||
* @param metadata The metadata object
|
||||
* @returns Metadata with _brainy.deleted=true
|
||||
*/
|
||||
export declare function markAsDeleted(metadata: any): any;
|
||||
/**
|
||||
* Mark an item as restored (not deleted)
|
||||
* @param metadata The metadata object
|
||||
* @returns Metadata with _brainy.deleted=false
|
||||
*/
|
||||
export declare function markAsRestored(metadata: any): any;
|
||||
/**
|
||||
* Check if an item is deleted
|
||||
* @param metadata The metadata object
|
||||
* @returns true if deleted, false otherwise (including if field missing)
|
||||
*/
|
||||
export declare function isDeleted(metadata: any): boolean;
|
||||
/**
|
||||
* Check if an item is active (not deleted)
|
||||
* @param metadata The metadata object
|
||||
* @returns true if not deleted (default), false if deleted
|
||||
*/
|
||||
export declare function isActive(metadata: any): boolean;
|
||||
export declare const BRAINY_DELETED_FIELD = "_brainy.deleted";
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Utility to ensure all metadata has the deleted field set properly
|
||||
* This is CRITICAL for O(1) soft delete filtering performance
|
||||
*
|
||||
* Uses _brainy namespace to avoid conflicts with user metadata
|
||||
*/
|
||||
const BRAINY_NAMESPACE = '_brainy';
|
||||
/**
|
||||
* Ensure metadata has internal Brainy fields set
|
||||
* @param metadata The metadata object (could be null/undefined)
|
||||
* @param preserveExisting If true, preserve existing deleted value
|
||||
* @returns Metadata with internal fields guaranteed
|
||||
*/
|
||||
export function ensureDeletedField(metadata, preserveExisting = true) {
|
||||
// Handle null/undefined metadata
|
||||
if (!metadata) {
|
||||
return {
|
||||
[BRAINY_NAMESPACE]: {
|
||||
deleted: false,
|
||||
version: 1
|
||||
}
|
||||
};
|
||||
}
|
||||
// Clone to avoid mutation
|
||||
const result = { ...metadata };
|
||||
// Ensure _brainy namespace exists
|
||||
if (!result[BRAINY_NAMESPACE]) {
|
||||
result[BRAINY_NAMESPACE] = {};
|
||||
}
|
||||
// Set deleted field if not present
|
||||
if (!('deleted' in result[BRAINY_NAMESPACE])) {
|
||||
result[BRAINY_NAMESPACE].deleted = false;
|
||||
}
|
||||
else if (!preserveExisting) {
|
||||
// Force to false if not preserving
|
||||
result[BRAINY_NAMESPACE].deleted = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Mark an item as soft deleted
|
||||
* @param metadata The metadata object
|
||||
* @returns Metadata with _brainy.deleted=true
|
||||
*/
|
||||
export function markAsDeleted(metadata) {
|
||||
const result = ensureDeletedField(metadata);
|
||||
result[BRAINY_NAMESPACE].deleted = true;
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Mark an item as restored (not deleted)
|
||||
* @param metadata The metadata object
|
||||
* @returns Metadata with _brainy.deleted=false
|
||||
*/
|
||||
export function markAsRestored(metadata) {
|
||||
const result = ensureDeletedField(metadata);
|
||||
result[BRAINY_NAMESPACE].deleted = false;
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Check if an item is deleted
|
||||
* @param metadata The metadata object
|
||||
* @returns true if deleted, false otherwise (including if field missing)
|
||||
*/
|
||||
export function isDeleted(metadata) {
|
||||
return metadata?.[BRAINY_NAMESPACE]?.deleted === true;
|
||||
}
|
||||
/**
|
||||
* Check if an item is active (not deleted)
|
||||
* @param metadata The metadata object
|
||||
* @returns true if not deleted (default), false if deleted
|
||||
*/
|
||||
export function isActive(metadata) {
|
||||
// If no deleted field or deleted=false, item is active
|
||||
return !isDeleted(metadata);
|
||||
}
|
||||
// Export the namespace constant for use in queries
|
||||
export const BRAINY_DELETED_FIELD = `${BRAINY_NAMESPACE}.deleted`;
|
||||
//# sourceMappingURL=ensureDeleted.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"ensureDeleted.js","sourceRoot":"","sources":["../../src/utils/ensureDeleted.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,gBAAgB,GAAG,SAAS,CAAA;AAElC;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAa,EAAE,mBAA4B,IAAI;IAChF,iCAAiC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,CAAC,gBAAgB,CAAC,EAAE;gBAClB,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,CAAC;aACX;SACF,CAAA;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAA;IAE9B,kCAAkC;IAClC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAA;IAC/B,CAAC;IAED,mCAAmC;IACnC,IAAI,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,gBAAgB,CAAC,CAAC,OAAO,GAAG,KAAK,CAAA;IAC1C,CAAC;SAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC7B,mCAAmC;QACnC,MAAM,CAAC,gBAAgB,CAAC,CAAC,OAAO,GAAG,KAAK,CAAA;IAC1C,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,QAAa;IACzC,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IAC3C,MAAM,CAAC,gBAAgB,CAAC,CAAC,OAAO,GAAG,IAAI,CAAA;IACvC,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAa;IAC1C,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IAC3C,MAAM,CAAC,gBAAgB,CAAC,CAAC,OAAO,GAAG,KAAK,CAAA;IACxC,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,QAAa;IACrC,OAAO,QAAQ,EAAE,CAAC,gBAAgB,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAa;IACpC,uDAAuD;IACvD,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;AAC7B,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,gBAAgB,UAAU,CAAA"}
|
||||
49
.recovery-workspace/dist-backup-20250910-141917/utils/environment.d.ts
vendored
Normal file
49
.recovery-workspace/dist-backup-20250910-141917/utils/environment.d.ts
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Utility functions for environment detection
|
||||
*/
|
||||
/**
|
||||
* Check if code is running in a browser environment
|
||||
*/
|
||||
export declare function isBrowser(): boolean;
|
||||
/**
|
||||
* Check if code is running in a Node.js environment
|
||||
*/
|
||||
export declare function isNode(): boolean;
|
||||
/**
|
||||
* Check if code is running in a Web Worker environment
|
||||
*/
|
||||
export declare function isWebWorker(): boolean;
|
||||
/**
|
||||
* Check if Web Workers are available in the current environment
|
||||
*/
|
||||
export declare function areWebWorkersAvailable(): boolean;
|
||||
/**
|
||||
* Check if Worker Threads are available in the current environment (Node.js)
|
||||
*/
|
||||
export declare function areWorkerThreadsAvailable(): Promise<boolean>;
|
||||
/**
|
||||
* Synchronous version that doesn't actually try to load the module
|
||||
* This is safer in ES module environments
|
||||
*/
|
||||
export declare function areWorkerThreadsAvailableSync(): boolean;
|
||||
/**
|
||||
* Determine if threading is available in the current environment
|
||||
* Returns true if either Web Workers (browser) or Worker Threads (Node.js) are available
|
||||
*/
|
||||
export declare function isThreadingAvailable(): boolean;
|
||||
/**
|
||||
* Async version of isThreadingAvailable
|
||||
*/
|
||||
export declare function isThreadingAvailableAsync(): Promise<boolean>;
|
||||
/**
|
||||
* Auto-detect production environment to minimize logging costs
|
||||
*/
|
||||
export declare function isProductionEnvironment(): boolean;
|
||||
/**
|
||||
* Get appropriate log level based on environment
|
||||
*/
|
||||
export declare function getLogLevel(): 'silent' | 'error' | 'warn' | 'info' | 'verbose';
|
||||
/**
|
||||
* Check if logging should be enabled for a given level
|
||||
*/
|
||||
export declare function shouldLog(level: 'error' | 'warn' | 'info' | 'verbose'): boolean;
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* Utility functions for environment detection
|
||||
*/
|
||||
/**
|
||||
* Check if code is running in a browser environment
|
||||
*/
|
||||
export function isBrowser() {
|
||||
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
||||
}
|
||||
/**
|
||||
* Check if code is running in a Node.js environment
|
||||
*/
|
||||
export function isNode() {
|
||||
// If browser environment is detected, prioritize it over Node.js
|
||||
// This handles cases like jsdom where both window and process exist
|
||||
if (isBrowser()) {
|
||||
return false;
|
||||
}
|
||||
return (typeof process !== 'undefined' &&
|
||||
process.versions != null &&
|
||||
process.versions.node != null);
|
||||
}
|
||||
/**
|
||||
* Check if code is running in a Web Worker environment
|
||||
*/
|
||||
export function isWebWorker() {
|
||||
return (typeof self === 'object' &&
|
||||
self.constructor &&
|
||||
self.constructor.name === 'DedicatedWorkerGlobalScope');
|
||||
}
|
||||
/**
|
||||
* Check if Web Workers are available in the current environment
|
||||
*/
|
||||
export function areWebWorkersAvailable() {
|
||||
return isBrowser() && typeof Worker !== 'undefined';
|
||||
}
|
||||
/**
|
||||
* Check if Worker Threads are available in the current environment (Node.js)
|
||||
*/
|
||||
export async function areWorkerThreadsAvailable() {
|
||||
if (!isNode())
|
||||
return false;
|
||||
try {
|
||||
// Use dynamic import to avoid errors in browser environments
|
||||
await import('worker_threads');
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Synchronous version that doesn't actually try to load the module
|
||||
* This is safer in ES module environments
|
||||
*/
|
||||
export function areWorkerThreadsAvailableSync() {
|
||||
if (!isNode())
|
||||
return false;
|
||||
// In Node.js 24.4.0+, worker_threads is always available
|
||||
return parseInt(process.versions.node.split('.')[0]) >= 24;
|
||||
}
|
||||
/**
|
||||
* Determine if threading is available in the current environment
|
||||
* Returns true if either Web Workers (browser) or Worker Threads (Node.js) are available
|
||||
*/
|
||||
export function isThreadingAvailable() {
|
||||
return areWebWorkersAvailable() || areWorkerThreadsAvailableSync();
|
||||
}
|
||||
/**
|
||||
* Async version of isThreadingAvailable
|
||||
*/
|
||||
export async function isThreadingAvailableAsync() {
|
||||
return areWebWorkersAvailable() || (await areWorkerThreadsAvailable());
|
||||
}
|
||||
/**
|
||||
* Auto-detect production environment to minimize logging costs
|
||||
*/
|
||||
export function isProductionEnvironment() {
|
||||
// Node.js environment detection
|
||||
if (isNode()) {
|
||||
// Check common production environment indicators
|
||||
const nodeEnv = process.env.NODE_ENV?.toLowerCase();
|
||||
if (nodeEnv === 'production' || nodeEnv === 'prod')
|
||||
return true;
|
||||
// Google Cloud Run detection
|
||||
if (process.env.K_SERVICE || process.env.GOOGLE_CLOUD_PROJECT)
|
||||
return true;
|
||||
// AWS Lambda detection
|
||||
if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV)
|
||||
return true;
|
||||
// Azure Functions detection
|
||||
if (process.env.AZURE_FUNCTIONS_ENVIRONMENT || process.env.WEBSITE_SITE_NAME)
|
||||
return true;
|
||||
// Vercel detection
|
||||
if (process.env.VERCEL || process.env.VERCEL_ENV === 'production')
|
||||
return true;
|
||||
// Netlify detection
|
||||
if (process.env.NETLIFY && process.env.CONTEXT === 'production')
|
||||
return true;
|
||||
// Heroku detection
|
||||
if (process.env.DYNO && process.env.NODE_ENV !== 'development')
|
||||
return true;
|
||||
// Railway detection
|
||||
if (process.env.RAILWAY_ENVIRONMENT === 'production')
|
||||
return true;
|
||||
// Fly.io detection
|
||||
if (process.env.FLY_APP_NAME && process.env.FLY_REGION)
|
||||
return true;
|
||||
// Docker in production (common patterns)
|
||||
if (process.env.DOCKER_ENV === 'production' || process.env.ENVIRONMENT === 'production')
|
||||
return true;
|
||||
// Generic production indicators
|
||||
if (process.env.PROD === 'true' || process.env.PRODUCTION === 'true')
|
||||
return true;
|
||||
}
|
||||
// Browser environment - assume development unless explicitly production
|
||||
if (isBrowser()) {
|
||||
// Check for production domain patterns
|
||||
const hostname = window?.location?.hostname;
|
||||
if (hostname) {
|
||||
// Avoid logging on production domains
|
||||
if (hostname.includes('.com') || hostname.includes('.org') || hostname.includes('.net')) {
|
||||
return !hostname.includes('localhost') && !hostname.includes('127.0.0.1') && !hostname.includes('dev');
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Get appropriate log level based on environment
|
||||
*/
|
||||
export function getLogLevel() {
|
||||
// Explicit log level override
|
||||
const explicitLevel = process.env.BRAINY_LOG_LEVEL?.toLowerCase();
|
||||
if (explicitLevel && ['silent', 'error', 'warn', 'info', 'verbose'].includes(explicitLevel)) {
|
||||
return explicitLevel;
|
||||
}
|
||||
// Auto-detect based on environment
|
||||
if (isProductionEnvironment()) {
|
||||
return 'error'; // Only log errors in production to minimize costs
|
||||
}
|
||||
// Development environments get more verbose logging
|
||||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev') {
|
||||
return 'verbose';
|
||||
}
|
||||
// Test environments should be quieter
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return 'warn';
|
||||
}
|
||||
// Default to info level
|
||||
return 'info';
|
||||
}
|
||||
/**
|
||||
* Check if logging should be enabled for a given level
|
||||
*/
|
||||
export function shouldLog(level) {
|
||||
const currentLevel = getLogLevel();
|
||||
if (currentLevel === 'silent')
|
||||
return false;
|
||||
const levels = ['error', 'warn', 'info', 'verbose'];
|
||||
const currentIndex = levels.indexOf(currentLevel);
|
||||
const messageIndex = levels.indexOf(level);
|
||||
return messageIndex <= currentIndex;
|
||||
}
|
||||
//# sourceMappingURL=environment.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/utils/environment.ts"],"names":[],"mappings":"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"}
|
||||
21
.recovery-workspace/dist-backup-20250910-141917/utils/fieldNameTracking.d.ts
vendored
Normal file
21
.recovery-workspace/dist-backup-20250910-141917/utils/fieldNameTracking.d.ts
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Utility functions for tracking and managing field names in JSON documents
|
||||
*/
|
||||
/**
|
||||
* Extracts field names from a JSON document
|
||||
* @param jsonObject The JSON object to extract field names from
|
||||
* @param options Configuration options
|
||||
* @returns An array of field paths (e.g., "user.name", "addresses[0].city")
|
||||
*/
|
||||
export declare function extractFieldNamesFromJson(jsonObject: any, options?: {
|
||||
maxDepth?: number;
|
||||
currentDepth?: number;
|
||||
currentPath?: string;
|
||||
fieldNames?: Set<string>;
|
||||
}): string[];
|
||||
/**
|
||||
* Maps field names to standard field names based on common patterns
|
||||
* @param fieldName The field name to map
|
||||
* @returns The standard field name if a match is found, or null if no match
|
||||
*/
|
||||
export declare function mapToStandardField(fieldName: string): string | null;
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Utility functions for tracking and managing field names in JSON documents
|
||||
*/
|
||||
/**
|
||||
* Extracts field names from a JSON document
|
||||
* @param jsonObject The JSON object to extract field names from
|
||||
* @param options Configuration options
|
||||
* @returns An array of field paths (e.g., "user.name", "addresses[0].city")
|
||||
*/
|
||||
export function extractFieldNamesFromJson(jsonObject, options = {}) {
|
||||
const { maxDepth = 5, currentDepth = 0, currentPath = '', fieldNames = new Set() } = options;
|
||||
if (jsonObject === null ||
|
||||
jsonObject === undefined ||
|
||||
typeof jsonObject !== 'object' ||
|
||||
currentDepth >= maxDepth) {
|
||||
return Array.from(fieldNames);
|
||||
}
|
||||
if (Array.isArray(jsonObject)) {
|
||||
// For arrays, we'll just check the first item to avoid explosion of paths
|
||||
if (jsonObject.length > 0) {
|
||||
const arrayPath = currentPath ? `${currentPath}[0]` : '[0]';
|
||||
extractFieldNamesFromJson(jsonObject[0], {
|
||||
maxDepth,
|
||||
currentDepth: currentDepth + 1,
|
||||
currentPath: arrayPath,
|
||||
fieldNames
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For objects, process each property
|
||||
for (const key of Object.keys(jsonObject)) {
|
||||
const value = jsonObject[key];
|
||||
const fieldPath = currentPath ? `${currentPath}.${key}` : key;
|
||||
// Add this field path
|
||||
fieldNames.add(fieldPath);
|
||||
// Recursively process nested objects
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
extractFieldNamesFromJson(value, {
|
||||
maxDepth,
|
||||
currentDepth: currentDepth + 1,
|
||||
currentPath: fieldPath,
|
||||
fieldNames
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(fieldNames);
|
||||
}
|
||||
/**
|
||||
* Maps field names to standard field names based on common patterns
|
||||
* @param fieldName The field name to map
|
||||
* @returns The standard field name if a match is found, or null if no match
|
||||
*/
|
||||
export function mapToStandardField(fieldName) {
|
||||
// Standard field mappings
|
||||
const standardMappings = {
|
||||
'title': ['title', 'name', 'headline', 'subject'],
|
||||
'description': ['description', 'summary', 'content', 'text', 'body'],
|
||||
'author': ['author', 'creator', 'user', 'owner', 'by'],
|
||||
'date': ['date', 'created', 'createdAt', 'timestamp', 'published'],
|
||||
'url': ['url', 'link', 'href', 'source'],
|
||||
'image': ['image', 'thumbnail', 'photo', 'picture'],
|
||||
'tags': ['tags', 'categories', 'keywords', 'topics']
|
||||
};
|
||||
// Check for matches
|
||||
for (const [standardField, possibleMatches] of Object.entries(standardMappings)) {
|
||||
// Exact match
|
||||
if (possibleMatches.includes(fieldName)) {
|
||||
return standardField;
|
||||
}
|
||||
// Path match (e.g., "user.name" matches "name")
|
||||
const parts = fieldName.split('.');
|
||||
const lastPart = parts[parts.length - 1];
|
||||
if (possibleMatches.includes(lastPart)) {
|
||||
return standardField;
|
||||
}
|
||||
// Array match (e.g., "items[0].name" matches "name")
|
||||
if (fieldName.includes('[')) {
|
||||
for (const part of parts) {
|
||||
const cleanPart = part.split('[')[0];
|
||||
if (possibleMatches.includes(cleanPart)) {
|
||||
return standardField;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//# sourceMappingURL=fieldNameTracking.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"fieldNameTracking.js","sourceRoot":"","sources":["../../src/utils/fieldNameTracking.ts"],"names":[],"mappings":"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"}
|
||||
64
.recovery-workspace/dist-backup-20250910-141917/utils/hybridModelManager.d.ts
vendored
Normal file
64
.recovery-workspace/dist-backup-20250910-141917/utils/hybridModelManager.d.ts
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Hybrid Model Manager - BEST OF BOTH WORLDS
|
||||
*
|
||||
* NOW A WRAPPER AROUND SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*
|
||||
* Previously combined:
|
||||
* 1. Multi-source downloading strategy (GitHub → CDN → Hugging Face)
|
||||
* 2. Singleton pattern preventing multiple ONNX model loads
|
||||
* 3. Environment-specific optimizations
|
||||
* 4. Graceful fallbacks and error handling
|
||||
*
|
||||
* Now delegates all operations to SingletonModelManager for true unification
|
||||
*/
|
||||
import { EmbeddingFunction } from '../coreTypes.js';
|
||||
/**
|
||||
* HybridModelManager - Now a wrapper around SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*/
|
||||
declare class HybridModelManager {
|
||||
private static instance;
|
||||
private constructor();
|
||||
static getInstance(): HybridModelManager;
|
||||
/**
|
||||
* Get the primary embedding model - delegates to SingletonModelManager
|
||||
*/
|
||||
getPrimaryModel(): Promise<any>;
|
||||
/**
|
||||
* Get embedding function - delegates to SingletonModelManager
|
||||
*/
|
||||
getEmbeddingFunction(): Promise<EmbeddingFunction>;
|
||||
/**
|
||||
* Check if model is ready - delegates to SingletonModelManager
|
||||
*/
|
||||
isModelReady(): boolean;
|
||||
/**
|
||||
* Force model reload - not supported with SingletonModelManager
|
||||
*/
|
||||
reloadModel(): Promise<void>;
|
||||
/**
|
||||
* Get model status - delegates to SingletonModelManager
|
||||
*/
|
||||
getModelStatus(): {
|
||||
loaded: boolean;
|
||||
ready: boolean;
|
||||
modelType: string;
|
||||
};
|
||||
}
|
||||
export declare const hybridModelManager: HybridModelManager;
|
||||
/**
|
||||
* Get the hybrid singleton embedding function - Now delegates to SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*/
|
||||
export declare function getHybridEmbeddingFunction(): Promise<EmbeddingFunction>;
|
||||
/**
|
||||
* Hybrid embedding function - Now delegates to SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*/
|
||||
export declare const hybridEmbeddingFunction: EmbeddingFunction;
|
||||
/**
|
||||
* Preload model for tests or production - Now delegates to SingletonModelManager
|
||||
*/
|
||||
export declare function preloadHybridModel(): Promise<void>;
|
||||
export {};
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Hybrid Model Manager - BEST OF BOTH WORLDS
|
||||
*
|
||||
* NOW A WRAPPER AROUND SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*
|
||||
* Previously combined:
|
||||
* 1. Multi-source downloading strategy (GitHub → CDN → Hugging Face)
|
||||
* 2. Singleton pattern preventing multiple ONNX model loads
|
||||
* 3. Environment-specific optimizations
|
||||
* 4. Graceful fallbacks and error handling
|
||||
*
|
||||
* Now delegates all operations to SingletonModelManager for true unification
|
||||
*/
|
||||
import { singletonModelManager, getUnifiedEmbeddingFunction } from '../embeddings/SingletonModelManager.js';
|
||||
/**
|
||||
* HybridModelManager - Now a wrapper around SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*/
|
||||
class HybridModelManager {
|
||||
constructor() {
|
||||
console.log('🔄 HybridModelManager now delegates to SingletonModelManager');
|
||||
}
|
||||
static getInstance() {
|
||||
if (!HybridModelManager.instance) {
|
||||
HybridModelManager.instance = new HybridModelManager();
|
||||
}
|
||||
return HybridModelManager.instance;
|
||||
}
|
||||
/**
|
||||
* Get the primary embedding model - delegates to SingletonModelManager
|
||||
*/
|
||||
async getPrimaryModel() {
|
||||
// Delegate to SingletonModelManager
|
||||
return await singletonModelManager.getModel();
|
||||
}
|
||||
/**
|
||||
* Get embedding function - delegates to SingletonModelManager
|
||||
*/
|
||||
async getEmbeddingFunction() {
|
||||
return await getUnifiedEmbeddingFunction();
|
||||
}
|
||||
/**
|
||||
* Check if model is ready - delegates to SingletonModelManager
|
||||
*/
|
||||
isModelReady() {
|
||||
return singletonModelManager.isInitialized();
|
||||
}
|
||||
/**
|
||||
* Force model reload - not supported with SingletonModelManager
|
||||
*/
|
||||
async reloadModel() {
|
||||
console.warn('⚠️ Model reload not supported with SingletonModelManager');
|
||||
console.log('ℹ️ Singleton model persists for consistency');
|
||||
// Just ensure model is initialized
|
||||
await this.getPrimaryModel();
|
||||
}
|
||||
/**
|
||||
* Get model status - delegates to SingletonModelManager
|
||||
*/
|
||||
getModelStatus() {
|
||||
const isReady = singletonModelManager.isInitialized();
|
||||
return {
|
||||
loaded: isReady,
|
||||
ready: isReady,
|
||||
modelType: 'SingletonModelManager (Unified model instance)'
|
||||
};
|
||||
}
|
||||
}
|
||||
HybridModelManager.instance = null;
|
||||
// Export singleton instance
|
||||
export const hybridModelManager = HybridModelManager.getInstance();
|
||||
/**
|
||||
* Get the hybrid singleton embedding function - Now delegates to SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*/
|
||||
export async function getHybridEmbeddingFunction() {
|
||||
return await getUnifiedEmbeddingFunction();
|
||||
}
|
||||
/**
|
||||
* Hybrid embedding function - Now delegates to SingletonModelManager
|
||||
* Maintained for backward compatibility
|
||||
*/
|
||||
export const hybridEmbeddingFunction = async (data) => {
|
||||
return await singletonModelManager.embed(data);
|
||||
};
|
||||
/**
|
||||
* Preload model for tests or production - Now delegates to SingletonModelManager
|
||||
*/
|
||||
export async function preloadHybridModel() {
|
||||
console.log('🚀 Preloading model via SingletonModelManager...');
|
||||
await singletonModelManager.getModel();
|
||||
console.log('✅ Singleton model preloaded and ready!');
|
||||
}
|
||||
//# sourceMappingURL=hybridModelManager.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"hybridModelManager.js","sourceRoot":"","sources":["../../src/utils/hybridModelManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAA;AAE3G;;;GAGG;AACH,MAAM,kBAAkB;IAGtB;QACE,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAA;IAC7E,CAAC;IAEM,MAAM,CAAC,WAAW;QACvB,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,CAAC;YACjC,kBAAkB,CAAC,QAAQ,GAAG,IAAI,kBAAkB,EAAE,CAAA;QACxD,CAAC;QACD,OAAO,kBAAkB,CAAC,QAAQ,CAAA;IACpC,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,eAAe;QAC1B,oCAAoC;QACpC,OAAO,MAAM,qBAAqB,CAAC,QAAQ,EAAE,CAAA;IAC/C,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,oBAAoB;QAC/B,OAAO,MAAM,2BAA2B,EAAE,CAAA;IAC5C,CAAC;IAED;;OAEG;IACI,YAAY;QACjB,OAAO,qBAAqB,CAAC,aAAa,EAAE,CAAA;IAC9C,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,WAAW;QACtB,OAAO,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAA;QACxE,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAA;QAC1D,mCAAmC;QACnC,MAAM,IAAI,CAAC,eAAe,EAAE,CAAA;IAC9B,CAAC;IAED;;OAEG;IACI,cAAc;QACnB,MAAM,OAAO,GAAG,qBAAqB,CAAC,aAAa,EAAE,CAAA;QACrD,OAAO;YACL,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,OAAO;YACd,SAAS,EAAE,gDAAgD;SAC5D,CAAA;IACH,CAAC;;AAvDc,2BAAQ,GAA8B,IAAI,CAAA;AA0D3D,4BAA4B;AAC5B,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,CAAC,WAAW,EAAE,CAAA;AAElE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,OAAO,MAAM,2BAA2B,EAAE,CAAA;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsB,KAAK,EAAE,IAAuB,EAAmB,EAAE;IAC3G,OAAO,MAAM,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AAChD,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAA;IAC/D,MAAM,qBAAqB,CAAC,QAAQ,EAAE,CAAA;IACtC,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAA;AACvD,CAAC"}
|
||||
7
.recovery-workspace/dist-backup-20250910-141917/utils/index.d.ts
vendored
Normal file
7
.recovery-workspace/dist-backup-20250910-141917/utils/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export * from './distance.js';
|
||||
export * from './embedding.js';
|
||||
export * from './workerUtils.js';
|
||||
export * from './statisticsCollector.js';
|
||||
export * from './jsonProcessing.js';
|
||||
export * from './fieldNameTracking.js';
|
||||
export * from './version.js';
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export * from './distance.js';
|
||||
export * from './embedding.js';
|
||||
export * from './workerUtils.js';
|
||||
export * from './statisticsCollector.js';
|
||||
export * from './jsonProcessing.js';
|
||||
export * from './fieldNameTracking.js';
|
||||
export * from './version.js';
|
||||
//# sourceMappingURL=index.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA"}
|
||||
60
.recovery-workspace/dist-backup-20250910-141917/utils/intelligentTypeMapper.d.ts
vendored
Normal file
60
.recovery-workspace/dist-backup-20250910-141917/utils/intelligentTypeMapper.d.ts
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* Intelligent Type Mapper
|
||||
* Maps generic/invalid type names to specific semantic types based on data analysis
|
||||
* Prevents semantic degradation from overuse of generic types
|
||||
*/
|
||||
/**
|
||||
* Intelligent Type Mapper
|
||||
*/
|
||||
export declare class IntelligentTypeMapper {
|
||||
private typeCache;
|
||||
private inferenceStats;
|
||||
/**
|
||||
* Map a noun type, with intelligent inference for generic types
|
||||
*/
|
||||
mapNounType(inputType: string, data?: any): string;
|
||||
/**
|
||||
* Map a verb type
|
||||
*/
|
||||
mapVerbType(inputType: string): string;
|
||||
/**
|
||||
* Infer type from data structure
|
||||
*/
|
||||
private inferTypeFromData;
|
||||
/**
|
||||
* Get direct mapping for common aliases
|
||||
*/
|
||||
private getDirectMapping;
|
||||
/**
|
||||
* Check if a type is valid
|
||||
*/
|
||||
private isValidNounType;
|
||||
/**
|
||||
* Check if a verb type is valid
|
||||
*/
|
||||
private isValidVerbType;
|
||||
/**
|
||||
* Get inference statistics
|
||||
*/
|
||||
getStats(): {
|
||||
cacheSize: number;
|
||||
inferenceRate: number;
|
||||
total: number;
|
||||
inferred: number;
|
||||
defaulted: number;
|
||||
cached: number;
|
||||
};
|
||||
/**
|
||||
* Clear the type cache
|
||||
*/
|
||||
clearCache(): void;
|
||||
}
|
||||
export declare const typeMapper: IntelligentTypeMapper;
|
||||
/**
|
||||
* Helper function for easy type mapping
|
||||
*/
|
||||
export declare function mapNounType(inputType: string, data?: any): string;
|
||||
/**
|
||||
* Helper function for verb mapping
|
||||
*/
|
||||
export declare function mapVerbType(inputType: string): string;
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
/**
|
||||
* Intelligent Type Mapper
|
||||
* Maps generic/invalid type names to specific semantic types based on data analysis
|
||||
* Prevents semantic degradation from overuse of generic types
|
||||
*/
|
||||
import { NounType, VerbType } from '../types/graphTypes.js';
|
||||
/**
|
||||
* Common aliases that users might use
|
||||
*/
|
||||
const GENERIC_ALIASES = new Set([
|
||||
'entity',
|
||||
'item',
|
||||
'object',
|
||||
'node',
|
||||
'record',
|
||||
'entry',
|
||||
'data',
|
||||
'resource'
|
||||
]);
|
||||
/**
|
||||
* Field signatures for type inference
|
||||
*/
|
||||
const TYPE_SIGNATURES = {
|
||||
// Person indicators
|
||||
person: {
|
||||
required: [],
|
||||
indicators: ['email', 'firstName', 'lastName', 'name', 'phone', 'username', 'userId'],
|
||||
patterns: [/@/, /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i],
|
||||
weight: 10
|
||||
},
|
||||
// User account indicators
|
||||
user: {
|
||||
required: [],
|
||||
indicators: ['username', 'password', 'accountId', 'loginTime', 'permissions', 'role'],
|
||||
patterns: [],
|
||||
weight: 9
|
||||
},
|
||||
// Organization indicators
|
||||
organization: {
|
||||
required: [],
|
||||
indicators: ['companyName', 'orgName', 'ein', 'vatNumber', 'employees', 'headquarters'],
|
||||
patterns: [],
|
||||
weight: 8
|
||||
},
|
||||
// Product indicators
|
||||
product: {
|
||||
required: [],
|
||||
indicators: ['price', 'sku', 'barcode', 'inventory', 'cost', 'productId', 'inStock'],
|
||||
patterns: [/^\$?\d+\.?\d*$/, /^[A-Z0-9-]+$/],
|
||||
weight: 8
|
||||
},
|
||||
// Document indicators
|
||||
document: {
|
||||
required: [],
|
||||
indicators: ['content', 'text', 'body', 'title', 'author', 'markdown', 'html'],
|
||||
patterns: [],
|
||||
weight: 7
|
||||
},
|
||||
// Message indicators
|
||||
message: {
|
||||
required: [],
|
||||
indicators: ['from', 'to', 'subject', 'body', 'sentAt', 'messageId', 'threadId'],
|
||||
patterns: [],
|
||||
weight: 7
|
||||
},
|
||||
// Task indicators
|
||||
task: {
|
||||
required: [],
|
||||
indicators: ['dueDate', 'assignee', 'status', 'priority', 'completed', 'taskId'],
|
||||
patterns: [],
|
||||
weight: 7
|
||||
},
|
||||
// Event indicators
|
||||
event: {
|
||||
required: [],
|
||||
indicators: ['startTime', 'endTime', 'date', 'location', 'attendees', 'eventType'],
|
||||
patterns: [],
|
||||
weight: 6
|
||||
},
|
||||
// Location indicators
|
||||
location: {
|
||||
required: [],
|
||||
indicators: ['latitude', 'longitude', 'address', 'city', 'country', 'zipCode', 'coordinates'],
|
||||
patterns: [/^-?\d+\.\d+$/, /^\d{5}(-\d{4})?$/],
|
||||
weight: 6
|
||||
},
|
||||
// File indicators
|
||||
file: {
|
||||
required: [],
|
||||
indicators: ['filename', 'filepath', 'extension', 'mimeType', 'fileSize', 'checksum'],
|
||||
patterns: [/\.[a-z0-9]+$/i],
|
||||
weight: 5
|
||||
},
|
||||
// Dataset indicators
|
||||
dataset: {
|
||||
required: [],
|
||||
indicators: ['schema', 'rows', 'columns', 'records', 'dataType', 'format'],
|
||||
patterns: [],
|
||||
weight: 5
|
||||
},
|
||||
// Media indicators
|
||||
media: {
|
||||
required: [],
|
||||
indicators: ['url', 'thumbnail', 'duration', 'resolution', 'codec', 'bitrate'],
|
||||
patterns: [/\.(jpg|jpeg|png|gif|mp4|mp3|wav|avi)$/i],
|
||||
weight: 5
|
||||
},
|
||||
// Project indicators
|
||||
project: {
|
||||
required: [],
|
||||
indicators: ['deadline', 'budget', 'team', 'milestones', 'deliverables', 'projectId'],
|
||||
patterns: [],
|
||||
weight: 5
|
||||
},
|
||||
// Service indicators
|
||||
service: {
|
||||
required: [],
|
||||
indicators: ['endpoint', 'apiKey', 'serviceUrl', 'port', 'protocol', 'healthCheck'],
|
||||
patterns: [/^https?:\/\//, /:\d+$/],
|
||||
weight: 4
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Intelligent Type Mapper
|
||||
*/
|
||||
export class IntelligentTypeMapper {
|
||||
constructor() {
|
||||
this.typeCache = new Map();
|
||||
this.inferenceStats = {
|
||||
total: 0,
|
||||
inferred: 0,
|
||||
defaulted: 0,
|
||||
cached: 0
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Map a noun type, with intelligent inference for generic types
|
||||
*/
|
||||
mapNounType(inputType, data) {
|
||||
// Check if it's already a valid type
|
||||
if (this.isValidNounType(inputType)) {
|
||||
return inputType;
|
||||
}
|
||||
// Check cache for this exact input
|
||||
const cacheKey = `${inputType}-${JSON.stringify(data || {}).substring(0, 100)}`;
|
||||
if (this.typeCache.has(cacheKey)) {
|
||||
this.inferenceStats.cached++;
|
||||
return this.typeCache.get(cacheKey);
|
||||
}
|
||||
this.inferenceStats.total++;
|
||||
// If it's a generic alias and we have data, try to infer
|
||||
if (GENERIC_ALIASES.has(inputType.toLowerCase()) && data) {
|
||||
const inferred = this.inferTypeFromData(data);
|
||||
if (inferred) {
|
||||
this.inferenceStats.inferred++;
|
||||
this.typeCache.set(cacheKey, inferred);
|
||||
return inferred;
|
||||
}
|
||||
}
|
||||
// Handle specific common mappings
|
||||
const directMapping = this.getDirectMapping(inputType);
|
||||
if (directMapping) {
|
||||
this.typeCache.set(cacheKey, directMapping);
|
||||
return directMapping;
|
||||
}
|
||||
// Default to 'thing' for truly unknown types
|
||||
this.inferenceStats.defaulted++;
|
||||
const defaultType = NounType.Thing;
|
||||
this.typeCache.set(cacheKey, defaultType);
|
||||
return defaultType;
|
||||
}
|
||||
/**
|
||||
* Map a verb type
|
||||
*/
|
||||
mapVerbType(inputType) {
|
||||
// Check if it's already valid
|
||||
if (this.isValidVerbType(inputType)) {
|
||||
return inputType;
|
||||
}
|
||||
// Common verb mappings
|
||||
const verbMappings = {
|
||||
'related': VerbType.RelatedTo,
|
||||
'relates': VerbType.RelatedTo,
|
||||
'has': VerbType.Contains,
|
||||
'includes': VerbType.Contains,
|
||||
'belongsTo': VerbType.PartOf,
|
||||
'in': VerbType.LocatedAt,
|
||||
'at': VerbType.LocatedAt,
|
||||
'references': VerbType.References,
|
||||
'cites': VerbType.References,
|
||||
'before': VerbType.Precedes,
|
||||
'after': VerbType.Succeeds,
|
||||
'causes': VerbType.Causes,
|
||||
'needs': VerbType.Requires,
|
||||
'requires': VerbType.Requires,
|
||||
'makes': VerbType.Creates,
|
||||
'produces': VerbType.Creates,
|
||||
'changes': VerbType.Modifies,
|
||||
'updates': VerbType.Modifies,
|
||||
'owns': VerbType.Owns,
|
||||
'ownedBy': VerbType.BelongsTo, // Use BelongsTo for reverse ownership
|
||||
'uses': VerbType.Uses,
|
||||
'usedBy': VerbType.Uses // Same relationship, just interpret direction
|
||||
};
|
||||
const normalized = inputType.toLowerCase();
|
||||
return verbMappings[normalized] || VerbType.RelatedTo;
|
||||
}
|
||||
/**
|
||||
* Infer type from data structure
|
||||
*/
|
||||
inferTypeFromData(data) {
|
||||
if (!data || typeof data !== 'object')
|
||||
return null;
|
||||
const scores = new Map();
|
||||
const fields = Object.keys(data);
|
||||
const values = Object.values(data);
|
||||
// Calculate scores for each type based on field matches
|
||||
for (const [type, signature] of Object.entries(TYPE_SIGNATURES)) {
|
||||
let score = 0;
|
||||
// Check required fields
|
||||
if (signature.required.length > 0) {
|
||||
const hasRequired = signature.required.every(field => fields.includes(field));
|
||||
if (!hasRequired)
|
||||
continue;
|
||||
score += signature.weight * 2;
|
||||
}
|
||||
// Check indicator fields
|
||||
for (const field of fields) {
|
||||
if (signature.indicators.some(indicator => field.toLowerCase().includes(indicator.toLowerCase()))) {
|
||||
score += signature.weight;
|
||||
}
|
||||
}
|
||||
// Check value patterns
|
||||
for (const value of values) {
|
||||
if (typeof value === 'string' && signature.patterns.length > 0) {
|
||||
for (const pattern of signature.patterns) {
|
||||
if (pattern.test(value)) {
|
||||
score += signature.weight / 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (score > 0) {
|
||||
scores.set(type, score);
|
||||
}
|
||||
}
|
||||
// Return the type with highest score
|
||||
if (scores.size > 0) {
|
||||
const sorted = Array.from(scores.entries()).sort((a, b) => b[1] - a[1]);
|
||||
return sorted[0][0];
|
||||
}
|
||||
// Fallback inference based on data structure
|
||||
if (fields.includes('url') || fields.includes('href')) {
|
||||
return NounType.Document;
|
||||
}
|
||||
if (Array.isArray(data) || fields.includes('items') || fields.includes('elements')) {
|
||||
return NounType.Collection;
|
||||
}
|
||||
// Check if it looks like a process/workflow
|
||||
if (fields.includes('steps') || fields.includes('stages')) {
|
||||
return NounType.Process;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Get direct mapping for common aliases
|
||||
*/
|
||||
getDirectMapping(inputType) {
|
||||
const mappings = {
|
||||
// Specific mappings that aren't generic
|
||||
'company': NounType.Organization,
|
||||
'corp': NounType.Organization,
|
||||
'business': NounType.Organization,
|
||||
'employee': NounType.Person,
|
||||
'staff': NounType.Person,
|
||||
'customer': NounType.Person,
|
||||
'client': NounType.Person,
|
||||
'article': NounType.Document,
|
||||
'post': NounType.Document,
|
||||
'page': NounType.Document,
|
||||
'image': NounType.Media,
|
||||
'video': NounType.Media,
|
||||
'audio': NounType.Media,
|
||||
'photo': NounType.Media,
|
||||
'place': NounType.Location,
|
||||
'address': NounType.Location,
|
||||
'country': NounType.Location,
|
||||
'city': NounType.Location,
|
||||
'todo': NounType.Task,
|
||||
'job': NounType.Task,
|
||||
'work': NounType.Task,
|
||||
'meeting': NounType.Event,
|
||||
'appointment': NounType.Event,
|
||||
'conference': NounType.Event,
|
||||
'folder': NounType.Collection,
|
||||
'group': NounType.Collection,
|
||||
'list': NounType.Collection,
|
||||
'category': NounType.Collection
|
||||
};
|
||||
const normalized = inputType.toLowerCase();
|
||||
return mappings[normalized] || null;
|
||||
}
|
||||
/**
|
||||
* Check if a type is valid
|
||||
*/
|
||||
isValidNounType(type) {
|
||||
return Object.values(NounType).includes(type);
|
||||
}
|
||||
/**
|
||||
* Check if a verb type is valid
|
||||
*/
|
||||
isValidVerbType(type) {
|
||||
return Object.values(VerbType).includes(type);
|
||||
}
|
||||
/**
|
||||
* Get inference statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
...this.inferenceStats,
|
||||
cacheSize: this.typeCache.size,
|
||||
inferenceRate: this.inferenceStats.total > 0
|
||||
? (this.inferenceStats.inferred / this.inferenceStats.total)
|
||||
: 0
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Clear the type cache
|
||||
*/
|
||||
clearCache() {
|
||||
this.typeCache.clear();
|
||||
}
|
||||
}
|
||||
// Singleton instance
|
||||
export const typeMapper = new IntelligentTypeMapper();
|
||||
/**
|
||||
* Helper function for easy type mapping
|
||||
*/
|
||||
export function mapNounType(inputType, data) {
|
||||
return typeMapper.mapNounType(inputType, data);
|
||||
}
|
||||
/**
|
||||
* Helper function for verb mapping
|
||||
*/
|
||||
export function mapVerbType(inputType) {
|
||||
return typeMapper.mapVerbType(inputType);
|
||||
}
|
||||
//# sourceMappingURL=intelligentTypeMapper.js.map
|
||||
File diff suppressed because one or more lines are too long
43
.recovery-workspace/dist-backup-20250910-141917/utils/jsonProcessing.d.ts
vendored
Normal file
43
.recovery-workspace/dist-backup-20250910-141917/utils/jsonProcessing.d.ts
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Utility functions for processing JSON documents for vectorization and search
|
||||
*/
|
||||
/**
|
||||
* Extracts text from a JSON object for vectorization
|
||||
* This function recursively processes the JSON object and extracts text from all fields
|
||||
* It can also prioritize specific fields if provided
|
||||
*
|
||||
* @param jsonObject The JSON object to extract text from
|
||||
* @param options Configuration options for text extraction
|
||||
* @returns A string containing the extracted text
|
||||
*/
|
||||
export declare function extractTextFromJson(jsonObject: any, options?: {
|
||||
priorityFields?: string[];
|
||||
excludeFields?: string[];
|
||||
includeFieldNames?: boolean;
|
||||
maxDepth?: number;
|
||||
currentDepth?: number;
|
||||
fieldPath?: string[];
|
||||
}): string;
|
||||
/**
|
||||
* Prepares a JSON document for vectorization
|
||||
* This function extracts text from the JSON document and formats it for optimal vectorization
|
||||
*
|
||||
* @param jsonDocument The JSON document to prepare
|
||||
* @param options Configuration options for preparation
|
||||
* @returns A string ready for vectorization
|
||||
*/
|
||||
export declare function prepareJsonForVectorization(jsonDocument: any, options?: {
|
||||
priorityFields?: string[];
|
||||
excludeFields?: string[];
|
||||
includeFieldNames?: boolean;
|
||||
maxDepth?: number;
|
||||
}): string;
|
||||
/**
|
||||
* Extracts text from a specific field in a JSON document
|
||||
* This is useful for searching within specific fields
|
||||
*
|
||||
* @param jsonDocument The JSON document to extract from
|
||||
* @param fieldPath The path to the field (e.g., "user.name" or "addresses[0].city")
|
||||
* @returns The extracted text or empty string if field not found
|
||||
*/
|
||||
export declare function extractFieldFromJson(jsonDocument: any, fieldPath: string): string;
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* Utility functions for processing JSON documents for vectorization and search
|
||||
*/
|
||||
/**
|
||||
* Extracts text from a JSON object for vectorization
|
||||
* This function recursively processes the JSON object and extracts text from all fields
|
||||
* It can also prioritize specific fields if provided
|
||||
*
|
||||
* @param jsonObject The JSON object to extract text from
|
||||
* @param options Configuration options for text extraction
|
||||
* @returns A string containing the extracted text
|
||||
*/
|
||||
export function extractTextFromJson(jsonObject, options = {}) {
|
||||
// Set default options
|
||||
const { priorityFields = [], excludeFields = [], includeFieldNames = true, maxDepth = 5, currentDepth = 0, fieldPath = [] } = options;
|
||||
// If input is not an object or array, or we've reached max depth, return as string
|
||||
if (jsonObject === null ||
|
||||
jsonObject === undefined ||
|
||||
typeof jsonObject !== 'object' ||
|
||||
currentDepth >= maxDepth) {
|
||||
return String(jsonObject || '');
|
||||
}
|
||||
const extractedText = [];
|
||||
const priorityText = [];
|
||||
// Process arrays
|
||||
if (Array.isArray(jsonObject)) {
|
||||
for (let i = 0; i < jsonObject.length; i++) {
|
||||
const value = jsonObject[i];
|
||||
const newPath = [...fieldPath, i.toString()];
|
||||
// Recursively extract text from array items
|
||||
const itemText = extractTextFromJson(value, {
|
||||
priorityFields,
|
||||
excludeFields,
|
||||
includeFieldNames,
|
||||
maxDepth,
|
||||
currentDepth: currentDepth + 1,
|
||||
fieldPath: newPath
|
||||
});
|
||||
if (itemText) {
|
||||
extractedText.push(itemText);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process objects
|
||||
else {
|
||||
for (const [key, value] of Object.entries(jsonObject)) {
|
||||
// Skip excluded fields
|
||||
if (excludeFields.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
const newPath = [...fieldPath, key];
|
||||
const fullPath = newPath.join('.');
|
||||
// Check if this is a priority field
|
||||
const isPriority = priorityFields.some(field => {
|
||||
// Exact match
|
||||
if (field === key)
|
||||
return true;
|
||||
// Path match
|
||||
if (field === fullPath)
|
||||
return true;
|
||||
// Wildcard match (e.g., "user.*" matches "user.name", "user.email", etc.)
|
||||
if (field.endsWith('.*') && fullPath.startsWith(field.slice(0, -2)))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
// Get the field value as text
|
||||
let fieldText;
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
// Recursively extract text from nested objects
|
||||
fieldText = extractTextFromJson(value, {
|
||||
priorityFields,
|
||||
excludeFields,
|
||||
includeFieldNames,
|
||||
maxDepth,
|
||||
currentDepth: currentDepth + 1,
|
||||
fieldPath: newPath
|
||||
});
|
||||
}
|
||||
else {
|
||||
fieldText = String(value || '');
|
||||
}
|
||||
// Add field name if requested
|
||||
if (includeFieldNames && fieldText) {
|
||||
fieldText = `${key}: ${fieldText}`;
|
||||
}
|
||||
// Add to appropriate collection
|
||||
if (fieldText) {
|
||||
if (isPriority) {
|
||||
priorityText.push(fieldText);
|
||||
}
|
||||
else {
|
||||
extractedText.push(fieldText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Combine priority text (repeated for emphasis) and regular text
|
||||
return [...priorityText, ...priorityText, ...extractedText].join(' ');
|
||||
}
|
||||
/**
|
||||
* Prepares a JSON document for vectorization
|
||||
* This function extracts text from the JSON document and formats it for optimal vectorization
|
||||
*
|
||||
* @param jsonDocument The JSON document to prepare
|
||||
* @param options Configuration options for preparation
|
||||
* @returns A string ready for vectorization
|
||||
*/
|
||||
export function prepareJsonForVectorization(jsonDocument, options = {}) {
|
||||
// If input is a string, try to parse it as JSON
|
||||
let document = jsonDocument;
|
||||
if (typeof jsonDocument === 'string') {
|
||||
try {
|
||||
document = JSON.parse(jsonDocument);
|
||||
}
|
||||
catch (e) {
|
||||
// If parsing fails, treat it as a plain string
|
||||
return jsonDocument;
|
||||
}
|
||||
}
|
||||
// If not an object after parsing, return as is
|
||||
if (typeof document !== 'object' || document === null) {
|
||||
return String(document || '');
|
||||
}
|
||||
// Extract text from the document
|
||||
return extractTextFromJson(document, options);
|
||||
}
|
||||
/**
|
||||
* Extracts text from a specific field in a JSON document
|
||||
* This is useful for searching within specific fields
|
||||
*
|
||||
* @param jsonDocument The JSON document to extract from
|
||||
* @param fieldPath The path to the field (e.g., "user.name" or "addresses[0].city")
|
||||
* @returns The extracted text or empty string if field not found
|
||||
*/
|
||||
export function extractFieldFromJson(jsonDocument, fieldPath) {
|
||||
// If input is a string, try to parse it as JSON
|
||||
let document = jsonDocument;
|
||||
if (typeof jsonDocument === 'string') {
|
||||
try {
|
||||
document = JSON.parse(jsonDocument);
|
||||
}
|
||||
catch (e) {
|
||||
// If parsing fails, return empty string
|
||||
return '';
|
||||
}
|
||||
}
|
||||
// If not an object after parsing, return empty string
|
||||
if (typeof document !== 'object' || document === null) {
|
||||
return '';
|
||||
}
|
||||
// Parse the field path
|
||||
const parts = fieldPath.split('.');
|
||||
let current = document;
|
||||
// Navigate through the path
|
||||
for (const part of parts) {
|
||||
// Handle array indexing (e.g., "addresses[0]")
|
||||
const match = part.match(/^([^[]+)(?:\[(\d+)\])?$/);
|
||||
if (!match) {
|
||||
return '';
|
||||
}
|
||||
const [, key, indexStr] = match;
|
||||
// Move to the next level
|
||||
current = current[key];
|
||||
// If we have an array index, access that element
|
||||
if (indexStr !== undefined && Array.isArray(current)) {
|
||||
const index = parseInt(indexStr, 10);
|
||||
current = current[index];
|
||||
}
|
||||
// If we've reached a null or undefined value, return empty string
|
||||
if (current === null || current === undefined) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
// Convert the final value to string
|
||||
return typeof current === 'object'
|
||||
? JSON.stringify(current)
|
||||
: String(current);
|
||||
}
|
||||
//# sourceMappingURL=jsonProcessing.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"jsonProcessing.js","sourceRoot":"","sources":["../../src/utils/jsonProcessing.ts"],"names":[],"mappings":"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"}
|
||||
79
.recovery-workspace/dist-backup-20250910-141917/utils/logger.d.ts
vendored
Normal file
79
.recovery-workspace/dist-backup-20250910-141917/utils/logger.d.ts
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Centralized logging utility for Brainy
|
||||
* Provides configurable log levels and consistent logging across the codebase
|
||||
* Automatically reduces logging in production environments to minimize costs
|
||||
*/
|
||||
export declare enum LogLevel {
|
||||
ERROR = 0,
|
||||
WARN = 1,
|
||||
INFO = 2,
|
||||
DEBUG = 3,
|
||||
TRACE = 4
|
||||
}
|
||||
export interface LoggerConfig {
|
||||
level: LogLevel;
|
||||
modules?: {
|
||||
[moduleName: string]: LogLevel;
|
||||
};
|
||||
timestamps?: boolean;
|
||||
includeModule?: boolean;
|
||||
handler?: (level: LogLevel, module: string, message: string, ...args: any[]) => void;
|
||||
}
|
||||
declare class Logger {
|
||||
private static instance;
|
||||
private config;
|
||||
private constructor();
|
||||
private applyEnvironmentDefaults;
|
||||
static getInstance(): Logger;
|
||||
configure(config: Partial<LoggerConfig>): void;
|
||||
private shouldLog;
|
||||
private formatMessage;
|
||||
private log;
|
||||
error(module: string, message: string, ...args: any[]): void;
|
||||
warn(module: string, message: string, ...args: any[]): void;
|
||||
info(module: string, message: string, ...args: any[]): void;
|
||||
debug(module: string, message: string, ...args: any[]): void;
|
||||
trace(module: string, message: string, ...args: any[]): void;
|
||||
createModuleLogger(module: string): {
|
||||
error: (message: string, ...args: any[]) => void;
|
||||
warn: (message: string, ...args: any[]) => void;
|
||||
info: (message: string, ...args: any[]) => void;
|
||||
debug: (message: string, ...args: any[]) => void;
|
||||
trace: (message: string, ...args: any[]) => void;
|
||||
};
|
||||
}
|
||||
export declare const logger: Logger;
|
||||
export declare function createModuleLogger(module: string): {
|
||||
error: (message: string, ...args: any[]) => void;
|
||||
warn: (message: string, ...args: any[]) => void;
|
||||
info: (message: string, ...args: any[]) => void;
|
||||
debug: (message: string, ...args: any[]) => void;
|
||||
trace: (message: string, ...args: any[]) => void;
|
||||
};
|
||||
export declare function configureLogger(config: Partial<LoggerConfig>): void;
|
||||
/**
|
||||
* Smart console replacement that automatically reduces logging in production
|
||||
* Dramatically reduces Google Cloud Run logging costs
|
||||
*
|
||||
* Usage: Replace console.log with smartConsole.log, etc.
|
||||
*/
|
||||
export declare const smartConsole: {
|
||||
log: (message?: any, ...args: any[]) => void;
|
||||
info: (message?: any, ...args: any[]) => void;
|
||||
warn: (message?: any, ...args: any[]) => void;
|
||||
error: (message?: any, ...args: any[]) => void;
|
||||
debug: (message?: any, ...args: any[]) => void;
|
||||
trace: (message?: any, ...args: any[]) => void;
|
||||
};
|
||||
/**
|
||||
* Production-optimized logging functions
|
||||
* These only log in non-production environments or when explicitly enabled
|
||||
*/
|
||||
export declare const prodLog: {
|
||||
error: (message?: any, ...args: any[]) => void;
|
||||
warn: (message?: any, ...args: any[]) => void;
|
||||
info: (message?: any, ...args: any[]) => void;
|
||||
debug: (message?: any, ...args: any[]) => void;
|
||||
log: (message?: any, ...args: any[]) => void;
|
||||
};
|
||||
export {};
|
||||
217
.recovery-workspace/dist-backup-20250910-141917/utils/logger.js
Normal file
217
.recovery-workspace/dist-backup-20250910-141917/utils/logger.js
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/**
|
||||
* Centralized logging utility for Brainy
|
||||
* Provides configurable log levels and consistent logging across the codebase
|
||||
* Automatically reduces logging in production environments to minimize costs
|
||||
*/
|
||||
import { isProductionEnvironment, getLogLevel } from './environment.js';
|
||||
export var LogLevel;
|
||||
(function (LogLevel) {
|
||||
LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
|
||||
LogLevel[LogLevel["WARN"] = 1] = "WARN";
|
||||
LogLevel[LogLevel["INFO"] = 2] = "INFO";
|
||||
LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
|
||||
LogLevel[LogLevel["TRACE"] = 4] = "TRACE";
|
||||
})(LogLevel || (LogLevel = {}));
|
||||
class Logger {
|
||||
constructor() {
|
||||
this.config = {
|
||||
level: LogLevel.ERROR, // Default to ERROR in production for cost optimization
|
||||
timestamps: false, // Disable timestamps in production to reduce log size
|
||||
includeModule: true
|
||||
};
|
||||
// Auto-detect production environment and set appropriate defaults
|
||||
this.applyEnvironmentDefaults();
|
||||
// Set log level from environment variable if available (overrides auto-detection)
|
||||
const envLogLevel = process.env.BRAINY_LOG_LEVEL;
|
||||
if (envLogLevel) {
|
||||
const level = LogLevel[envLogLevel.toUpperCase()];
|
||||
if (level !== undefined) {
|
||||
this.config.level = level;
|
||||
}
|
||||
}
|
||||
// Parse module-specific log levels
|
||||
const moduleLogLevels = process.env.BRAINY_MODULE_LOG_LEVELS;
|
||||
if (moduleLogLevels) {
|
||||
try {
|
||||
this.config.modules = JSON.parse(moduleLogLevels);
|
||||
}
|
||||
catch (e) {
|
||||
// Ignore parsing errors
|
||||
}
|
||||
}
|
||||
}
|
||||
applyEnvironmentDefaults() {
|
||||
const envLogLevel = getLogLevel();
|
||||
// Convert environment log level to Logger LogLevel
|
||||
switch (envLogLevel) {
|
||||
case 'silent':
|
||||
this.config.level = -1; // Below ERROR to silence all logs
|
||||
break;
|
||||
case 'error':
|
||||
this.config.level = LogLevel.ERROR;
|
||||
this.config.timestamps = false; // Minimize log size in production
|
||||
break;
|
||||
case 'warn':
|
||||
this.config.level = LogLevel.WARN;
|
||||
this.config.timestamps = false;
|
||||
break;
|
||||
case 'info':
|
||||
this.config.level = LogLevel.INFO;
|
||||
this.config.timestamps = true;
|
||||
break;
|
||||
case 'verbose':
|
||||
this.config.level = LogLevel.DEBUG;
|
||||
this.config.timestamps = true;
|
||||
break;
|
||||
}
|
||||
// In production environments, be extra conservative to minimize costs
|
||||
if (isProductionEnvironment()) {
|
||||
this.config.level = Math.min(this.config.level, LogLevel.ERROR);
|
||||
this.config.timestamps = false;
|
||||
this.config.includeModule = false; // Reduce log size
|
||||
}
|
||||
}
|
||||
static getInstance() {
|
||||
if (!Logger.instance) {
|
||||
Logger.instance = new Logger();
|
||||
}
|
||||
return Logger.instance;
|
||||
}
|
||||
configure(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
}
|
||||
shouldLog(level, module) {
|
||||
// Check module-specific level first
|
||||
if (this.config.modules && this.config.modules[module] !== undefined) {
|
||||
return level <= this.config.modules[module];
|
||||
}
|
||||
// Otherwise use global level
|
||||
return level <= this.config.level;
|
||||
}
|
||||
formatMessage(level, module, message) {
|
||||
const parts = [];
|
||||
if (this.config.timestamps) {
|
||||
parts.push(`[${new Date().toISOString()}]`);
|
||||
}
|
||||
parts.push(`[${LogLevel[level]}]`);
|
||||
if (this.config.includeModule) {
|
||||
parts.push(`[${module}]`);
|
||||
}
|
||||
parts.push(message);
|
||||
return parts.join(' ');
|
||||
}
|
||||
log(level, module, message, ...args) {
|
||||
if (!this.shouldLog(level, module)) {
|
||||
return;
|
||||
}
|
||||
if (this.config.handler) {
|
||||
this.config.handler(level, module, message, ...args);
|
||||
return;
|
||||
}
|
||||
const formattedMessage = this.formatMessage(level, module, message);
|
||||
switch (level) {
|
||||
case LogLevel.ERROR:
|
||||
console.error(formattedMessage, ...args);
|
||||
break;
|
||||
case LogLevel.WARN:
|
||||
console.warn(formattedMessage, ...args);
|
||||
break;
|
||||
case LogLevel.INFO:
|
||||
console.info(formattedMessage, ...args);
|
||||
break;
|
||||
case LogLevel.DEBUG:
|
||||
case LogLevel.TRACE:
|
||||
console.log(formattedMessage, ...args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
error(module, message, ...args) {
|
||||
this.log(LogLevel.ERROR, module, message, ...args);
|
||||
}
|
||||
warn(module, message, ...args) {
|
||||
this.log(LogLevel.WARN, module, message, ...args);
|
||||
}
|
||||
info(module, message, ...args) {
|
||||
this.log(LogLevel.INFO, module, message, ...args);
|
||||
}
|
||||
debug(module, message, ...args) {
|
||||
this.log(LogLevel.DEBUG, module, message, ...args);
|
||||
}
|
||||
trace(module, message, ...args) {
|
||||
this.log(LogLevel.TRACE, module, message, ...args);
|
||||
}
|
||||
// Create a module-specific logger
|
||||
createModuleLogger(module) {
|
||||
return {
|
||||
error: (message, ...args) => this.error(module, message, ...args),
|
||||
warn: (message, ...args) => this.warn(module, message, ...args),
|
||||
info: (message, ...args) => this.info(module, message, ...args),
|
||||
debug: (message, ...args) => this.debug(module, message, ...args),
|
||||
trace: (message, ...args) => this.trace(module, message, ...args)
|
||||
};
|
||||
}
|
||||
}
|
||||
// Export singleton instance
|
||||
export const logger = Logger.getInstance();
|
||||
// Export convenience function for creating module loggers
|
||||
export function createModuleLogger(module) {
|
||||
return logger.createModuleLogger(module);
|
||||
}
|
||||
// Export function to configure logger
|
||||
export function configureLogger(config) {
|
||||
logger.configure(config);
|
||||
}
|
||||
/**
|
||||
* Smart console replacement that automatically reduces logging in production
|
||||
* Dramatically reduces Google Cloud Run logging costs
|
||||
*
|
||||
* Usage: Replace console.log with smartConsole.log, etc.
|
||||
*/
|
||||
export const smartConsole = {
|
||||
log: (message, ...args) => {
|
||||
if (logger['shouldLog'](LogLevel.INFO, 'console')) {
|
||||
console.log(message, ...args);
|
||||
}
|
||||
},
|
||||
info: (message, ...args) => {
|
||||
if (logger['shouldLog'](LogLevel.INFO, 'console')) {
|
||||
console.info(message, ...args);
|
||||
}
|
||||
},
|
||||
warn: (message, ...args) => {
|
||||
if (logger['shouldLog'](LogLevel.WARN, 'console')) {
|
||||
console.warn(message, ...args);
|
||||
}
|
||||
},
|
||||
error: (message, ...args) => {
|
||||
if (logger['shouldLog'](LogLevel.ERROR, 'console')) {
|
||||
console.error(message, ...args);
|
||||
}
|
||||
},
|
||||
debug: (message, ...args) => {
|
||||
if (logger['shouldLog'](LogLevel.DEBUG, 'console')) {
|
||||
console.debug(message, ...args);
|
||||
}
|
||||
},
|
||||
trace: (message, ...args) => {
|
||||
if (logger['shouldLog'](LogLevel.TRACE, 'console')) {
|
||||
console.trace(message, ...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Production-optimized logging functions
|
||||
* These only log in non-production environments or when explicitly enabled
|
||||
*/
|
||||
export const prodLog = {
|
||||
// Only log errors in production (always visible)
|
||||
error: (message, ...args) => {
|
||||
console.error(message, ...args);
|
||||
},
|
||||
// These are suppressed in production unless BRAINY_LOG_LEVEL is set
|
||||
warn: (message, ...args) => smartConsole.warn(message, ...args),
|
||||
info: (message, ...args) => smartConsole.info(message, ...args),
|
||||
debug: (message, ...args) => smartConsole.debug(message, ...args),
|
||||
log: (message, ...args) => smartConsole.log(message, ...args)
|
||||
};
|
||||
//# sourceMappingURL=logger.js.map
|
||||
File diff suppressed because one or more lines are too long
93
.recovery-workspace/dist-backup-20250910-141917/utils/metadataFilter.d.ts
vendored
Normal file
93
.recovery-workspace/dist-backup-20250910-141917/utils/metadataFilter.d.ts
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Smart metadata filtering for vector search
|
||||
* Filters DURING search to ensure relevant results
|
||||
* Simple API that just works without configuration
|
||||
*/
|
||||
import { SearchResult, HNSWNoun } from '../coreTypes.js';
|
||||
/**
|
||||
* Brainy Field Operators (BFO) - Our own field query system
|
||||
* Designed for performance, clarity, and patent independence
|
||||
*/
|
||||
export interface BrainyFieldOperators {
|
||||
equals?: any;
|
||||
notEquals?: any;
|
||||
is?: any;
|
||||
isNot?: any;
|
||||
greaterThan?: any;
|
||||
greaterEqual?: any;
|
||||
lessThan?: any;
|
||||
lessEqual?: any;
|
||||
between?: [any, any];
|
||||
oneOf?: any[];
|
||||
noneOf?: any[];
|
||||
contains?: any;
|
||||
excludes?: any;
|
||||
hasAll?: any[];
|
||||
length?: number;
|
||||
exists?: boolean;
|
||||
missing?: boolean;
|
||||
matches?: string | RegExp;
|
||||
startsWith?: string;
|
||||
endsWith?: string;
|
||||
allOf?: MetadataFilter[];
|
||||
anyOf?: MetadataFilter[];
|
||||
not?: MetadataFilter;
|
||||
eq?: any;
|
||||
ne?: any;
|
||||
gt?: any;
|
||||
gte?: any;
|
||||
lt?: any;
|
||||
lte?: any;
|
||||
}
|
||||
/**
|
||||
* Metadata filter definition
|
||||
*/
|
||||
export interface MetadataFilter {
|
||||
[key: string]: any | BrainyFieldOperators;
|
||||
}
|
||||
/**
|
||||
* Options for metadata filtering
|
||||
*/
|
||||
export interface MetadataFilterOptions {
|
||||
metadata?: MetadataFilter;
|
||||
scoring?: {
|
||||
vectorWeight?: number;
|
||||
metadataWeight?: number;
|
||||
metadataBoosts?: Record<string, number | ((value: any, query: any) => number)>;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Check if metadata matches the filter
|
||||
*/
|
||||
export declare function matchesMetadataFilter(metadata: any, filter: MetadataFilter): boolean;
|
||||
/**
|
||||
* Calculate metadata boost score
|
||||
*/
|
||||
export declare function calculateMetadataScore(metadata: any, filter: MetadataFilter, scoring?: MetadataFilterOptions['scoring']): number;
|
||||
/**
|
||||
* Apply compound scoring to search results
|
||||
*/
|
||||
export declare function applyCompoundScoring<T>(results: SearchResult<T>[], filter: MetadataFilter, scoring?: MetadataFilterOptions['scoring']): SearchResult<T>[];
|
||||
/**
|
||||
* Filter search results by metadata
|
||||
*/
|
||||
export declare function filterSearchResultsByMetadata<T>(results: SearchResult<T>[], filter: MetadataFilter): SearchResult<T>[];
|
||||
/**
|
||||
* Filter nouns by metadata before search
|
||||
*/
|
||||
export declare function filterNounsByMetadata(nouns: HNSWNoun[], filter: MetadataFilter): HNSWNoun[];
|
||||
/**
|
||||
* Aggregate search results for faceted search
|
||||
*/
|
||||
export interface FacetConfig {
|
||||
field: string;
|
||||
limit?: number;
|
||||
}
|
||||
export interface FacetResult {
|
||||
[value: string]: number;
|
||||
}
|
||||
export interface AggregationResult<T> {
|
||||
results: SearchResult<T>[];
|
||||
facets: Record<string, FacetResult>;
|
||||
}
|
||||
export declare function aggregateSearchResults<T>(results: SearchResult<T>[], facets: Record<string, FacetConfig>): AggregationResult<T>;
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
/**
|
||||
* Smart metadata filtering for vector search
|
||||
* Filters DURING search to ensure relevant results
|
||||
* Simple API that just works without configuration
|
||||
*/
|
||||
/**
|
||||
* Check if a value matches a query with operators
|
||||
*/
|
||||
function matchesQuery(value, query) {
|
||||
// Direct equality check
|
||||
if (typeof query !== 'object' || query === null || Array.isArray(query)) {
|
||||
return value === query;
|
||||
}
|
||||
// Check for Brainy Field Operators (BFO)
|
||||
for (const [op, operand] of Object.entries(query)) {
|
||||
switch (op) {
|
||||
// Equality operators
|
||||
case 'equals':
|
||||
case 'is':
|
||||
case 'eq':
|
||||
if (value !== operand)
|
||||
return false;
|
||||
break;
|
||||
case 'notEquals':
|
||||
case 'isNot':
|
||||
case 'ne':
|
||||
// Special handling: if value is undefined and operand is not undefined,
|
||||
// they are not equal (so the condition passes)
|
||||
// This ensures items without a 'deleted' field match 'deleted !== true'
|
||||
if (value === operand)
|
||||
return false;
|
||||
// If value is undefined and operand is not, they're not equal (pass)
|
||||
// If both are undefined, they're equal (fail, handled above)
|
||||
break;
|
||||
// Comparison operators
|
||||
case 'greaterThan':
|
||||
case 'gt':
|
||||
if (typeof value !== 'number' || typeof operand !== 'number' || !(value > operand))
|
||||
return false;
|
||||
break;
|
||||
case 'greaterEqual':
|
||||
case 'gte':
|
||||
if (typeof value !== 'number' || typeof operand !== 'number' || !(value >= operand))
|
||||
return false;
|
||||
break;
|
||||
case 'lessThan':
|
||||
case 'lt':
|
||||
if (typeof value !== 'number' || typeof operand !== 'number' || !(value < operand))
|
||||
return false;
|
||||
break;
|
||||
case 'lessEqual':
|
||||
case 'lte':
|
||||
if (typeof value !== 'number' || typeof operand !== 'number' || !(value <= operand))
|
||||
return false;
|
||||
break;
|
||||
case 'between':
|
||||
if (!Array.isArray(operand) || operand.length !== 2)
|
||||
return false;
|
||||
if (typeof value !== 'number' || !(value >= operand[0] && value <= operand[1]))
|
||||
return false;
|
||||
break;
|
||||
// Array/Set operators
|
||||
case 'oneOf':
|
||||
if (!Array.isArray(operand) || !operand.includes(value))
|
||||
return false;
|
||||
break;
|
||||
case 'noneOf':
|
||||
if (!Array.isArray(operand) || operand.includes(value))
|
||||
return false;
|
||||
break;
|
||||
case 'contains':
|
||||
if (!Array.isArray(value) || !value.includes(operand))
|
||||
return false;
|
||||
break;
|
||||
case 'excludes':
|
||||
if (!Array.isArray(value) || value.includes(operand))
|
||||
return false;
|
||||
break;
|
||||
case 'hasAll':
|
||||
if (!Array.isArray(value) || !Array.isArray(operand))
|
||||
return false;
|
||||
for (const item of operand) {
|
||||
if (!value.includes(item))
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'length':
|
||||
if (!Array.isArray(value) || value.length !== operand)
|
||||
return false;
|
||||
break;
|
||||
// Existence operators
|
||||
case 'exists':
|
||||
if ((value !== undefined) !== operand)
|
||||
return false;
|
||||
break;
|
||||
case 'missing':
|
||||
if ((value === undefined) !== operand)
|
||||
return false;
|
||||
break;
|
||||
// Pattern operators
|
||||
case 'matches':
|
||||
const regex = typeof operand === 'string' ? new RegExp(operand) : operand;
|
||||
if (!(regex instanceof RegExp) || !regex.test(String(value)))
|
||||
return false;
|
||||
break;
|
||||
case 'startsWith':
|
||||
if (typeof value !== 'string' || !value.startsWith(String(operand)))
|
||||
return false;
|
||||
break;
|
||||
case 'endsWith':
|
||||
if (typeof value !== 'string' || !value.endsWith(String(operand)))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
// Unknown operator, treat as field name
|
||||
if (!matchesFieldQuery(value, op, operand))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Check if a field matches a query
|
||||
*/
|
||||
function matchesFieldQuery(obj, field, query) {
|
||||
const value = getNestedValue(obj, field);
|
||||
return matchesQuery(value, query);
|
||||
}
|
||||
/**
|
||||
* Get nested value from object using dot notation
|
||||
*/
|
||||
function getNestedValue(obj, path) {
|
||||
const parts = path.split('.');
|
||||
let current = obj;
|
||||
for (const part of parts) {
|
||||
if (current === null || current === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
/**
|
||||
* Check if metadata matches the filter
|
||||
*/
|
||||
export function matchesMetadataFilter(metadata, filter) {
|
||||
if (!filter || Object.keys(filter).length === 0) {
|
||||
return true;
|
||||
}
|
||||
for (const [key, query] of Object.entries(filter)) {
|
||||
// Handle logical operators
|
||||
if (key === 'allOf') {
|
||||
if (!Array.isArray(query))
|
||||
return false;
|
||||
for (const subFilter of query) {
|
||||
if (!matchesMetadataFilter(metadata, subFilter))
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === 'anyOf') {
|
||||
if (!Array.isArray(query))
|
||||
return false;
|
||||
let matched = false;
|
||||
for (const subFilter of query) {
|
||||
if (matchesMetadataFilter(metadata, subFilter)) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
if (key === 'not') {
|
||||
if (matchesMetadataFilter(metadata, query))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
// Handle field queries
|
||||
const value = getNestedValue(metadata, key);
|
||||
if (!matchesQuery(value, query)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Calculate metadata boost score
|
||||
*/
|
||||
export function calculateMetadataScore(metadata, filter, scoring) {
|
||||
if (!scoring || !scoring.metadataBoosts) {
|
||||
return 0;
|
||||
}
|
||||
let score = 0;
|
||||
for (const [field, boost] of Object.entries(scoring.metadataBoosts)) {
|
||||
const value = getNestedValue(metadata, field);
|
||||
if (typeof boost === 'function') {
|
||||
score += boost(value, filter);
|
||||
}
|
||||
else if (value !== undefined) {
|
||||
// Check if the field matches the filter
|
||||
const fieldFilter = filter[field];
|
||||
if (fieldFilter && matchesQuery(value, fieldFilter)) {
|
||||
score += boost;
|
||||
}
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
/**
|
||||
* Apply compound scoring to search results
|
||||
*/
|
||||
export function applyCompoundScoring(results, filter, scoring) {
|
||||
if (!scoring || (!scoring.vectorWeight && !scoring.metadataWeight)) {
|
||||
return results;
|
||||
}
|
||||
const vectorWeight = scoring.vectorWeight ?? 1.0;
|
||||
const metadataWeight = scoring.metadataWeight ?? 0.0;
|
||||
return results.map(result => {
|
||||
const metadataScore = calculateMetadataScore(result.metadata, filter, scoring);
|
||||
const combinedScore = (result.score * vectorWeight) + (metadataScore * metadataWeight);
|
||||
return {
|
||||
...result,
|
||||
score: combinedScore
|
||||
};
|
||||
}).sort((a, b) => b.score - a.score); // Re-sort by combined score
|
||||
}
|
||||
/**
|
||||
* Filter search results by metadata
|
||||
*/
|
||||
export function filterSearchResultsByMetadata(results, filter) {
|
||||
if (!filter || Object.keys(filter).length === 0) {
|
||||
return results;
|
||||
}
|
||||
return results.filter(result => matchesMetadataFilter(result.metadata, filter));
|
||||
}
|
||||
/**
|
||||
* Filter nouns by metadata before search
|
||||
*/
|
||||
export function filterNounsByMetadata(nouns, filter) {
|
||||
if (!filter || Object.keys(filter).length === 0) {
|
||||
return nouns;
|
||||
}
|
||||
return nouns.filter(noun => matchesMetadataFilter(noun.metadata, filter));
|
||||
}
|
||||
export function aggregateSearchResults(results, facets) {
|
||||
const facetResults = {};
|
||||
for (const [facetName, config] of Object.entries(facets)) {
|
||||
const counts = {};
|
||||
for (const result of results) {
|
||||
const value = getNestedValue(result.metadata, config.field);
|
||||
if (value !== undefined) {
|
||||
const key = String(value);
|
||||
counts[key] = (counts[key] || 0) + 1;
|
||||
}
|
||||
}
|
||||
// Sort by count and apply limit
|
||||
const sorted = Object.entries(counts)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, config.limit || 10);
|
||||
facetResults[facetName] = Object.fromEntries(sorted);
|
||||
}
|
||||
return {
|
||||
results,
|
||||
facets: facetResults
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=metadataFilter.js.map
|
||||
File diff suppressed because one or more lines are too long
189
.recovery-workspace/dist-backup-20250910-141917/utils/metadataIndex.d.ts
vendored
Normal file
189
.recovery-workspace/dist-backup-20250910-141917/utils/metadataIndex.d.ts
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/**
|
||||
* Metadata Index System
|
||||
* Maintains inverted indexes for fast metadata filtering
|
||||
* Automatically updates indexes when data changes
|
||||
*/
|
||||
import { StorageAdapter } from '../coreTypes.js';
|
||||
export interface MetadataIndexEntry {
|
||||
field: string;
|
||||
value: string | number | boolean;
|
||||
ids: Set<string>;
|
||||
lastUpdated: number;
|
||||
}
|
||||
export interface FieldIndexData {
|
||||
values: Record<string, number>;
|
||||
lastUpdated: number;
|
||||
}
|
||||
export interface MetadataIndexStats {
|
||||
totalEntries: number;
|
||||
totalIds: number;
|
||||
fieldsIndexed: string[];
|
||||
lastRebuild: number;
|
||||
indexSize: number;
|
||||
}
|
||||
export interface MetadataIndexConfig {
|
||||
maxIndexSize?: number;
|
||||
rebuildThreshold?: number;
|
||||
autoOptimize?: boolean;
|
||||
indexedFields?: string[];
|
||||
excludeFields?: string[];
|
||||
}
|
||||
export declare class MetadataIndexManager {
|
||||
private storage;
|
||||
private config;
|
||||
private indexCache;
|
||||
private dirtyEntries;
|
||||
private isRebuilding;
|
||||
private metadataCache;
|
||||
private fieldIndexes;
|
||||
private dirtyFields;
|
||||
private lastFlushTime;
|
||||
private autoFlushThreshold;
|
||||
private sortedIndices;
|
||||
private numericFields;
|
||||
private unifiedCache;
|
||||
constructor(storage: StorageAdapter, config?: MetadataIndexConfig);
|
||||
/**
|
||||
* Get index key for field and value
|
||||
*/
|
||||
private getIndexKey;
|
||||
/**
|
||||
* Ensure sorted index exists for a field (for range queries)
|
||||
*/
|
||||
private ensureSortedIndex;
|
||||
/**
|
||||
* Build sorted index for a field from hash index
|
||||
*/
|
||||
private buildSortedIndex;
|
||||
/**
|
||||
* Binary search for range start (inclusive or exclusive)
|
||||
*/
|
||||
private binarySearchStart;
|
||||
/**
|
||||
* Binary search for range end (inclusive or exclusive)
|
||||
*/
|
||||
private binarySearchEnd;
|
||||
/**
|
||||
* Get IDs matching a range query
|
||||
*/
|
||||
private getIdsForRange;
|
||||
/**
|
||||
* Generate field index filename for filter discovery
|
||||
*/
|
||||
private getFieldIndexFilename;
|
||||
/**
|
||||
* Generate value chunk filename for scalable storage
|
||||
*/
|
||||
private getValueChunkFilename;
|
||||
/**
|
||||
* Make a value safe for use in filenames
|
||||
*/
|
||||
private makeSafeFilename;
|
||||
/**
|
||||
* Normalize value for consistent indexing
|
||||
*/
|
||||
private normalizeValue;
|
||||
/**
|
||||
* Create a short hash for long values to avoid filesystem filename limits
|
||||
*/
|
||||
private hashValue;
|
||||
/**
|
||||
* Check if field should be indexed
|
||||
*/
|
||||
private shouldIndexField;
|
||||
/**
|
||||
* Extract indexable field-value pairs from metadata
|
||||
*/
|
||||
private extractIndexableFields;
|
||||
/**
|
||||
* Add item to metadata indexes
|
||||
*/
|
||||
addToIndex(id: string, metadata: any, skipFlush?: boolean): Promise<void>;
|
||||
/**
|
||||
* Update field index with value count
|
||||
*/
|
||||
private updateFieldIndex;
|
||||
/**
|
||||
* Remove item from metadata indexes
|
||||
*/
|
||||
removeFromIndex(id: string, metadata?: any): Promise<void>;
|
||||
/**
|
||||
* Get all IDs in the index
|
||||
*/
|
||||
getAllIds(): Promise<string[]>;
|
||||
/**
|
||||
* Get IDs for a specific field-value combination with caching
|
||||
*/
|
||||
getIds(field: string, value: any): Promise<string[]>;
|
||||
/**
|
||||
* Get all available values for a field (for filter discovery)
|
||||
*/
|
||||
getFilterValues(field: string): Promise<string[]>;
|
||||
/**
|
||||
* Get all indexed fields (for filter discovery)
|
||||
*/
|
||||
getFilterFields(): Promise<string[]>;
|
||||
/**
|
||||
* Convert Brainy Field Operator filter to simple field-value criteria for indexing
|
||||
*/
|
||||
private convertFilterToCriteria;
|
||||
/**
|
||||
* Get IDs matching Brainy Field Operator metadata filter using indexes where possible
|
||||
*/
|
||||
getIdsForFilter(filter: any): Promise<string[]>;
|
||||
/**
|
||||
* DEPRECATED - Old implementation for backward compatibility
|
||||
*/
|
||||
private getIdsForFilterOld;
|
||||
/**
|
||||
* Get IDs matching multiple criteria (intersection) - LEGACY METHOD
|
||||
* @deprecated Use getIdsForFilter instead
|
||||
*/
|
||||
getIdsForCriteria(criteria: Record<string, any>): Promise<string[]>;
|
||||
/**
|
||||
* Flush dirty entries to storage (non-blocking version)
|
||||
*/
|
||||
flush(): Promise<void>;
|
||||
/**
|
||||
* Yield control back to the Node.js event loop
|
||||
* Prevents blocking during long-running operations
|
||||
*/
|
||||
private yieldToEventLoop;
|
||||
/**
|
||||
* Load field index from storage
|
||||
*/
|
||||
private loadFieldIndex;
|
||||
/**
|
||||
* Save field index to storage
|
||||
*/
|
||||
private saveFieldIndex;
|
||||
/**
|
||||
* Save sorted index to storage for range queries
|
||||
*/
|
||||
private saveSortedIndex;
|
||||
/**
|
||||
* Load sorted index from storage
|
||||
*/
|
||||
private loadSortedIndex;
|
||||
/**
|
||||
* Get index statistics
|
||||
*/
|
||||
getStats(): Promise<MetadataIndexStats>;
|
||||
/**
|
||||
* Rebuild entire index from scratch using pagination
|
||||
* Non-blocking version that yields control back to event loop
|
||||
*/
|
||||
rebuild(): Promise<void>;
|
||||
/**
|
||||
* Load index entry from storage using safe filenames
|
||||
*/
|
||||
private loadIndexEntry;
|
||||
/**
|
||||
* Save index entry to storage using safe filenames
|
||||
*/
|
||||
private saveIndexEntry;
|
||||
/**
|
||||
* Delete index entry from storage using safe filenames
|
||||
*/
|
||||
private deleteIndexEntry;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
60
.recovery-workspace/dist-backup-20250910-141917/utils/metadataIndexCache.d.ts
vendored
Normal file
60
.recovery-workspace/dist-backup-20250910-141917/utils/metadataIndexCache.d.ts
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* MetadataIndexCache - Caches metadata index data for improved performance
|
||||
* Reuses the same pattern as SearchCache for consistency
|
||||
*/
|
||||
export interface MetadataCacheEntry {
|
||||
data: any;
|
||||
timestamp: number;
|
||||
hits: number;
|
||||
}
|
||||
export interface MetadataIndexCacheConfig {
|
||||
maxAge?: number;
|
||||
maxSize?: number;
|
||||
enabled?: boolean;
|
||||
hitCountWeight?: number;
|
||||
}
|
||||
export declare class MetadataIndexCache {
|
||||
private cache;
|
||||
private maxAge;
|
||||
private maxSize;
|
||||
private enabled;
|
||||
private hitCountWeight;
|
||||
private hits;
|
||||
private misses;
|
||||
private evictions;
|
||||
constructor(config?: MetadataIndexCacheConfig);
|
||||
/**
|
||||
* Get cached entry
|
||||
*/
|
||||
get(key: string): any | undefined;
|
||||
/**
|
||||
* Set cache entry
|
||||
*/
|
||||
set(key: string, data: any): void;
|
||||
/**
|
||||
* Evict least valuable entry based on age and hit count
|
||||
*/
|
||||
private evictLeastValuable;
|
||||
/**
|
||||
* Invalidate cache entries matching a pattern
|
||||
*/
|
||||
invalidatePattern(pattern: string): void;
|
||||
/**
|
||||
* Clear all cache entries
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats(): {
|
||||
size: number;
|
||||
hits: number;
|
||||
misses: number;
|
||||
hitRate: number;
|
||||
evictions: number;
|
||||
};
|
||||
/**
|
||||
* Get estimated memory usage
|
||||
*/
|
||||
getMemoryUsage(): number;
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* MetadataIndexCache - Caches metadata index data for improved performance
|
||||
* Reuses the same pattern as SearchCache for consistency
|
||||
*/
|
||||
export class MetadataIndexCache {
|
||||
constructor(config = {}) {
|
||||
this.cache = new Map();
|
||||
// Cache statistics
|
||||
this.hits = 0;
|
||||
this.misses = 0;
|
||||
this.evictions = 0;
|
||||
this.maxAge = config.maxAge ?? 5 * 60 * 1000; // 5 minutes
|
||||
this.maxSize = config.maxSize ?? 500; // More entries than SearchCache since indexes are smaller
|
||||
this.enabled = config.enabled ?? true;
|
||||
this.hitCountWeight = config.hitCountWeight ?? 0.3;
|
||||
}
|
||||
/**
|
||||
* Get cached entry
|
||||
*/
|
||||
get(key) {
|
||||
if (!this.enabled)
|
||||
return undefined;
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) {
|
||||
this.misses++;
|
||||
return undefined;
|
||||
}
|
||||
// Check if entry is expired
|
||||
if (Date.now() - entry.timestamp > this.maxAge) {
|
||||
this.cache.delete(key);
|
||||
this.misses++;
|
||||
return undefined;
|
||||
}
|
||||
// Update hit count
|
||||
entry.hits++;
|
||||
this.hits++;
|
||||
return entry.data;
|
||||
}
|
||||
/**
|
||||
* Set cache entry
|
||||
*/
|
||||
set(key, data) {
|
||||
if (!this.enabled)
|
||||
return;
|
||||
// Evict entries if at max size
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
this.evictLeastValuable();
|
||||
}
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
hits: 0
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Evict least valuable entry based on age and hit count
|
||||
*/
|
||||
evictLeastValuable() {
|
||||
let leastValuableKey = null;
|
||||
let lowestScore = Infinity;
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
const age = Date.now() - entry.timestamp;
|
||||
const ageScore = age / this.maxAge;
|
||||
const hitScore = entry.hits * this.hitCountWeight;
|
||||
const score = hitScore - ageScore;
|
||||
if (score < lowestScore) {
|
||||
lowestScore = score;
|
||||
leastValuableKey = key;
|
||||
}
|
||||
}
|
||||
if (leastValuableKey) {
|
||||
this.cache.delete(leastValuableKey);
|
||||
this.evictions++;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invalidate cache entries matching a pattern
|
||||
*/
|
||||
invalidatePattern(pattern) {
|
||||
const keysToDelete = [];
|
||||
for (const key of this.cache.keys()) {
|
||||
if (key.includes(pattern)) {
|
||||
keysToDelete.push(key);
|
||||
}
|
||||
}
|
||||
keysToDelete.forEach(key => this.cache.delete(key));
|
||||
}
|
||||
/**
|
||||
* Clear all cache entries
|
||||
*/
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
size: this.cache.size,
|
||||
hits: this.hits,
|
||||
misses: this.misses,
|
||||
hitRate: this.hits / (this.hits + this.misses) || 0,
|
||||
evictions: this.evictions
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get estimated memory usage
|
||||
*/
|
||||
getMemoryUsage() {
|
||||
// Rough estimate: 100 bytes per entry + data size
|
||||
let totalSize = 0;
|
||||
for (const entry of this.cache.values()) {
|
||||
totalSize += 100; // Base overhead
|
||||
totalSize += JSON.stringify(entry.data).length * 2; // Unicode chars
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=metadataIndexCache.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"metadataIndexCache.js","sourceRoot":"","sources":["../../src/utils/metadataIndexCache.ts"],"names":[],"mappings":"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"}
|
||||
113
.recovery-workspace/dist-backup-20250910-141917/utils/metadataNamespace.d.ts
vendored
Normal file
113
.recovery-workspace/dist-backup-20250910-141917/utils/metadataNamespace.d.ts
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Clean Metadata Architecture for Brainy 2.2
|
||||
* No backward compatibility - doing it RIGHT from the start!
|
||||
*/
|
||||
export declare const BRAINY_NS: "_brainy";
|
||||
export declare const AUG_NS: "_augmentations";
|
||||
export declare const AUDIT_NS: "_audit";
|
||||
export declare const DELETED_FIELD: "_brainy.deleted";
|
||||
export declare const INDEXED_FIELD: "_brainy.indexed";
|
||||
export declare const VERSION_FIELD: "_brainy.version";
|
||||
/**
|
||||
* Internal Brainy metadata structure
|
||||
* These fields are ALWAYS present and indexed for O(1) access
|
||||
*/
|
||||
export interface BrainyInternalMetadata {
|
||||
deleted: boolean;
|
||||
indexed: boolean;
|
||||
version: number;
|
||||
created: number;
|
||||
updated: number;
|
||||
partition?: number;
|
||||
domain?: string;
|
||||
priority?: number;
|
||||
ttl?: number;
|
||||
}
|
||||
/**
|
||||
* Complete metadata structure with namespaces
|
||||
*/
|
||||
export interface NamespacedMetadata<T = any> {
|
||||
[key: string]: any;
|
||||
[BRAINY_NS]: BrainyInternalMetadata;
|
||||
[AUG_NS]?: {
|
||||
[augmentationName: string]: any;
|
||||
};
|
||||
[AUDIT_NS]?: Array<{
|
||||
timestamp: number;
|
||||
augmentation: string;
|
||||
field: string;
|
||||
oldValue: any;
|
||||
newValue: any;
|
||||
}>;
|
||||
}
|
||||
/**
|
||||
* Create properly namespaced metadata
|
||||
* This is called for EVERY noun/verb creation
|
||||
*/
|
||||
export declare function createNamespacedMetadata<T = any>(userMetadata?: T): NamespacedMetadata<T>;
|
||||
/**
|
||||
* Update metadata while preserving namespaces
|
||||
*/
|
||||
export declare function updateNamespacedMetadata<T = any>(existing: NamespacedMetadata<T>, updates: Partial<T>): NamespacedMetadata<T>;
|
||||
/**
|
||||
* Soft delete a noun (O(1) operation)
|
||||
*/
|
||||
export declare function markDeleted<T = any>(metadata: NamespacedMetadata<T>): NamespacedMetadata<T>;
|
||||
/**
|
||||
* Restore a soft-deleted noun (O(1) operation)
|
||||
*/
|
||||
export declare function markRestored<T = any>(metadata: NamespacedMetadata<T>): NamespacedMetadata<T>;
|
||||
/**
|
||||
* Check if a noun is deleted (O(1) check)
|
||||
*/
|
||||
export declare function isDeleted<T = any>(metadata: NamespacedMetadata<T>): boolean;
|
||||
/**
|
||||
* Get user metadata without internal fields
|
||||
* Used by augmentations to get clean user data
|
||||
*/
|
||||
export declare function getUserMetadata<T = any>(metadata: NamespacedMetadata<T>): T;
|
||||
/**
|
||||
* Set augmentation data in isolated namespace
|
||||
*/
|
||||
export declare function setAugmentationData<T = any>(metadata: NamespacedMetadata<T>, augmentationName: string, data: any): NamespacedMetadata<T>;
|
||||
/**
|
||||
* Add audit entry for tracking
|
||||
*/
|
||||
export declare function addAuditEntry<T = any>(metadata: NamespacedMetadata<T>, entry: {
|
||||
augmentation: string;
|
||||
field: string;
|
||||
oldValue: any;
|
||||
newValue: any;
|
||||
}): NamespacedMetadata<T>;
|
||||
/**
|
||||
* INDEXING EXPLANATION:
|
||||
*
|
||||
* The MetadataIndex flattens nested objects into dot-notation keys:
|
||||
*
|
||||
* Input metadata:
|
||||
* {
|
||||
* name: "Django",
|
||||
* _brainy: {
|
||||
* deleted: false,
|
||||
* indexed: true
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Creates index entries:
|
||||
* - "name" -> "django" -> Set([id1, id2...])
|
||||
* - "_brainy.deleted" -> "false" -> Set([id1, id2...]) // O(1) lookup!
|
||||
* - "_brainy.indexed" -> "true" -> Set([id1, id2...])
|
||||
*
|
||||
* Query: { "_brainy.deleted": false }
|
||||
* Lookup: index["_brainy.deleted"]["false"] -> Set of IDs in O(1)
|
||||
*
|
||||
* This is why namespacing doesn't hurt performance - it's all flattened!
|
||||
*/
|
||||
/**
|
||||
* Fields that should ALWAYS be indexed for O(1) access
|
||||
*/
|
||||
export declare const ALWAYS_INDEXED_FIELDS: ("_brainy.deleted" | "_brainy.indexed" | "_brainy.version")[];
|
||||
/**
|
||||
* Fields that should use sorted index for O(log n) range queries
|
||||
*/
|
||||
export declare const SORTED_INDEX_FIELDS: string[];
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* Clean Metadata Architecture for Brainy 2.2
|
||||
* No backward compatibility - doing it RIGHT from the start!
|
||||
*/
|
||||
// Namespace constants
|
||||
export const BRAINY_NS = '_brainy';
|
||||
export const AUG_NS = '_augmentations';
|
||||
export const AUDIT_NS = '_audit';
|
||||
// Field paths for O(1) indexing
|
||||
export const DELETED_FIELD = `${BRAINY_NS}.deleted`;
|
||||
export const INDEXED_FIELD = `${BRAINY_NS}.indexed`;
|
||||
export const VERSION_FIELD = `${BRAINY_NS}.version`;
|
||||
/**
|
||||
* Create properly namespaced metadata
|
||||
* This is called for EVERY noun/verb creation
|
||||
*/
|
||||
export function createNamespacedMetadata(userMetadata) {
|
||||
const now = Date.now();
|
||||
// Start with user metadata or empty object
|
||||
const result = userMetadata ? { ...userMetadata } : {};
|
||||
// ALWAYS add internal namespace with required fields
|
||||
result[BRAINY_NS] = {
|
||||
deleted: false, // CRITICAL: Always false for new items
|
||||
indexed: true, // New items are indexed
|
||||
version: 1, // Current schema version
|
||||
created: now,
|
||||
updated: now
|
||||
};
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Update metadata while preserving namespaces
|
||||
*/
|
||||
export function updateNamespacedMetadata(existing, updates) {
|
||||
const now = Date.now();
|
||||
// Merge user fields
|
||||
const result = {
|
||||
...existing,
|
||||
...updates
|
||||
};
|
||||
// Preserve internal namespace but update timestamp
|
||||
result[BRAINY_NS] = {
|
||||
...existing[BRAINY_NS],
|
||||
updated: now
|
||||
};
|
||||
// Preserve augmentation namespace
|
||||
if (existing[AUG_NS]) {
|
||||
result[AUG_NS] = existing[AUG_NS];
|
||||
}
|
||||
// Preserve audit trail
|
||||
if (existing[AUDIT_NS]) {
|
||||
result[AUDIT_NS] = existing[AUDIT_NS];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Soft delete a noun (O(1) operation)
|
||||
*/
|
||||
export function markDeleted(metadata) {
|
||||
return {
|
||||
...metadata,
|
||||
[BRAINY_NS]: {
|
||||
...metadata[BRAINY_NS],
|
||||
deleted: true,
|
||||
updated: Date.now()
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Restore a soft-deleted noun (O(1) operation)
|
||||
*/
|
||||
export function markRestored(metadata) {
|
||||
return {
|
||||
...metadata,
|
||||
[BRAINY_NS]: {
|
||||
...metadata[BRAINY_NS],
|
||||
deleted: false,
|
||||
updated: Date.now()
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Check if a noun is deleted (O(1) check)
|
||||
*/
|
||||
export function isDeleted(metadata) {
|
||||
return metadata[BRAINY_NS]?.deleted === true;
|
||||
}
|
||||
/**
|
||||
* Get user metadata without internal fields
|
||||
* Used by augmentations to get clean user data
|
||||
*/
|
||||
export function getUserMetadata(metadata) {
|
||||
const { [BRAINY_NS]: _, [AUG_NS]: __, [AUDIT_NS]: ___, ...userMeta } = metadata;
|
||||
return userMeta;
|
||||
}
|
||||
/**
|
||||
* Set augmentation data in isolated namespace
|
||||
*/
|
||||
export function setAugmentationData(metadata, augmentationName, data) {
|
||||
const result = { ...metadata };
|
||||
if (!result[AUG_NS]) {
|
||||
result[AUG_NS] = {};
|
||||
}
|
||||
result[AUG_NS][augmentationName] = data;
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Add audit entry for tracking
|
||||
*/
|
||||
export function addAuditEntry(metadata, entry) {
|
||||
const result = { ...metadata };
|
||||
if (!result[AUDIT_NS]) {
|
||||
result[AUDIT_NS] = [];
|
||||
}
|
||||
result[AUDIT_NS].push({
|
||||
...entry,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* INDEXING EXPLANATION:
|
||||
*
|
||||
* The MetadataIndex flattens nested objects into dot-notation keys:
|
||||
*
|
||||
* Input metadata:
|
||||
* {
|
||||
* name: "Django",
|
||||
* _brainy: {
|
||||
* deleted: false,
|
||||
* indexed: true
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Creates index entries:
|
||||
* - "name" -> "django" -> Set([id1, id2...])
|
||||
* - "_brainy.deleted" -> "false" -> Set([id1, id2...]) // O(1) lookup!
|
||||
* - "_brainy.indexed" -> "true" -> Set([id1, id2...])
|
||||
*
|
||||
* Query: { "_brainy.deleted": false }
|
||||
* Lookup: index["_brainy.deleted"]["false"] -> Set of IDs in O(1)
|
||||
*
|
||||
* This is why namespacing doesn't hurt performance - it's all flattened!
|
||||
*/
|
||||
/**
|
||||
* Fields that should ALWAYS be indexed for O(1) access
|
||||
*/
|
||||
export const ALWAYS_INDEXED_FIELDS = [
|
||||
DELETED_FIELD, // For soft delete filtering
|
||||
INDEXED_FIELD, // For index management
|
||||
VERSION_FIELD // For schema versioning
|
||||
];
|
||||
/**
|
||||
* Fields that should use sorted index for O(log n) range queries
|
||||
*/
|
||||
export const SORTED_INDEX_FIELDS = [
|
||||
`${BRAINY_NS}.created`,
|
||||
`${BRAINY_NS}.updated`,
|
||||
`${BRAINY_NS}.priority`,
|
||||
`${BRAINY_NS}.ttl`
|
||||
];
|
||||
//# sourceMappingURL=metadataNamespace.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"metadataNamespace.js","sourceRoot":"","sources":["../../src/utils/metadataNamespace.ts"],"names":[],"mappings":"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"}
|
||||
24
.recovery-workspace/dist-backup-20250910-141917/utils/nodeVersionCheck.d.ts
vendored
Normal file
24
.recovery-workspace/dist-backup-20250910-141917/utils/nodeVersionCheck.d.ts
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Node.js Version Compatibility Check
|
||||
*
|
||||
* Brainy requires Node.js 22.x LTS for maximum stability with ONNX Runtime.
|
||||
* This prevents V8 HandleScope locking issues in worker threads.
|
||||
*/
|
||||
export interface VersionInfo {
|
||||
current: string;
|
||||
major: number;
|
||||
isSupported: boolean;
|
||||
recommendation: string;
|
||||
}
|
||||
/**
|
||||
* Check if the current Node.js version is supported
|
||||
*/
|
||||
export declare function checkNodeVersion(): VersionInfo;
|
||||
/**
|
||||
* Enforce Node.js version requirement with helpful error messaging
|
||||
*/
|
||||
export declare function enforceNodeVersion(): void;
|
||||
/**
|
||||
* Soft warning for version issues (non-blocking)
|
||||
*/
|
||||
export declare function warnNodeVersion(): boolean;
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Node.js Version Compatibility Check
|
||||
*
|
||||
* Brainy requires Node.js 22.x LTS for maximum stability with ONNX Runtime.
|
||||
* This prevents V8 HandleScope locking issues in worker threads.
|
||||
*/
|
||||
/**
|
||||
* Check if the current Node.js version is supported
|
||||
*/
|
||||
export function checkNodeVersion() {
|
||||
const nodeVersion = process.version;
|
||||
const majorVersion = parseInt(nodeVersion.split('.')[0].substring(1));
|
||||
const versionInfo = {
|
||||
current: nodeVersion,
|
||||
major: majorVersion,
|
||||
isSupported: majorVersion === 22,
|
||||
recommendation: 'Node.js 22.x LTS'
|
||||
};
|
||||
return versionInfo;
|
||||
}
|
||||
/**
|
||||
* Enforce Node.js version requirement with helpful error messaging
|
||||
*/
|
||||
export function enforceNodeVersion() {
|
||||
const versionInfo = checkNodeVersion();
|
||||
if (!versionInfo.isSupported) {
|
||||
const errorMessage = [
|
||||
'🚨 BRAINY COMPATIBILITY ERROR',
|
||||
'━'.repeat(50),
|
||||
`❌ Current Node.js: ${versionInfo.current}`,
|
||||
`✅ Required: ${versionInfo.recommendation}`,
|
||||
'',
|
||||
'💡 Quick Fix:',
|
||||
' nvm install 22 && nvm use 22',
|
||||
' npm install',
|
||||
'',
|
||||
'📖 Why Node.js 22?',
|
||||
' • Maximum ONNX Runtime stability',
|
||||
' • Prevents V8 threading crashes',
|
||||
' • Optimal zero-config performance',
|
||||
'',
|
||||
'🔗 More info: https://github.com/soulcraftlabs/brainy#node-version',
|
||||
'━'.repeat(50)
|
||||
].join('\n');
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Soft warning for version issues (non-blocking)
|
||||
*/
|
||||
export function warnNodeVersion() {
|
||||
const versionInfo = checkNodeVersion();
|
||||
if (!versionInfo.isSupported) {
|
||||
console.warn([
|
||||
'⚠️ BRAINY VERSION WARNING',
|
||||
` Current: ${versionInfo.current}`,
|
||||
` Recommended: ${versionInfo.recommendation}`,
|
||||
' Consider upgrading for best stability',
|
||||
''
|
||||
].join('\n'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//# sourceMappingURL=nodeVersionCheck.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"nodeVersionCheck.js","sourceRoot":"","sources":["../../src/utils/nodeVersionCheck.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAA;IACnC,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAErE,MAAM,WAAW,GAAgB;QAC/B,OAAO,EAAE,WAAW;QACpB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,YAAY,KAAK,EAAE;QAChC,cAAc,EAAE,kBAAkB;KACnC,CAAA;IAED,OAAO,WAAW,CAAA;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,WAAW,GAAG,gBAAgB,EAAE,CAAA;IAEtC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,YAAY,GAAG;YACnB,+BAA+B;YAC/B,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACd,sBAAsB,WAAW,CAAC,OAAO,EAAE;YAC3C,eAAe,WAAW,CAAC,cAAc,EAAE;YAC3C,EAAE;YACF,eAAe;YACf,gCAAgC;YAChC,eAAe;YACf,EAAE;YACF,oBAAoB;YACpB,oCAAoC;YACpC,mCAAmC;YACnC,qCAAqC;YACrC,EAAE;YACF,oEAAoE;YACpE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;SACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEZ,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,CAAA;IAC/B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,WAAW,GAAG,gBAAgB,EAAE,CAAA;IAEtC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC;YACX,4BAA4B;YAC5B,eAAe,WAAW,CAAC,OAAO,EAAE;YACpC,mBAAmB,WAAW,CAAC,cAAc,EAAE;YAC/C,0CAA0C;YAC1C,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAEb,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|
||||
58
.recovery-workspace/dist-backup-20250910-141917/utils/operationUtils.d.ts
vendored
Normal file
58
.recovery-workspace/dist-backup-20250910-141917/utils/operationUtils.d.ts
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Utility functions for timeout and retry logic
|
||||
* Used by storage adapters to handle network operations reliably
|
||||
*/
|
||||
export interface TimeoutConfig {
|
||||
get?: number;
|
||||
add?: number;
|
||||
delete?: number;
|
||||
}
|
||||
export interface RetryConfig {
|
||||
maxRetries?: number;
|
||||
initialDelay?: number;
|
||||
maxDelay?: number;
|
||||
backoffMultiplier?: number;
|
||||
}
|
||||
export interface OperationConfig {
|
||||
timeouts?: TimeoutConfig;
|
||||
retryPolicy?: RetryConfig;
|
||||
}
|
||||
export declare const DEFAULT_TIMEOUTS: Required<TimeoutConfig>;
|
||||
export declare const DEFAULT_RETRY_POLICY: Required<RetryConfig>;
|
||||
/**
|
||||
* Wraps a promise with a timeout
|
||||
*/
|
||||
export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, operation: string): Promise<T>;
|
||||
/**
|
||||
* Executes an operation with retry logic and exponential backoff
|
||||
*/
|
||||
export declare function withRetry<T>(operation: () => Promise<T>, operationName: string, config?: RetryConfig): Promise<T>;
|
||||
/**
|
||||
* Executes an operation with both timeout and retry logic
|
||||
*/
|
||||
export declare function withTimeoutAndRetry<T>(operation: () => Promise<T>, operationName: string, timeoutMs: number, retryConfig?: RetryConfig): Promise<T>;
|
||||
/**
|
||||
* Creates a configured operation executor for a specific operation type
|
||||
*/
|
||||
export declare function createOperationExecutor(operationType: keyof TimeoutConfig, config?: OperationConfig): <T>(operation: () => Promise<T>, operationName: string) => Promise<T>;
|
||||
/**
|
||||
* Storage operation executors for different operation types
|
||||
*/
|
||||
export declare class StorageOperationExecutors {
|
||||
private getExecutor;
|
||||
private addExecutor;
|
||||
private deleteExecutor;
|
||||
constructor(config?: OperationConfig);
|
||||
/**
|
||||
* Execute a get operation with timeout and retry
|
||||
*/
|
||||
executeGet<T>(operation: () => Promise<T>, operationName: string): Promise<T>;
|
||||
/**
|
||||
* Execute an add operation with timeout and retry
|
||||
*/
|
||||
executeAdd<T>(operation: () => Promise<T>, operationName: string): Promise<T>;
|
||||
/**
|
||||
* Execute a delete operation with timeout and retry
|
||||
*/
|
||||
executeDelete<T>(operation: () => Promise<T>, operationName: string): Promise<T>;
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* Utility functions for timeout and retry logic
|
||||
* Used by storage adapters to handle network operations reliably
|
||||
*/
|
||||
import { BrainyError } from '../errors/brainyError.js';
|
||||
// Default configuration values
|
||||
export const DEFAULT_TIMEOUTS = {
|
||||
get: 30000, // 30 seconds
|
||||
add: 60000, // 1 minute
|
||||
delete: 30000 // 30 seconds
|
||||
};
|
||||
export const DEFAULT_RETRY_POLICY = {
|
||||
maxRetries: 3,
|
||||
initialDelay: 1000,
|
||||
maxDelay: 10000,
|
||||
backoffMultiplier: 2
|
||||
};
|
||||
/**
|
||||
* Wraps a promise with a timeout
|
||||
*/
|
||||
export function withTimeout(promise, timeoutMs, operation) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
reject(BrainyError.timeout(operation, timeoutMs));
|
||||
}, timeoutMs);
|
||||
promise
|
||||
.then((result) => {
|
||||
clearTimeout(timeoutId);
|
||||
resolve(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
clearTimeout(timeoutId);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Calculates the delay for exponential backoff
|
||||
*/
|
||||
function calculateBackoffDelay(attemptNumber, initialDelay, maxDelay, backoffMultiplier) {
|
||||
const delay = initialDelay * Math.pow(backoffMultiplier, attemptNumber - 1);
|
||||
return Math.min(delay, maxDelay);
|
||||
}
|
||||
/**
|
||||
* Sleeps for the specified number of milliseconds
|
||||
*/
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
/**
|
||||
* Executes an operation with retry logic and exponential backoff
|
||||
*/
|
||||
export async function withRetry(operation, operationName, config = {}) {
|
||||
const { maxRetries = DEFAULT_RETRY_POLICY.maxRetries, initialDelay = DEFAULT_RETRY_POLICY.initialDelay, maxDelay = DEFAULT_RETRY_POLICY.maxDelay, backoffMultiplier = DEFAULT_RETRY_POLICY.backoffMultiplier } = config;
|
||||
let lastError;
|
||||
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
||||
try {
|
||||
return await operation();
|
||||
}
|
||||
catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
// If this is the last attempt, don't retry
|
||||
if (attempt > maxRetries) {
|
||||
break;
|
||||
}
|
||||
// Check if the error is retryable
|
||||
if (!BrainyError.isRetryable(lastError)) {
|
||||
throw BrainyError.fromError(lastError, operationName);
|
||||
}
|
||||
// Calculate delay for exponential backoff
|
||||
const delay = calculateBackoffDelay(attempt, initialDelay, maxDelay, backoffMultiplier);
|
||||
console.warn(`Operation '${operationName}' failed on attempt ${attempt}/${maxRetries + 1}. ` +
|
||||
`Retrying in ${delay}ms. Error: ${lastError.message}`);
|
||||
// Wait before retrying
|
||||
await sleep(delay);
|
||||
}
|
||||
}
|
||||
// All retries exhausted
|
||||
throw BrainyError.retryExhausted(operationName, maxRetries, lastError);
|
||||
}
|
||||
/**
|
||||
* Executes an operation with both timeout and retry logic
|
||||
*/
|
||||
export async function withTimeoutAndRetry(operation, operationName, timeoutMs, retryConfig = {}) {
|
||||
return withRetry(() => withTimeout(operation(), timeoutMs, operationName), operationName, retryConfig);
|
||||
}
|
||||
/**
|
||||
* Creates a configured operation executor for a specific operation type
|
||||
*/
|
||||
export function createOperationExecutor(operationType, config = {}) {
|
||||
const timeouts = { ...DEFAULT_TIMEOUTS, ...config.timeouts };
|
||||
const retryPolicy = { ...DEFAULT_RETRY_POLICY, ...config.retryPolicy };
|
||||
const timeoutMs = timeouts[operationType];
|
||||
return async function executeOperation(operation, operationName) {
|
||||
return withTimeoutAndRetry(operation, operationName, timeoutMs, retryPolicy);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Storage operation executors for different operation types
|
||||
*/
|
||||
export class StorageOperationExecutors {
|
||||
constructor(config = {}) {
|
||||
this.getExecutor = createOperationExecutor('get', config);
|
||||
this.addExecutor = createOperationExecutor('add', config);
|
||||
this.deleteExecutor = createOperationExecutor('delete', config);
|
||||
}
|
||||
/**
|
||||
* Execute a get operation with timeout and retry
|
||||
*/
|
||||
async executeGet(operation, operationName) {
|
||||
return this.getExecutor(operation, operationName);
|
||||
}
|
||||
/**
|
||||
* Execute an add operation with timeout and retry
|
||||
*/
|
||||
async executeAdd(operation, operationName) {
|
||||
return this.addExecutor(operation, operationName);
|
||||
}
|
||||
/**
|
||||
* Execute a delete operation with timeout and retry
|
||||
*/
|
||||
async executeDelete(operation, operationName) {
|
||||
return this.deleteExecutor(operation, operationName);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=operationUtils.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"operationUtils.js","sourceRoot":"","sources":["../../src/utils/operationUtils.ts"],"names":[],"mappings":"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"}
|
||||
114
.recovery-workspace/dist-backup-20250910-141917/utils/performanceMonitor.d.ts
vendored
Normal file
114
.recovery-workspace/dist-backup-20250910-141917/utils/performanceMonitor.d.ts
vendored
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* Performance Monitor
|
||||
* Automatically tracks and optimizes system performance
|
||||
* Provides real-time insights and auto-tuning recommendations
|
||||
*/
|
||||
interface PerformanceMetrics {
|
||||
totalOperations: number;
|
||||
successfulOperations: number;
|
||||
failedOperations: number;
|
||||
averageLatency: number;
|
||||
p95Latency: number;
|
||||
p99Latency: number;
|
||||
operationsPerSecond: number;
|
||||
bytesPerSecond: number;
|
||||
memoryUsage: number;
|
||||
cpuUsage: number;
|
||||
socketUtilization: number;
|
||||
queueDepth: number;
|
||||
errorRate: number;
|
||||
healthScore: number;
|
||||
}
|
||||
interface PerformanceTrend {
|
||||
metric: string;
|
||||
direction: 'improving' | 'degrading' | 'stable';
|
||||
changeRate: number;
|
||||
prediction: number;
|
||||
}
|
||||
/**
|
||||
* Comprehensive performance monitoring and optimization
|
||||
*/
|
||||
export declare class PerformanceMonitor {
|
||||
private logger;
|
||||
private metrics;
|
||||
private history;
|
||||
private maxHistorySize;
|
||||
private operationLatencies;
|
||||
private operationSizes;
|
||||
private lastReset;
|
||||
private resetInterval;
|
||||
private lastCpuUsage;
|
||||
private lastCpuCheck;
|
||||
private thresholds;
|
||||
private recommendations;
|
||||
private autoOptimizeEnabled;
|
||||
private lastOptimization;
|
||||
private optimizationInterval;
|
||||
/**
|
||||
* Track an operation completion
|
||||
*/
|
||||
trackOperation(success: boolean, latency: number, bytes?: number): void;
|
||||
/**
|
||||
* Update all metrics
|
||||
*/
|
||||
private updateMetrics;
|
||||
/**
|
||||
* Update resource metrics
|
||||
*/
|
||||
private updateResourceMetrics;
|
||||
/**
|
||||
* Calculate overall health score
|
||||
*/
|
||||
private calculateHealthScore;
|
||||
/**
|
||||
* Check for alert conditions
|
||||
*/
|
||||
private checkAlerts;
|
||||
/**
|
||||
* Auto-optimize system based on metrics
|
||||
*/
|
||||
private autoOptimize;
|
||||
/**
|
||||
* Analyze performance trends
|
||||
*/
|
||||
private analyzeTrends;
|
||||
/**
|
||||
* Reset counters
|
||||
*/
|
||||
private resetCounters;
|
||||
/**
|
||||
* Get current metrics
|
||||
*/
|
||||
getMetrics(): Readonly<PerformanceMetrics>;
|
||||
/**
|
||||
* Get performance trends
|
||||
*/
|
||||
getTrends(): PerformanceTrend[];
|
||||
/**
|
||||
* Get recommendations
|
||||
*/
|
||||
getRecommendations(): string[];
|
||||
/**
|
||||
* Get performance report
|
||||
*/
|
||||
getReport(): {
|
||||
metrics: PerformanceMetrics;
|
||||
trends: PerformanceTrend[];
|
||||
recommendations: string[];
|
||||
socketConfig: any;
|
||||
backpressureStatus: any;
|
||||
};
|
||||
/**
|
||||
* Enable/disable auto-optimization
|
||||
*/
|
||||
setAutoOptimize(enabled: boolean): void;
|
||||
/**
|
||||
* Reset all metrics and history
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
/**
|
||||
* Get the global performance monitor instance
|
||||
*/
|
||||
export declare function getGlobalPerformanceMonitor(): PerformanceMonitor;
|
||||
export {};
|
||||
|
|
@ -0,0 +1,384 @@
|
|||
/**
|
||||
* Performance Monitor
|
||||
* Automatically tracks and optimizes system performance
|
||||
* Provides real-time insights and auto-tuning recommendations
|
||||
*/
|
||||
import { createModuleLogger } from './logger.js';
|
||||
import { getGlobalSocketManager } from './adaptiveSocketManager.js';
|
||||
import { getGlobalBackpressure } from './adaptiveBackpressure.js';
|
||||
/**
|
||||
* Comprehensive performance monitoring and optimization
|
||||
*/
|
||||
export class PerformanceMonitor {
|
||||
constructor() {
|
||||
this.logger = createModuleLogger('PerformanceMonitor');
|
||||
// Current metrics
|
||||
this.metrics = {
|
||||
totalOperations: 0,
|
||||
successfulOperations: 0,
|
||||
failedOperations: 0,
|
||||
averageLatency: 0,
|
||||
p95Latency: 0,
|
||||
p99Latency: 0,
|
||||
operationsPerSecond: 0,
|
||||
bytesPerSecond: 0,
|
||||
memoryUsage: 0,
|
||||
cpuUsage: 0,
|
||||
socketUtilization: 0,
|
||||
queueDepth: 0,
|
||||
errorRate: 0,
|
||||
healthScore: 100
|
||||
};
|
||||
// Historical data for trend analysis
|
||||
this.history = [];
|
||||
this.maxHistorySize = 1000;
|
||||
// Operation tracking
|
||||
this.operationLatencies = [];
|
||||
this.operationSizes = [];
|
||||
this.lastReset = Date.now();
|
||||
this.resetInterval = 60000; // Reset counters every minute
|
||||
// CPU tracking
|
||||
this.lastCpuUsage = process.cpuUsage ? process.cpuUsage() : null;
|
||||
this.lastCpuCheck = Date.now();
|
||||
// Alert thresholds
|
||||
this.thresholds = {
|
||||
errorRate: 0.05, // 5% error rate
|
||||
latencyP95: 5000, // 5 second P95
|
||||
memoryUsage: 0.8, // 80% memory
|
||||
cpuUsage: 0.9, // 90% CPU
|
||||
healthScore: 70 // Health score below 70
|
||||
};
|
||||
// Optimization recommendations
|
||||
this.recommendations = [];
|
||||
// Auto-optimization state
|
||||
this.autoOptimizeEnabled = true;
|
||||
this.lastOptimization = Date.now();
|
||||
this.optimizationInterval = 30000; // Optimize every 30 seconds
|
||||
}
|
||||
/**
|
||||
* Track an operation completion
|
||||
*/
|
||||
trackOperation(success, latency, bytes = 0) {
|
||||
// Update counters
|
||||
this.metrics.totalOperations++;
|
||||
if (success) {
|
||||
this.metrics.successfulOperations++;
|
||||
}
|
||||
else {
|
||||
this.metrics.failedOperations++;
|
||||
}
|
||||
// Track latency
|
||||
this.operationLatencies.push(latency);
|
||||
if (this.operationLatencies.length > 10000) {
|
||||
this.operationLatencies = this.operationLatencies.slice(-5000);
|
||||
}
|
||||
// Track size
|
||||
if (bytes > 0) {
|
||||
this.operationSizes.push(bytes);
|
||||
if (this.operationSizes.length > 10000) {
|
||||
this.operationSizes = this.operationSizes.slice(-5000);
|
||||
}
|
||||
}
|
||||
// Update metrics periodically
|
||||
this.updateMetrics();
|
||||
}
|
||||
/**
|
||||
* Update all metrics
|
||||
*/
|
||||
updateMetrics() {
|
||||
const now = Date.now();
|
||||
const timeSinceReset = (now - this.lastReset) / 1000;
|
||||
// Calculate latency percentiles
|
||||
if (this.operationLatencies.length > 0) {
|
||||
const sorted = [...this.operationLatencies].sort((a, b) => a - b);
|
||||
const p95Index = Math.floor(sorted.length * 0.95);
|
||||
const p99Index = Math.floor(sorted.length * 0.99);
|
||||
this.metrics.averageLatency = sorted.reduce((a, b) => a + b, 0) / sorted.length;
|
||||
this.metrics.p95Latency = sorted[p95Index] || 0;
|
||||
this.metrics.p99Latency = sorted[p99Index] || 0;
|
||||
}
|
||||
// Calculate throughput
|
||||
if (timeSinceReset > 0) {
|
||||
this.metrics.operationsPerSecond = this.metrics.totalOperations / timeSinceReset;
|
||||
const totalBytes = this.operationSizes.reduce((a, b) => a + b, 0);
|
||||
this.metrics.bytesPerSecond = totalBytes / timeSinceReset;
|
||||
}
|
||||
// Calculate error rate
|
||||
this.metrics.errorRate = this.metrics.totalOperations > 0
|
||||
? this.metrics.failedOperations / this.metrics.totalOperations
|
||||
: 0;
|
||||
// Update resource metrics
|
||||
this.updateResourceMetrics();
|
||||
// Calculate health score
|
||||
this.calculateHealthScore();
|
||||
// Store in history
|
||||
this.history.push({ ...this.metrics });
|
||||
if (this.history.length > this.maxHistorySize) {
|
||||
this.history.shift();
|
||||
}
|
||||
// Check for alerts
|
||||
this.checkAlerts();
|
||||
// Auto-optimize if enabled
|
||||
if (this.autoOptimizeEnabled && now - this.lastOptimization > this.optimizationInterval) {
|
||||
this.autoOptimize();
|
||||
this.lastOptimization = now;
|
||||
}
|
||||
// Reset counters periodically
|
||||
if (now - this.lastReset > this.resetInterval) {
|
||||
this.resetCounters();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update resource metrics
|
||||
*/
|
||||
updateResourceMetrics() {
|
||||
// Memory usage
|
||||
if (typeof process !== 'undefined' && process.memoryUsage) {
|
||||
const memUsage = process.memoryUsage();
|
||||
this.metrics.memoryUsage = memUsage.heapUsed / memUsage.heapTotal;
|
||||
}
|
||||
// CPU usage (Node.js only)
|
||||
if (this.lastCpuUsage && process.cpuUsage) {
|
||||
const currentCpuUsage = process.cpuUsage();
|
||||
const now = Date.now();
|
||||
const timeDiff = now - this.lastCpuCheck;
|
||||
if (timeDiff > 1000) { // Update CPU every second
|
||||
const userDiff = currentCpuUsage.user - this.lastCpuUsage.user;
|
||||
const systemDiff = currentCpuUsage.system - this.lastCpuUsage.system;
|
||||
const totalDiff = userDiff + systemDiff;
|
||||
// CPU percentage (approximate)
|
||||
this.metrics.cpuUsage = totalDiff / (timeDiff * 1000);
|
||||
this.lastCpuUsage = currentCpuUsage;
|
||||
this.lastCpuCheck = now;
|
||||
}
|
||||
}
|
||||
// Get metrics from socket manager
|
||||
const socketMetrics = getGlobalSocketManager().getMetrics();
|
||||
this.metrics.socketUtilization = socketMetrics.socketUtilization;
|
||||
// Get metrics from backpressure system
|
||||
const backpressureStatus = getGlobalBackpressure().getStatus();
|
||||
this.metrics.queueDepth = backpressureStatus.queueLength;
|
||||
}
|
||||
/**
|
||||
* Calculate overall health score
|
||||
*/
|
||||
calculateHealthScore() {
|
||||
let score = 100;
|
||||
// Deduct points for high error rate
|
||||
if (this.metrics.errorRate > 0.01) {
|
||||
score -= Math.min(30, this.metrics.errorRate * 300);
|
||||
}
|
||||
// Deduct points for high latency
|
||||
if (this.metrics.p95Latency > 3000) {
|
||||
score -= Math.min(20, (this.metrics.p95Latency - 3000) / 100);
|
||||
}
|
||||
// Deduct points for high memory usage
|
||||
if (this.metrics.memoryUsage > 0.7) {
|
||||
score -= Math.min(20, (this.metrics.memoryUsage - 0.7) * 66);
|
||||
}
|
||||
// Deduct points for high CPU usage
|
||||
if (this.metrics.cpuUsage > 0.8) {
|
||||
score -= Math.min(15, (this.metrics.cpuUsage - 0.8) * 75);
|
||||
}
|
||||
// Deduct points for low throughput
|
||||
if (this.metrics.operationsPerSecond < 1 && this.metrics.totalOperations > 10) {
|
||||
score -= 10;
|
||||
}
|
||||
// Deduct points for queue depth
|
||||
if (this.metrics.queueDepth > 100) {
|
||||
score -= Math.min(15, this.metrics.queueDepth / 20);
|
||||
}
|
||||
this.metrics.healthScore = Math.max(0, Math.min(100, score));
|
||||
}
|
||||
/**
|
||||
* Check for alert conditions
|
||||
*/
|
||||
checkAlerts() {
|
||||
const alerts = [];
|
||||
if (this.metrics.errorRate > this.thresholds.errorRate) {
|
||||
alerts.push(`High error rate: ${(this.metrics.errorRate * 100).toFixed(1)}%`);
|
||||
}
|
||||
if (this.metrics.p95Latency > this.thresholds.latencyP95) {
|
||||
alerts.push(`High P95 latency: ${this.metrics.p95Latency}ms`);
|
||||
}
|
||||
if (this.metrics.memoryUsage > this.thresholds.memoryUsage) {
|
||||
alerts.push(`High memory usage: ${(this.metrics.memoryUsage * 100).toFixed(1)}%`);
|
||||
}
|
||||
if (this.metrics.cpuUsage > this.thresholds.cpuUsage) {
|
||||
alerts.push(`High CPU usage: ${(this.metrics.cpuUsage * 100).toFixed(1)}%`);
|
||||
}
|
||||
if (this.metrics.healthScore < this.thresholds.healthScore) {
|
||||
alerts.push(`Low health score: ${this.metrics.healthScore.toFixed(0)}`);
|
||||
}
|
||||
if (alerts.length > 0) {
|
||||
this.logger.warn('Performance alerts', { alerts, metrics: this.metrics });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Auto-optimize system based on metrics
|
||||
*/
|
||||
autoOptimize() {
|
||||
this.recommendations = [];
|
||||
// Analyze trends
|
||||
const trends = this.analyzeTrends();
|
||||
// Generate recommendations based on metrics and trends
|
||||
if (this.metrics.errorRate > 0.02) {
|
||||
this.recommendations.push('Reduce load or increase timeouts due to high error rate');
|
||||
}
|
||||
if (this.metrics.p95Latency > 3000) {
|
||||
this.recommendations.push('Increase batch size or socket limits to improve latency');
|
||||
}
|
||||
if (this.metrics.memoryUsage > 0.7) {
|
||||
this.recommendations.push('Reduce cache sizes or batch sizes to free memory');
|
||||
}
|
||||
if (this.metrics.queueDepth > 50) {
|
||||
this.recommendations.push('Increase concurrency limits to reduce queue depth');
|
||||
}
|
||||
// Check for degrading trends
|
||||
trends.forEach(trend => {
|
||||
if (trend.direction === 'degrading' && Math.abs(trend.changeRate) > 0.1) {
|
||||
this.recommendations.push(`${trend.metric} is degrading at ${(trend.changeRate * 100).toFixed(1)}% per minute`);
|
||||
}
|
||||
});
|
||||
// Log recommendations if any
|
||||
if (this.recommendations.length > 0) {
|
||||
this.logger.info('Performance optimization recommendations', {
|
||||
recommendations: this.recommendations,
|
||||
metrics: this.metrics
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Analyze performance trends
|
||||
*/
|
||||
analyzeTrends() {
|
||||
const trends = [];
|
||||
if (this.history.length < 10) {
|
||||
return trends; // Not enough data
|
||||
}
|
||||
// Get recent history
|
||||
const recent = this.history.slice(-20);
|
||||
const older = this.history.slice(-40, -20);
|
||||
// Compare key metrics
|
||||
const metricsToAnalyze = [
|
||||
'errorRate',
|
||||
'averageLatency',
|
||||
'operationsPerSecond',
|
||||
'memoryUsage',
|
||||
'healthScore'
|
||||
];
|
||||
metricsToAnalyze.forEach(metric => {
|
||||
const recentAvg = recent.reduce((sum, m) => sum + m[metric], 0) / recent.length;
|
||||
const olderAvg = older.length > 0
|
||||
? older.reduce((sum, m) => sum + m[metric], 0) / older.length
|
||||
: recentAvg;
|
||||
const changeRate = olderAvg !== 0 ? (recentAvg - olderAvg) / olderAvg : 0;
|
||||
let direction = 'stable';
|
||||
if (Math.abs(changeRate) > 0.05) { // 5% threshold
|
||||
// For error rate and latency, increase is bad
|
||||
if (metric === 'errorRate' || metric === 'averageLatency' || metric === 'memoryUsage') {
|
||||
direction = changeRate > 0 ? 'degrading' : 'improving';
|
||||
}
|
||||
else {
|
||||
// For throughput and health score, increase is good
|
||||
direction = changeRate > 0 ? 'improving' : 'degrading';
|
||||
}
|
||||
}
|
||||
// Simple linear prediction
|
||||
const prediction = recentAvg + (recentAvg * changeRate);
|
||||
trends.push({
|
||||
metric,
|
||||
direction,
|
||||
changeRate,
|
||||
prediction
|
||||
});
|
||||
});
|
||||
return trends;
|
||||
}
|
||||
/**
|
||||
* Reset counters
|
||||
*/
|
||||
resetCounters() {
|
||||
this.metrics.totalOperations = 0;
|
||||
this.metrics.successfulOperations = 0;
|
||||
this.metrics.failedOperations = 0;
|
||||
this.operationSizes = [];
|
||||
this.lastReset = Date.now();
|
||||
}
|
||||
/**
|
||||
* Get current metrics
|
||||
*/
|
||||
getMetrics() {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
/**
|
||||
* Get performance trends
|
||||
*/
|
||||
getTrends() {
|
||||
return this.analyzeTrends();
|
||||
}
|
||||
/**
|
||||
* Get recommendations
|
||||
*/
|
||||
getRecommendations() {
|
||||
return [...this.recommendations];
|
||||
}
|
||||
/**
|
||||
* Get performance report
|
||||
*/
|
||||
getReport() {
|
||||
return {
|
||||
metrics: this.getMetrics(),
|
||||
trends: this.getTrends(),
|
||||
recommendations: this.getRecommendations(),
|
||||
socketConfig: getGlobalSocketManager().getConfig(),
|
||||
backpressureStatus: getGlobalBackpressure().getStatus()
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Enable/disable auto-optimization
|
||||
*/
|
||||
setAutoOptimize(enabled) {
|
||||
this.autoOptimizeEnabled = enabled;
|
||||
this.logger.info(`Auto-optimization ${enabled ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
/**
|
||||
* Reset all metrics and history
|
||||
*/
|
||||
reset() {
|
||||
this.metrics = {
|
||||
totalOperations: 0,
|
||||
successfulOperations: 0,
|
||||
failedOperations: 0,
|
||||
averageLatency: 0,
|
||||
p95Latency: 0,
|
||||
p99Latency: 0,
|
||||
operationsPerSecond: 0,
|
||||
bytesPerSecond: 0,
|
||||
memoryUsage: 0,
|
||||
cpuUsage: 0,
|
||||
socketUtilization: 0,
|
||||
queueDepth: 0,
|
||||
errorRate: 0,
|
||||
healthScore: 100
|
||||
};
|
||||
this.history = [];
|
||||
this.operationLatencies = [];
|
||||
this.operationSizes = [];
|
||||
this.recommendations = [];
|
||||
this.lastReset = Date.now();
|
||||
this.logger.info('Performance monitor reset');
|
||||
}
|
||||
}
|
||||
// Global singleton instance
|
||||
let globalMonitor = null;
|
||||
/**
|
||||
* Get the global performance monitor instance
|
||||
*/
|
||||
export function getGlobalPerformanceMonitor() {
|
||||
if (!globalMonitor) {
|
||||
globalMonitor = new PerformanceMonitor();
|
||||
}
|
||||
return globalMonitor;
|
||||
}
|
||||
//# sourceMappingURL=performanceMonitor.js.map
|
||||
File diff suppressed because one or more lines are too long
87
.recovery-workspace/dist-backup-20250910-141917/utils/periodicCleanup.d.ts
vendored
Normal file
87
.recovery-workspace/dist-backup-20250910-141917/utils/periodicCleanup.d.ts
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Periodic Cleanup for Soft-Deleted Items
|
||||
*
|
||||
* SAFETY-FIRST APPROACH:
|
||||
* - Maintains durability guarantees (storage-first)
|
||||
* - Coordinates HNSW and metadata index consistency
|
||||
* - Isolated from live operations
|
||||
* - Graceful failure handling
|
||||
*/
|
||||
import type { StorageAdapter } from '../coreTypes.js';
|
||||
import type { HNSWIndex } from '../hnsw/hnswIndex.js';
|
||||
import type { MetadataIndexManager } from './metadataIndex.js';
|
||||
export interface CleanupConfig {
|
||||
/** Age in milliseconds after which soft-deleted items are eligible for cleanup */
|
||||
maxAge: number;
|
||||
/** Maximum number of items to clean up in one batch */
|
||||
batchSize: number;
|
||||
/** Interval between cleanup runs (milliseconds) */
|
||||
cleanupInterval: number;
|
||||
/** Whether to run cleanup automatically */
|
||||
enabled: boolean;
|
||||
}
|
||||
export interface CleanupStats {
|
||||
itemsProcessed: number;
|
||||
itemsDeleted: number;
|
||||
errors: number;
|
||||
lastRun: number;
|
||||
nextRun: number;
|
||||
}
|
||||
/**
|
||||
* Coordinates safe cleanup of old soft-deleted items across all indexes
|
||||
*
|
||||
* CRITICAL SAFETY FEATURES:
|
||||
* 1. Storage-first deletion (durability)
|
||||
* 2. Index consistency coordination
|
||||
* 3. Batch processing with limits
|
||||
* 4. Error isolation and recovery
|
||||
*/
|
||||
export declare class PeriodicCleanup {
|
||||
private storage;
|
||||
private hnswIndex;
|
||||
private metadataIndex;
|
||||
private config;
|
||||
private stats;
|
||||
private cleanupTimer;
|
||||
private running;
|
||||
constructor(storage: StorageAdapter, hnswIndex: HNSWIndex, metadataIndex: MetadataIndexManager | null, config?: Partial<CleanupConfig>);
|
||||
/**
|
||||
* Start periodic cleanup
|
||||
*/
|
||||
start(): void;
|
||||
/**
|
||||
* Stop periodic cleanup
|
||||
*/
|
||||
stop(): void;
|
||||
/**
|
||||
* Run cleanup manually
|
||||
*/
|
||||
runNow(): Promise<CleanupStats>;
|
||||
/**
|
||||
* Get current cleanup statistics
|
||||
*/
|
||||
getStats(): CleanupStats;
|
||||
private scheduleNext;
|
||||
/**
|
||||
* CRITICAL: Coordinated cleanup across all indexes
|
||||
*
|
||||
* SAFETY PROTOCOL:
|
||||
* 1. Find eligible items (old + soft-deleted)
|
||||
* 2. Remove from storage FIRST (durability)
|
||||
* 3. Remove from HNSW (graph consistency)
|
||||
* 4. Remove from metadata index (search consistency)
|
||||
* 5. Track stats and errors
|
||||
*/
|
||||
private performCleanup;
|
||||
/**
|
||||
* Find items eligible for cleanup (old + soft-deleted)
|
||||
*/
|
||||
private findEligibleItems;
|
||||
/**
|
||||
* Process a batch of items for cleanup
|
||||
*
|
||||
* CRITICAL: This maintains the durability-first approach:
|
||||
* Storage → HNSW → Metadata Index
|
||||
*/
|
||||
private processBatch;
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/**
|
||||
* Periodic Cleanup for Soft-Deleted Items
|
||||
*
|
||||
* SAFETY-FIRST APPROACH:
|
||||
* - Maintains durability guarantees (storage-first)
|
||||
* - Coordinates HNSW and metadata index consistency
|
||||
* - Isolated from live operations
|
||||
* - Graceful failure handling
|
||||
*/
|
||||
import { prodLog } from './logger.js';
|
||||
import { isDeleted } from './metadataNamespace.js';
|
||||
/**
|
||||
* Coordinates safe cleanup of old soft-deleted items across all indexes
|
||||
*
|
||||
* CRITICAL SAFETY FEATURES:
|
||||
* 1. Storage-first deletion (durability)
|
||||
* 2. Index consistency coordination
|
||||
* 3. Batch processing with limits
|
||||
* 4. Error isolation and recovery
|
||||
*/
|
||||
export class PeriodicCleanup {
|
||||
constructor(storage, hnswIndex, metadataIndex, config = {}) {
|
||||
this.cleanupTimer = null;
|
||||
this.running = false;
|
||||
this.storage = storage;
|
||||
this.hnswIndex = hnswIndex;
|
||||
this.metadataIndex = metadataIndex;
|
||||
// Default: clean up items deleted more than 1 hour ago
|
||||
this.config = {
|
||||
maxAge: config.maxAge ?? 60 * 60 * 1000, // 1 hour
|
||||
batchSize: config.batchSize ?? 100, // 100 items max per batch
|
||||
cleanupInterval: config.cleanupInterval ?? 15 * 60 * 1000, // Every 15 minutes
|
||||
enabled: config.enabled ?? true
|
||||
};
|
||||
this.stats = {
|
||||
itemsProcessed: 0,
|
||||
itemsDeleted: 0,
|
||||
errors: 0,
|
||||
lastRun: 0,
|
||||
nextRun: 0
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Start periodic cleanup
|
||||
*/
|
||||
start() {
|
||||
if (!this.config.enabled || this.cleanupTimer) {
|
||||
return;
|
||||
}
|
||||
prodLog.info(`Starting periodic cleanup: maxAge=${this.config.maxAge}, batchSize=${this.config.batchSize}, interval=${this.config.cleanupInterval}`);
|
||||
this.scheduleNext();
|
||||
}
|
||||
/**
|
||||
* Stop periodic cleanup
|
||||
*/
|
||||
stop() {
|
||||
if (this.cleanupTimer) {
|
||||
clearTimeout(this.cleanupTimer);
|
||||
this.cleanupTimer = null;
|
||||
}
|
||||
prodLog.info('Stopped periodic cleanup');
|
||||
}
|
||||
/**
|
||||
* Run cleanup manually
|
||||
*/
|
||||
async runNow() {
|
||||
if (this.running) {
|
||||
throw new Error('Cleanup already running');
|
||||
}
|
||||
return this.performCleanup();
|
||||
}
|
||||
/**
|
||||
* Get current cleanup statistics
|
||||
*/
|
||||
getStats() {
|
||||
return { ...this.stats };
|
||||
}
|
||||
scheduleNext() {
|
||||
const nextRun = Date.now() + this.config.cleanupInterval;
|
||||
this.stats.nextRun = nextRun;
|
||||
this.cleanupTimer = setTimeout(async () => {
|
||||
await this.performCleanup();
|
||||
this.scheduleNext();
|
||||
}, this.config.cleanupInterval);
|
||||
}
|
||||
/**
|
||||
* CRITICAL: Coordinated cleanup across all indexes
|
||||
*
|
||||
* SAFETY PROTOCOL:
|
||||
* 1. Find eligible items (old + soft-deleted)
|
||||
* 2. Remove from storage FIRST (durability)
|
||||
* 3. Remove from HNSW (graph consistency)
|
||||
* 4. Remove from metadata index (search consistency)
|
||||
* 5. Track stats and errors
|
||||
*/
|
||||
async performCleanup() {
|
||||
if (this.running) {
|
||||
prodLog.warn('Cleanup already running, skipping');
|
||||
return this.stats;
|
||||
}
|
||||
this.running = true;
|
||||
const startTime = Date.now();
|
||||
this.stats.lastRun = startTime;
|
||||
try {
|
||||
prodLog.debug(`Starting cleanup run: maxAge=${this.config.maxAge}, cutoffTime=${startTime - this.config.maxAge}`);
|
||||
// Step 1: Find eligible items for cleanup
|
||||
const eligibleItems = await this.findEligibleItems(startTime);
|
||||
if (eligibleItems.length === 0) {
|
||||
prodLog.debug('No items eligible for cleanup');
|
||||
return this.stats;
|
||||
}
|
||||
prodLog.info(`Found ${eligibleItems.length} items eligible for cleanup`);
|
||||
// Step 2: Process in batches for safety
|
||||
let processed = 0;
|
||||
let deleted = 0;
|
||||
let errors = 0;
|
||||
for (let i = 0; i < eligibleItems.length; i += this.config.batchSize) {
|
||||
const batch = eligibleItems.slice(i, i + this.config.batchSize);
|
||||
const batchResult = await this.processBatch(batch);
|
||||
processed += batchResult.processed;
|
||||
deleted += batchResult.deleted;
|
||||
errors += batchResult.errors;
|
||||
// Small delay between batches to avoid overwhelming the system
|
||||
if (i + this.config.batchSize < eligibleItems.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
}
|
||||
// Update stats
|
||||
this.stats.itemsProcessed += processed;
|
||||
this.stats.itemsDeleted += deleted;
|
||||
this.stats.errors += errors;
|
||||
prodLog.info(`Cleanup run completed: processed=${processed}, deleted=${deleted}, errors=${errors}, duration=${Date.now() - startTime}ms`);
|
||||
}
|
||||
catch (error) {
|
||||
prodLog.error(`Cleanup run failed: ${error}`);
|
||||
this.stats.errors++;
|
||||
}
|
||||
finally {
|
||||
this.running = false;
|
||||
}
|
||||
return this.stats;
|
||||
}
|
||||
/**
|
||||
* Find items eligible for cleanup (old + soft-deleted)
|
||||
*/
|
||||
async findEligibleItems(currentTime) {
|
||||
const cutoffTime = currentTime - this.config.maxAge;
|
||||
const eligibleItems = [];
|
||||
try {
|
||||
// Get all nouns from storage (using pagination to avoid memory issues)
|
||||
const nounsResult = await this.storage.getNouns({
|
||||
pagination: { limit: 1000 } // Process in chunks
|
||||
});
|
||||
for (const noun of nounsResult.items) {
|
||||
try {
|
||||
if (!noun.metadata || !isDeleted(noun.metadata)) {
|
||||
continue; // Not deleted, skip
|
||||
}
|
||||
// Check if old enough for cleanup
|
||||
const deletedTime = noun.metadata._brainy?.updated || 0;
|
||||
if (deletedTime && (currentTime - deletedTime) > this.config.maxAge) {
|
||||
eligibleItems.push(noun.id);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
prodLog.warn(`Failed to check item ${noun.id} for cleanup eligibility: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
prodLog.error(`Failed to find eligible items: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
return eligibleItems;
|
||||
}
|
||||
/**
|
||||
* Process a batch of items for cleanup
|
||||
*
|
||||
* CRITICAL: This maintains the durability-first approach:
|
||||
* Storage → HNSW → Metadata Index
|
||||
*/
|
||||
async processBatch(itemIds) {
|
||||
let processed = 0;
|
||||
let deleted = 0;
|
||||
let errors = 0;
|
||||
for (const id of itemIds) {
|
||||
processed++;
|
||||
try {
|
||||
// STEP 1: Remove from storage FIRST (durability guarantee)
|
||||
try {
|
||||
await this.storage.deleteNoun(id);
|
||||
}
|
||||
catch (storageError) {
|
||||
prodLog.warn(`Failed to delete ${id} from storage: ${storageError}`);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
// STEP 2: Remove from HNSW index (vector search consistency)
|
||||
const hnswResult = this.hnswIndex.removeItem(id);
|
||||
if (!hnswResult) {
|
||||
prodLog.warn(`Failed to remove ${id} from HNSW index (may not have been indexed)`);
|
||||
// Not a critical error - item might not have been in vector index
|
||||
}
|
||||
// STEP 3: Remove from metadata index (faceted search consistency)
|
||||
if (this.metadataIndex) {
|
||||
await this.metadataIndex.removeFromIndex(id);
|
||||
}
|
||||
deleted++;
|
||||
prodLog.debug(`Successfully cleaned up item ${id}`);
|
||||
}
|
||||
catch (error) {
|
||||
errors++;
|
||||
prodLog.error(`Failed to cleanup item ${id}: ${error}`);
|
||||
}
|
||||
}
|
||||
return { processed, deleted, errors };
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=periodicCleanup.js.map
|
||||
File diff suppressed because one or more lines are too long
160
.recovery-workspace/dist-backup-20250910-141917/utils/rateLimiter.d.ts
vendored
Normal file
160
.recovery-workspace/dist-backup-20250910-141917/utils/rateLimiter.d.ts
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* Rate Limiter for Brainy API
|
||||
*
|
||||
* Provides rate limiting without external dependencies like Redis.
|
||||
* - Uses in-memory storage for single instances
|
||||
* - Can use S3/R2 for distributed rate limiting
|
||||
*
|
||||
* @module rateLimiter
|
||||
*/
|
||||
import { BaseStorageAdapter } from '../storage/adapters/baseStorageAdapter.js';
|
||||
export interface RateLimitConfig {
|
||||
/**
|
||||
* Maximum number of requests allowed
|
||||
*/
|
||||
maxRequests: number;
|
||||
/**
|
||||
* Time window in milliseconds
|
||||
*/
|
||||
windowMs: number;
|
||||
/**
|
||||
* Optional message to return when rate limit is exceeded
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* Whether to use distributed storage (S3/R2) for rate limiting
|
||||
*/
|
||||
distributed?: boolean;
|
||||
/**
|
||||
* Storage adapter for distributed rate limiting
|
||||
*/
|
||||
storage?: BaseStorageAdapter;
|
||||
/**
|
||||
* Key prefix for distributed storage
|
||||
*/
|
||||
keyPrefix?: string;
|
||||
}
|
||||
export interface RateLimitResult {
|
||||
/**
|
||||
* Whether the request is allowed
|
||||
*/
|
||||
allowed: boolean;
|
||||
/**
|
||||
* Number of requests remaining in the current window
|
||||
*/
|
||||
remaining: number;
|
||||
/**
|
||||
* Time when the rate limit window resets (Unix timestamp)
|
||||
*/
|
||||
resetTime: number;
|
||||
/**
|
||||
* Total limit for the window
|
||||
*/
|
||||
limit: number;
|
||||
}
|
||||
/**
|
||||
* Simple in-memory rate limiter
|
||||
*/
|
||||
export declare class RateLimiter {
|
||||
private config;
|
||||
private requests;
|
||||
private cleanupInterval;
|
||||
constructor(config: RateLimitConfig);
|
||||
/**
|
||||
* Check if a request is allowed and update the rate limit
|
||||
*/
|
||||
checkLimit(identifier: string): Promise<RateLimitResult>;
|
||||
/**
|
||||
* Check rate limit using in-memory storage
|
||||
*/
|
||||
private checkMemoryLimit;
|
||||
/**
|
||||
* Check rate limit using distributed storage (S3/R2)
|
||||
*/
|
||||
private checkDistributedLimit;
|
||||
/**
|
||||
* Reset rate limit for a specific identifier
|
||||
*/
|
||||
reset(identifier: string): Promise<void>;
|
||||
/**
|
||||
* Start cleanup interval to remove expired entries
|
||||
*/
|
||||
private startCleanup;
|
||||
/**
|
||||
* Stop the rate limiter and cleanup
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
/**
|
||||
* Express/Connect middleware for rate limiting
|
||||
*/
|
||||
export declare function rateLimitMiddleware(config: RateLimitConfig): (req: any, res: any, next: any) => Promise<void>;
|
||||
/**
|
||||
* Create a rate limiter for use with Brainy
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // For single instance (in-memory)
|
||||
* const limiter = createRateLimiter({
|
||||
* maxRequests: 100,
|
||||
* windowMs: 15 * 60 * 1000 // 15 minutes
|
||||
* })
|
||||
*
|
||||
* // For distributed (using S3/R2)
|
||||
* const limiter = createRateLimiter({
|
||||
* maxRequests: 100,
|
||||
* windowMs: 15 * 60 * 1000,
|
||||
* distributed: true,
|
||||
* storage: myS3Adapter
|
||||
* })
|
||||
*
|
||||
* // Check rate limit
|
||||
* const result = await limiter.checkLimit('user-123')
|
||||
* if (!result.allowed) {
|
||||
* throw new Error('Rate limit exceeded')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function createRateLimiter(config: RateLimitConfig): RateLimiter;
|
||||
/**
|
||||
* Preset configurations for common use cases
|
||||
*/
|
||||
export declare const RateLimitPresets: {
|
||||
/**
|
||||
* Default API rate limit: 100 requests per 15 minutes
|
||||
*/
|
||||
default: {
|
||||
maxRequests: number;
|
||||
windowMs: number;
|
||||
};
|
||||
/**
|
||||
* Strict rate limit: 10 requests per minute
|
||||
*/
|
||||
strict: {
|
||||
maxRequests: number;
|
||||
windowMs: number;
|
||||
};
|
||||
/**
|
||||
* Lenient rate limit: 1000 requests per hour
|
||||
*/
|
||||
lenient: {
|
||||
maxRequests: number;
|
||||
windowMs: number;
|
||||
};
|
||||
/**
|
||||
* Search endpoint: 30 requests per minute
|
||||
*/
|
||||
search: {
|
||||
maxRequests: number;
|
||||
windowMs: number;
|
||||
message: string;
|
||||
};
|
||||
/**
|
||||
* Write operations: 20 requests per minute
|
||||
*/
|
||||
write: {
|
||||
maxRequests: number;
|
||||
windowMs: number;
|
||||
message: string;
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
/**
|
||||
* Rate Limiter for Brainy API
|
||||
*
|
||||
* Provides rate limiting without external dependencies like Redis.
|
||||
* - Uses in-memory storage for single instances
|
||||
* - Can use S3/R2 for distributed rate limiting
|
||||
*
|
||||
* @module rateLimiter
|
||||
*/
|
||||
/**
|
||||
* Simple in-memory rate limiter
|
||||
*/
|
||||
export class RateLimiter {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.requests = new Map();
|
||||
this.cleanupInterval = null;
|
||||
// Start cleanup interval to remove expired entries
|
||||
this.startCleanup();
|
||||
}
|
||||
/**
|
||||
* Check if a request is allowed and update the rate limit
|
||||
*/
|
||||
async checkLimit(identifier) {
|
||||
const now = Date.now();
|
||||
if (this.config.distributed && this.config.storage) {
|
||||
return this.checkDistributedLimit(identifier, now);
|
||||
}
|
||||
return this.checkMemoryLimit(identifier, now);
|
||||
}
|
||||
/**
|
||||
* Check rate limit using in-memory storage
|
||||
*/
|
||||
checkMemoryLimit(identifier, now) {
|
||||
const entry = this.requests.get(identifier);
|
||||
const resetTime = now + this.config.windowMs;
|
||||
if (!entry || entry.resetTime <= now) {
|
||||
// New window or expired window
|
||||
this.requests.set(identifier, {
|
||||
count: 1,
|
||||
resetTime
|
||||
});
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: this.config.maxRequests - 1,
|
||||
resetTime,
|
||||
limit: this.config.maxRequests
|
||||
};
|
||||
}
|
||||
// Existing window
|
||||
if (entry.count < this.config.maxRequests) {
|
||||
entry.count++;
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: this.config.maxRequests - entry.count,
|
||||
resetTime: entry.resetTime,
|
||||
limit: this.config.maxRequests
|
||||
};
|
||||
}
|
||||
// Rate limit exceeded
|
||||
return {
|
||||
allowed: false,
|
||||
remaining: 0,
|
||||
resetTime: entry.resetTime,
|
||||
limit: this.config.maxRequests
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Check rate limit using distributed storage (S3/R2)
|
||||
*/
|
||||
async checkDistributedLimit(identifier, now) {
|
||||
const storage = this.config.storage;
|
||||
const key = `ratelimit_${identifier}`;
|
||||
const resetTime = now + this.config.windowMs;
|
||||
try {
|
||||
// Try to get existing rate limit data from metadata storage
|
||||
const existing = await storage.getMetadata(key);
|
||||
if (!existing || !existing.resetTime ||
|
||||
Number(existing.resetTime) <= now) {
|
||||
// New window or expired window
|
||||
await storage.saveMetadata(key, {
|
||||
count: 1,
|
||||
resetTime: resetTime,
|
||||
identifier
|
||||
});
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: this.config.maxRequests - 1,
|
||||
resetTime,
|
||||
limit: this.config.maxRequests
|
||||
};
|
||||
}
|
||||
const count = Number(existing.count || 0);
|
||||
if (count < this.config.maxRequests) {
|
||||
// Update count
|
||||
await storage.saveMetadata(key, {
|
||||
count: count + 1,
|
||||
resetTime: existing.resetTime,
|
||||
identifier
|
||||
});
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: this.config.maxRequests - count - 1,
|
||||
resetTime: Number(existing.resetTime),
|
||||
limit: this.config.maxRequests
|
||||
};
|
||||
}
|
||||
// Rate limit exceeded
|
||||
return {
|
||||
allowed: false,
|
||||
remaining: 0,
|
||||
resetTime: Number(existing.resetTime),
|
||||
limit: this.config.maxRequests
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
// On error, fail open (allow the request)
|
||||
console.warn('Rate limiter error, failing open:', error);
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: this.config.maxRequests,
|
||||
resetTime,
|
||||
limit: this.config.maxRequests
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reset rate limit for a specific identifier
|
||||
*/
|
||||
async reset(identifier) {
|
||||
if (this.config.distributed && this.config.storage) {
|
||||
const key = `ratelimit_${identifier}`;
|
||||
// Reset by setting count to 0 and expired time
|
||||
await this.config.storage.saveMetadata(key, {
|
||||
count: 0,
|
||||
resetTime: 0,
|
||||
identifier
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.requests.delete(identifier);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start cleanup interval to remove expired entries
|
||||
*/
|
||||
startCleanup() {
|
||||
// Run cleanup every minute
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
const expired = [];
|
||||
for (const [key, entry] of this.requests) {
|
||||
if (entry.resetTime <= now) {
|
||||
expired.push(key);
|
||||
}
|
||||
}
|
||||
for (const key of expired) {
|
||||
this.requests.delete(key);
|
||||
}
|
||||
}, 60000); // 1 minute
|
||||
// Don't keep Node.js process alive just for cleanup
|
||||
if (this.cleanupInterval.unref) {
|
||||
this.cleanupInterval.unref();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Stop the rate limiter and cleanup
|
||||
*/
|
||||
destroy() {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = null;
|
||||
}
|
||||
this.requests.clear();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Express/Connect middleware for rate limiting
|
||||
*/
|
||||
export function rateLimitMiddleware(config) {
|
||||
const limiter = new RateLimiter(config);
|
||||
return async (req, res, next) => {
|
||||
// Use IP address as identifier (can be customized)
|
||||
const identifier = req.ip || req.connection?.remoteAddress || 'unknown';
|
||||
const result = await limiter.checkLimit(identifier);
|
||||
// Set rate limit headers
|
||||
res.setHeader('X-RateLimit-Limit', result.limit);
|
||||
res.setHeader('X-RateLimit-Remaining', result.remaining);
|
||||
res.setHeader('X-RateLimit-Reset', result.resetTime);
|
||||
if (!result.allowed) {
|
||||
res.status(429).json({
|
||||
error: config.message || 'Too many requests, please try again later.',
|
||||
retryAfter: Math.ceil((result.resetTime - Date.now()) / 1000)
|
||||
});
|
||||
return;
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Create a rate limiter for use with Brainy
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // For single instance (in-memory)
|
||||
* const limiter = createRateLimiter({
|
||||
* maxRequests: 100,
|
||||
* windowMs: 15 * 60 * 1000 // 15 minutes
|
||||
* })
|
||||
*
|
||||
* // For distributed (using S3/R2)
|
||||
* const limiter = createRateLimiter({
|
||||
* maxRequests: 100,
|
||||
* windowMs: 15 * 60 * 1000,
|
||||
* distributed: true,
|
||||
* storage: myS3Adapter
|
||||
* })
|
||||
*
|
||||
* // Check rate limit
|
||||
* const result = await limiter.checkLimit('user-123')
|
||||
* if (!result.allowed) {
|
||||
* throw new Error('Rate limit exceeded')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function createRateLimiter(config) {
|
||||
return new RateLimiter(config);
|
||||
}
|
||||
/**
|
||||
* Preset configurations for common use cases
|
||||
*/
|
||||
export const RateLimitPresets = {
|
||||
/**
|
||||
* Default API rate limit: 100 requests per 15 minutes
|
||||
*/
|
||||
default: {
|
||||
maxRequests: 100,
|
||||
windowMs: 15 * 60 * 1000
|
||||
},
|
||||
/**
|
||||
* Strict rate limit: 10 requests per minute
|
||||
*/
|
||||
strict: {
|
||||
maxRequests: 10,
|
||||
windowMs: 60 * 1000
|
||||
},
|
||||
/**
|
||||
* Lenient rate limit: 1000 requests per hour
|
||||
*/
|
||||
lenient: {
|
||||
maxRequests: 1000,
|
||||
windowMs: 60 * 60 * 1000
|
||||
},
|
||||
/**
|
||||
* Search endpoint: 30 requests per minute
|
||||
*/
|
||||
search: {
|
||||
maxRequests: 30,
|
||||
windowMs: 60 * 1000,
|
||||
message: 'Search rate limit exceeded. Please wait before searching again.'
|
||||
},
|
||||
/**
|
||||
* Write operations: 20 requests per minute
|
||||
*/
|
||||
write: {
|
||||
maxRequests: 20,
|
||||
windowMs: 60 * 1000,
|
||||
message: 'Write rate limit exceeded. Please slow down your write operations.'
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=rateLimiter.js.map
|
||||
File diff suppressed because one or more lines are too long
91
.recovery-workspace/dist-backup-20250910-141917/utils/requestCoalescer.d.ts
vendored
Normal file
91
.recovery-workspace/dist-backup-20250910-141917/utils/requestCoalescer.d.ts
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Request Coalescer
|
||||
* Batches and deduplicates operations to reduce S3 API calls
|
||||
* Automatically flushes based on size, time, or pressure
|
||||
*/
|
||||
interface CoalescedOperation {
|
||||
type: 'write' | 'read' | 'delete';
|
||||
key: string;
|
||||
data?: any;
|
||||
resolve: (value: any) => void;
|
||||
reject: (error: any) => void;
|
||||
timestamp: number;
|
||||
}
|
||||
interface BatchStats {
|
||||
totalOperations: number;
|
||||
coalescedOperations: number;
|
||||
deduplicated: number;
|
||||
batchesProcessed: number;
|
||||
averageBatchSize: number;
|
||||
}
|
||||
/**
|
||||
* Coalesces multiple operations into efficient batches
|
||||
*/
|
||||
export declare class RequestCoalescer {
|
||||
private logger;
|
||||
private writeQueue;
|
||||
private readQueue;
|
||||
private deleteQueue;
|
||||
private maxBatchSize;
|
||||
private maxBatchAge;
|
||||
private minBatchSize;
|
||||
private flushTimer;
|
||||
private lastFlush;
|
||||
private stats;
|
||||
private processor;
|
||||
constructor(processor: (batch: CoalescedOperation[]) => Promise<void>, options?: {
|
||||
maxBatchSize?: number;
|
||||
maxBatchAge?: number;
|
||||
minBatchSize?: number;
|
||||
});
|
||||
/**
|
||||
* Add a write operation to be coalesced
|
||||
*/
|
||||
write(key: string, data: any): Promise<void>;
|
||||
/**
|
||||
* Add a read operation to be coalesced
|
||||
*/
|
||||
read(key: string): Promise<any>;
|
||||
/**
|
||||
* Add a delete operation to be coalesced
|
||||
*/
|
||||
delete(key: string): Promise<void>;
|
||||
/**
|
||||
* Check if we should flush the queues
|
||||
*/
|
||||
private checkFlush;
|
||||
/**
|
||||
* Flush all queued operations
|
||||
*/
|
||||
flush(reason?: string): Promise<void>;
|
||||
/**
|
||||
* Get current statistics
|
||||
*/
|
||||
getStats(): BatchStats;
|
||||
/**
|
||||
* Get current queue sizes
|
||||
*/
|
||||
getQueueSizes(): {
|
||||
writes: number;
|
||||
reads: number;
|
||||
deletes: number;
|
||||
total: number;
|
||||
};
|
||||
/**
|
||||
* Adjust batch parameters based on load
|
||||
*/
|
||||
adjustParameters(pending: number): void;
|
||||
/**
|
||||
* Force immediate flush of all operations
|
||||
*/
|
||||
forceFlush(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Get or create a coalescer for a storage instance
|
||||
*/
|
||||
export declare function getCoalescer(storageId: string, processor: (batch: any[]) => Promise<void>): RequestCoalescer;
|
||||
/**
|
||||
* Clear all coalescers
|
||||
*/
|
||||
export declare function clearCoalescers(): void;
|
||||
export {};
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
/**
|
||||
* Request Coalescer
|
||||
* Batches and deduplicates operations to reduce S3 API calls
|
||||
* Automatically flushes based on size, time, or pressure
|
||||
*/
|
||||
import { createModuleLogger } from './logger.js';
|
||||
/**
|
||||
* Coalesces multiple operations into efficient batches
|
||||
*/
|
||||
export class RequestCoalescer {
|
||||
constructor(processor, options) {
|
||||
this.logger = createModuleLogger('RequestCoalescer');
|
||||
// Operation queues by type
|
||||
this.writeQueue = new Map();
|
||||
this.readQueue = new Map();
|
||||
this.deleteQueue = new Map();
|
||||
// Batch configuration
|
||||
this.maxBatchSize = 100;
|
||||
this.maxBatchAge = 100; // ms - flush quickly under load
|
||||
this.minBatchSize = 10; // Don't flush until we have enough
|
||||
// Flush timers
|
||||
this.flushTimer = null;
|
||||
this.lastFlush = Date.now();
|
||||
// Statistics
|
||||
this.stats = {
|
||||
totalOperations: 0,
|
||||
coalescedOperations: 0,
|
||||
deduplicated: 0,
|
||||
batchesProcessed: 0,
|
||||
averageBatchSize: 0
|
||||
};
|
||||
this.processor = processor;
|
||||
if (options) {
|
||||
this.maxBatchSize = options.maxBatchSize || this.maxBatchSize;
|
||||
this.maxBatchAge = options.maxBatchAge || this.maxBatchAge;
|
||||
this.minBatchSize = options.minBatchSize || this.minBatchSize;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add a write operation to be coalesced
|
||||
*/
|
||||
async write(key, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Check if we already have a pending write for this key
|
||||
const existing = this.writeQueue.get(key);
|
||||
if (existing && existing.length > 0) {
|
||||
// Replace the data but resolve all promises
|
||||
const last = existing[existing.length - 1];
|
||||
last.data = data; // Use latest data
|
||||
// Add this promise to be resolved
|
||||
existing.push({
|
||||
type: 'write',
|
||||
key,
|
||||
data,
|
||||
resolve,
|
||||
reject,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
this.stats.deduplicated++;
|
||||
}
|
||||
else {
|
||||
// New write operation
|
||||
this.writeQueue.set(key, [{
|
||||
type: 'write',
|
||||
key,
|
||||
data,
|
||||
resolve,
|
||||
reject,
|
||||
timestamp: Date.now()
|
||||
}]);
|
||||
}
|
||||
this.stats.totalOperations++;
|
||||
this.checkFlush();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add a read operation to be coalesced
|
||||
*/
|
||||
async read(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Check if we already have a pending read for this key
|
||||
const existing = this.readQueue.get(key);
|
||||
if (existing && existing.length > 0) {
|
||||
// Coalesce with existing read
|
||||
existing.push({
|
||||
type: 'read',
|
||||
key,
|
||||
resolve,
|
||||
reject,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
this.stats.deduplicated++;
|
||||
}
|
||||
else {
|
||||
// New read operation
|
||||
this.readQueue.set(key, [{
|
||||
type: 'read',
|
||||
key,
|
||||
resolve,
|
||||
reject,
|
||||
timestamp: Date.now()
|
||||
}]);
|
||||
}
|
||||
this.stats.totalOperations++;
|
||||
this.checkFlush();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add a delete operation to be coalesced
|
||||
*/
|
||||
async delete(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Cancel any pending writes for this key
|
||||
if (this.writeQueue.has(key)) {
|
||||
const writes = this.writeQueue.get(key);
|
||||
writes.forEach(op => op.reject(new Error('Cancelled by delete')));
|
||||
this.writeQueue.delete(key);
|
||||
this.stats.deduplicated += writes.length;
|
||||
}
|
||||
// Cancel any pending reads for this key
|
||||
if (this.readQueue.has(key)) {
|
||||
const reads = this.readQueue.get(key);
|
||||
reads.forEach(op => op.resolve(null)); // Return null for deleted items
|
||||
this.readQueue.delete(key);
|
||||
this.stats.deduplicated += reads.length;
|
||||
}
|
||||
// Check if we already have a pending delete
|
||||
const existing = this.deleteQueue.get(key);
|
||||
if (existing && existing.length > 0) {
|
||||
// Coalesce with existing delete
|
||||
existing.push({
|
||||
type: 'delete',
|
||||
key,
|
||||
resolve,
|
||||
reject,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
this.stats.deduplicated++;
|
||||
}
|
||||
else {
|
||||
// New delete operation
|
||||
this.deleteQueue.set(key, [{
|
||||
type: 'delete',
|
||||
key,
|
||||
resolve,
|
||||
reject,
|
||||
timestamp: Date.now()
|
||||
}]);
|
||||
}
|
||||
this.stats.totalOperations++;
|
||||
this.checkFlush();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Check if we should flush the queues
|
||||
*/
|
||||
checkFlush() {
|
||||
const totalSize = this.writeQueue.size + this.readQueue.size + this.deleteQueue.size;
|
||||
const now = Date.now();
|
||||
const age = now - this.lastFlush;
|
||||
// Immediate flush conditions
|
||||
if (totalSize >= this.maxBatchSize) {
|
||||
this.flush('size_limit');
|
||||
return;
|
||||
}
|
||||
// Age-based flush
|
||||
if (age >= this.maxBatchAge && totalSize >= this.minBatchSize) {
|
||||
this.flush('age_limit');
|
||||
return;
|
||||
}
|
||||
// Schedule a flush if not already scheduled
|
||||
if (!this.flushTimer && totalSize > 0) {
|
||||
const delay = Math.max(10, this.maxBatchAge - age);
|
||||
this.flushTimer = setTimeout(() => {
|
||||
this.flush('timer');
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Flush all queued operations
|
||||
*/
|
||||
async flush(reason = 'manual') {
|
||||
// Clear timer
|
||||
if (this.flushTimer) {
|
||||
clearTimeout(this.flushTimer);
|
||||
this.flushTimer = null;
|
||||
}
|
||||
// Collect all operations into a single batch
|
||||
const batch = [];
|
||||
// Process deletes first (highest priority)
|
||||
this.deleteQueue.forEach((ops) => {
|
||||
// Only take the first operation per key (others are duplicates)
|
||||
if (ops.length > 0) {
|
||||
batch.push(ops[0]);
|
||||
this.stats.coalescedOperations += ops.length;
|
||||
}
|
||||
});
|
||||
// Then writes
|
||||
this.writeQueue.forEach((ops) => {
|
||||
if (ops.length > 0) {
|
||||
// Use the last write (most recent data)
|
||||
const lastWrite = ops[ops.length - 1];
|
||||
batch.push(lastWrite);
|
||||
this.stats.coalescedOperations += ops.length;
|
||||
}
|
||||
});
|
||||
// Then reads
|
||||
this.readQueue.forEach((ops) => {
|
||||
if (ops.length > 0) {
|
||||
batch.push(ops[0]);
|
||||
this.stats.coalescedOperations += ops.length;
|
||||
}
|
||||
});
|
||||
// Clear queues
|
||||
const allOps = [
|
||||
...Array.from(this.deleteQueue.values()).flat(),
|
||||
...Array.from(this.writeQueue.values()).flat(),
|
||||
...Array.from(this.readQueue.values()).flat()
|
||||
];
|
||||
this.deleteQueue.clear();
|
||||
this.writeQueue.clear();
|
||||
this.readQueue.clear();
|
||||
if (batch.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Update stats
|
||||
this.stats.batchesProcessed++;
|
||||
this.stats.averageBatchSize =
|
||||
(this.stats.averageBatchSize * (this.stats.batchesProcessed - 1) + batch.length) /
|
||||
this.stats.batchesProcessed;
|
||||
this.logger.debug(`Flushing batch of ${batch.length} operations (${allOps.length} total) - reason: ${reason}`);
|
||||
// Process the batch
|
||||
try {
|
||||
await this.processor(batch);
|
||||
// Resolve all promises
|
||||
allOps.forEach(op => {
|
||||
if (op.type === 'read') {
|
||||
// Find the result for this read
|
||||
const result = batch.find(b => b.key === op.key && b.type === 'read');
|
||||
op.resolve(result?.data || null);
|
||||
}
|
||||
else {
|
||||
op.resolve(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
// Reject all promises
|
||||
allOps.forEach(op => op.reject(error));
|
||||
this.logger.error('Batch processing failed:', error);
|
||||
}
|
||||
this.lastFlush = Date.now();
|
||||
}
|
||||
/**
|
||||
* Get current statistics
|
||||
*/
|
||||
getStats() {
|
||||
return { ...this.stats };
|
||||
}
|
||||
/**
|
||||
* Get current queue sizes
|
||||
*/
|
||||
getQueueSizes() {
|
||||
return {
|
||||
writes: this.writeQueue.size,
|
||||
reads: this.readQueue.size,
|
||||
deletes: this.deleteQueue.size,
|
||||
total: this.writeQueue.size + this.readQueue.size + this.deleteQueue.size
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Adjust batch parameters based on load
|
||||
*/
|
||||
adjustParameters(pending) {
|
||||
if (pending > 10000) {
|
||||
// Extreme load - batch aggressively
|
||||
this.maxBatchSize = 500;
|
||||
this.maxBatchAge = 50;
|
||||
this.minBatchSize = 50;
|
||||
}
|
||||
else if (pending > 1000) {
|
||||
// High load - larger batches
|
||||
this.maxBatchSize = 200;
|
||||
this.maxBatchAge = 100;
|
||||
this.minBatchSize = 20;
|
||||
}
|
||||
else if (pending > 100) {
|
||||
// Moderate load
|
||||
this.maxBatchSize = 100;
|
||||
this.maxBatchAge = 200;
|
||||
this.minBatchSize = 10;
|
||||
}
|
||||
else {
|
||||
// Low load - optimize for latency
|
||||
this.maxBatchSize = 50;
|
||||
this.maxBatchAge = 500;
|
||||
this.minBatchSize = 5;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Force immediate flush of all operations
|
||||
*/
|
||||
async forceFlush() {
|
||||
await this.flush('force');
|
||||
}
|
||||
}
|
||||
// Global coalescer instances by storage type
|
||||
const coalescers = new Map();
|
||||
/**
|
||||
* Get or create a coalescer for a storage instance
|
||||
*/
|
||||
export function getCoalescer(storageId, processor) {
|
||||
if (!coalescers.has(storageId)) {
|
||||
coalescers.set(storageId, new RequestCoalescer(processor));
|
||||
}
|
||||
return coalescers.get(storageId);
|
||||
}
|
||||
/**
|
||||
* Clear all coalescers
|
||||
*/
|
||||
export function clearCoalescers() {
|
||||
coalescers.clear();
|
||||
}
|
||||
//# sourceMappingURL=requestCoalescer.js.map
|
||||
File diff suppressed because one or more lines are too long
10
.recovery-workspace/dist-backup-20250910-141917/utils/requestDeduplicator.d.ts
vendored
Normal file
10
.recovery-workspace/dist-backup-20250910-141917/utils/requestDeduplicator.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Request Deduplicator Utility
|
||||
* Provides key generation for request deduplication
|
||||
*/
|
||||
export declare class RequestDeduplicator {
|
||||
/**
|
||||
* Generate a unique key for search requests to enable deduplication
|
||||
*/
|
||||
static getSearchKey(query: string, k: number, options: any): string;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Request Deduplicator Utility
|
||||
* Provides key generation for request deduplication
|
||||
*/
|
||||
export class RequestDeduplicator {
|
||||
/**
|
||||
* Generate a unique key for search requests to enable deduplication
|
||||
*/
|
||||
static getSearchKey(query, k, options) {
|
||||
// Create a consistent key from search parameters
|
||||
const optionsKey = options ? JSON.stringify({
|
||||
metadata: options.metadata,
|
||||
service: options.service,
|
||||
searchMode: options.searchMode,
|
||||
threshold: options.threshold,
|
||||
includeVectors: options.includeVectors,
|
||||
includeMetadata: options.includeMetadata,
|
||||
sortBy: options.sortBy,
|
||||
cursor: options.cursor
|
||||
}) : '{}';
|
||||
return `search:${query}:${k}:${optionsKey}`;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=requestDeduplicator.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"requestDeduplicator.js","sourceRoot":"","sources":["../../src/utils/requestDeduplicator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,mBAAmB;IAC9B;;OAEG;IACH,MAAM,CAAC,YAAY,CACjB,KAAa,EACb,CAAS,EACT,OAAY;QAEZ,iDAAiD;QACjD,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;YAC1C,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAET,OAAO,UAAU,KAAK,IAAI,CAAC,IAAI,UAAU,EAAE,CAAA;IAC7C,CAAC;CACF"}
|
||||
93
.recovery-workspace/dist-backup-20250910-141917/utils/searchCache.d.ts
vendored
Normal file
93
.recovery-workspace/dist-backup-20250910-141917/utils/searchCache.d.ts
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* SearchCache - Caches search results for improved performance
|
||||
*/
|
||||
import { SearchResult } from '../coreTypes.js';
|
||||
export interface CacheEntry<T = any> {
|
||||
results: SearchResult<T>[];
|
||||
timestamp: number;
|
||||
hits: number;
|
||||
}
|
||||
export interface SearchCacheConfig {
|
||||
maxAge?: number;
|
||||
maxSize?: number;
|
||||
enabled?: boolean;
|
||||
hitCountWeight?: number;
|
||||
}
|
||||
export declare class SearchCache<T = any> {
|
||||
private cache;
|
||||
private maxAge;
|
||||
private maxSize;
|
||||
private enabled;
|
||||
private hitCountWeight;
|
||||
private hits;
|
||||
private misses;
|
||||
private evictions;
|
||||
constructor(config?: SearchCacheConfig);
|
||||
/**
|
||||
* Generate cache key from search parameters
|
||||
*/
|
||||
getCacheKey(query: any, k: number, options?: Record<string, any>): string;
|
||||
/**
|
||||
* Get cached results if available and not expired
|
||||
*/
|
||||
get(key: string): SearchResult<T>[] | null;
|
||||
/**
|
||||
* Cache search results
|
||||
*/
|
||||
set(key: string, results: SearchResult<T>[]): void;
|
||||
/**
|
||||
* Evict the oldest entry based on timestamp and hit count
|
||||
*/
|
||||
private evictOldest;
|
||||
/**
|
||||
* Clear all cached results
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Invalidate cache entries that might be affected by data changes
|
||||
*/
|
||||
invalidate(pattern?: string | RegExp): void;
|
||||
/**
|
||||
* Smart invalidation for real-time data updates
|
||||
* Only clears cache if it's getting stale or if data changes significantly
|
||||
*/
|
||||
invalidateOnDataChange(changeType?: 'add' | 'update' | 'delete'): void;
|
||||
/**
|
||||
* Check if cache entries have expired and remove them
|
||||
* This is especially important in distributed scenarios where
|
||||
* real-time updates might be delayed or missed
|
||||
*/
|
||||
cleanupExpiredEntries(): number;
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats(): {
|
||||
hits: number;
|
||||
misses: number;
|
||||
evictions: number;
|
||||
hitRate: number;
|
||||
size: number;
|
||||
maxSize: number;
|
||||
enabled: boolean;
|
||||
};
|
||||
/**
|
||||
* Enable or disable caching
|
||||
*/
|
||||
setEnabled(enabled: boolean): void;
|
||||
/**
|
||||
* Get memory usage estimate in bytes
|
||||
*/
|
||||
getMemoryUsage(): number;
|
||||
/**
|
||||
* Get current cache configuration
|
||||
*/
|
||||
getConfig(): SearchCacheConfig;
|
||||
/**
|
||||
* Update cache configuration dynamically
|
||||
*/
|
||||
updateConfig(newConfig: Partial<SearchCacheConfig>): void;
|
||||
/**
|
||||
* Evict entries if cache exceeds maxSize
|
||||
*/
|
||||
private evictIfNeeded;
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* SearchCache - Caches search results for improved performance
|
||||
*/
|
||||
export class SearchCache {
|
||||
constructor(config = {}) {
|
||||
this.cache = new Map();
|
||||
// Cache statistics
|
||||
this.hits = 0;
|
||||
this.misses = 0;
|
||||
this.evictions = 0;
|
||||
this.maxAge = config.maxAge ?? 5 * 60 * 1000; // 5 minutes
|
||||
this.maxSize = config.maxSize ?? 100;
|
||||
this.enabled = config.enabled ?? true;
|
||||
this.hitCountWeight = config.hitCountWeight ?? 0.3;
|
||||
}
|
||||
/**
|
||||
* Generate cache key from search parameters
|
||||
*/
|
||||
getCacheKey(query, k, options = {}) {
|
||||
// Create a normalized key that ignores order of options
|
||||
const normalizedOptions = Object.keys(options)
|
||||
.sort()
|
||||
.reduce((acc, key) => {
|
||||
// Skip cache-related options
|
||||
if (key === 'skipCache' || key === 'useStreaming')
|
||||
return acc;
|
||||
acc[key] = options[key];
|
||||
return acc;
|
||||
}, {});
|
||||
return JSON.stringify({
|
||||
query: typeof query === 'object' ? JSON.stringify(query) : query,
|
||||
k,
|
||||
...normalizedOptions
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get cached results if available and not expired
|
||||
*/
|
||||
get(key) {
|
||||
if (!this.enabled)
|
||||
return null;
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) {
|
||||
this.misses++;
|
||||
return null;
|
||||
}
|
||||
// Check if expired
|
||||
if (Date.now() - entry.timestamp > this.maxAge) {
|
||||
this.cache.delete(key);
|
||||
this.misses++;
|
||||
return null;
|
||||
}
|
||||
// Update hit count and statistics
|
||||
entry.hits++;
|
||||
this.hits++;
|
||||
return entry.results;
|
||||
}
|
||||
/**
|
||||
* Cache search results
|
||||
*/
|
||||
set(key, results) {
|
||||
if (!this.enabled)
|
||||
return;
|
||||
// Evict if cache is full
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
this.evictOldest();
|
||||
}
|
||||
this.cache.set(key, {
|
||||
results: [...results], // Deep copy to prevent mutations
|
||||
timestamp: Date.now(),
|
||||
hits: 0
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Evict the oldest entry based on timestamp and hit count
|
||||
*/
|
||||
evictOldest() {
|
||||
let oldestKey = null;
|
||||
let oldestScore = Infinity;
|
||||
const now = Date.now();
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
// Score combines age and inverse hit count
|
||||
const age = now - entry.timestamp;
|
||||
const hitScore = entry.hits > 0 ? 1 / entry.hits : 1;
|
||||
const score = age + (hitScore * this.hitCountWeight * this.maxAge);
|
||||
if (score < oldestScore) {
|
||||
oldestScore = score;
|
||||
oldestKey = key;
|
||||
}
|
||||
}
|
||||
if (oldestKey) {
|
||||
this.cache.delete(oldestKey);
|
||||
this.evictions++;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clear all cached results
|
||||
*/
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
this.hits = 0;
|
||||
this.misses = 0;
|
||||
this.evictions = 0;
|
||||
}
|
||||
/**
|
||||
* Invalidate cache entries that might be affected by data changes
|
||||
*/
|
||||
invalidate(pattern) {
|
||||
if (!pattern) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
const keysToDelete = [];
|
||||
for (const key of this.cache.keys()) {
|
||||
const shouldDelete = typeof pattern === 'string'
|
||||
? key.includes(pattern)
|
||||
: pattern.test(key);
|
||||
if (shouldDelete) {
|
||||
keysToDelete.push(key);
|
||||
}
|
||||
}
|
||||
keysToDelete.forEach(key => this.cache.delete(key));
|
||||
}
|
||||
/**
|
||||
* Smart invalidation for real-time data updates
|
||||
* Only clears cache if it's getting stale or if data changes significantly
|
||||
*/
|
||||
invalidateOnDataChange(changeType) {
|
||||
// For now, clear all caches on data changes to ensure consistency
|
||||
// In the future, we could implement more sophisticated invalidation
|
||||
// based on the type of change and affected data
|
||||
this.clear();
|
||||
}
|
||||
/**
|
||||
* Check if cache entries have expired and remove them
|
||||
* This is especially important in distributed scenarios where
|
||||
* real-time updates might be delayed or missed
|
||||
*/
|
||||
cleanupExpiredEntries() {
|
||||
const now = Date.now();
|
||||
const keysToDelete = [];
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
if (now - entry.timestamp > this.maxAge) {
|
||||
keysToDelete.push(key);
|
||||
}
|
||||
}
|
||||
keysToDelete.forEach(key => this.cache.delete(key));
|
||||
return keysToDelete.length;
|
||||
}
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats() {
|
||||
const total = this.hits + this.misses;
|
||||
return {
|
||||
hits: this.hits,
|
||||
misses: this.misses,
|
||||
evictions: this.evictions,
|
||||
hitRate: total > 0 ? this.hits / total : 0,
|
||||
size: this.cache.size,
|
||||
maxSize: this.maxSize,
|
||||
enabled: this.enabled
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Enable or disable caching
|
||||
*/
|
||||
setEnabled(enabled) {
|
||||
Object.defineProperty(this, 'enabled', { value: enabled, writable: false });
|
||||
if (!enabled) {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get memory usage estimate in bytes
|
||||
*/
|
||||
getMemoryUsage() {
|
||||
let totalSize = 0;
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
// Estimate key size
|
||||
totalSize += key.length * 2; // UTF-16 characters
|
||||
// Estimate entry size
|
||||
totalSize += JSON.stringify(entry.results).length * 2;
|
||||
totalSize += 16; // timestamp + hits (8 bytes each)
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
/**
|
||||
* Get current cache configuration
|
||||
*/
|
||||
getConfig() {
|
||||
return {
|
||||
enabled: this.enabled,
|
||||
maxSize: this.maxSize,
|
||||
maxAge: this.maxAge,
|
||||
hitCountWeight: this.hitCountWeight
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Update cache configuration dynamically
|
||||
*/
|
||||
updateConfig(newConfig) {
|
||||
if (newConfig.enabled !== undefined) {
|
||||
this.enabled = newConfig.enabled;
|
||||
}
|
||||
if (newConfig.maxSize !== undefined) {
|
||||
this.maxSize = newConfig.maxSize;
|
||||
// Trigger eviction if current size exceeds new limit
|
||||
this.evictIfNeeded();
|
||||
}
|
||||
if (newConfig.maxAge !== undefined) {
|
||||
this.maxAge = newConfig.maxAge;
|
||||
// Clean up entries that are now expired with new TTL
|
||||
this.cleanupExpiredEntries();
|
||||
}
|
||||
if (newConfig.hitCountWeight !== undefined) {
|
||||
this.hitCountWeight = newConfig.hitCountWeight;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Evict entries if cache exceeds maxSize
|
||||
*/
|
||||
evictIfNeeded() {
|
||||
if (this.cache.size <= this.maxSize) {
|
||||
return;
|
||||
}
|
||||
// Calculate eviction score for each entry (same logic as existing eviction)
|
||||
const entries = Array.from(this.cache.entries()).map(([key, entry]) => {
|
||||
const age = Date.now() - entry.timestamp;
|
||||
const hitCount = entry.hits;
|
||||
// Eviction score: lower is more likely to be evicted
|
||||
// Combines age and hit count (weighted by hitCountWeight)
|
||||
const ageScore = age / this.maxAge;
|
||||
const hitScore = 1 / (hitCount + 1); // Inverse of hits (more hits = lower score)
|
||||
const score = ageScore * (1 - this.hitCountWeight) + hitScore * this.hitCountWeight;
|
||||
return { key, entry, score };
|
||||
});
|
||||
// Sort by score (lowest first - these will be evicted)
|
||||
entries.sort((a, b) => a.score - b.score);
|
||||
// Evict entries until we're under the limit
|
||||
const toEvict = entries.slice(0, this.cache.size - this.maxSize);
|
||||
toEvict.forEach(({ key }) => {
|
||||
this.cache.delete(key);
|
||||
this.evictions++;
|
||||
});
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=searchCache.js.map
|
||||
File diff suppressed because one or more lines are too long
28
.recovery-workspace/dist-backup-20250910-141917/utils/statistics.d.ts
vendored
Normal file
28
.recovery-workspace/dist-backup-20250910-141917/utils/statistics.d.ts
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Utility functions for retrieving statistics from Brainy
|
||||
*/
|
||||
import { Brainy } from '../brainy.js';
|
||||
/**
|
||||
* Get statistics about the current state of a Brainy instance
|
||||
* This function provides access to statistics at the root level of the library
|
||||
*
|
||||
* @param instance A Brainy instance to get statistics from
|
||||
* @param options Additional options for retrieving statistics
|
||||
* @returns Object containing counts of nouns, verbs, metadata entries, and HNSW index size
|
||||
* @throws Error if the instance is not provided or if statistics retrieval fails
|
||||
*/
|
||||
export declare function getStatistics(instance: Brainy, options?: {
|
||||
service?: string | string[];
|
||||
}): Promise<{
|
||||
nounCount: number;
|
||||
verbCount: number;
|
||||
metadataCount: number;
|
||||
hnswIndexSize: number;
|
||||
serviceBreakdown?: {
|
||||
[service: string]: {
|
||||
nounCount: number;
|
||||
verbCount: number;
|
||||
metadataCount: number;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Utility functions for retrieving statistics from Brainy
|
||||
*/
|
||||
/**
|
||||
* Get statistics about the current state of a Brainy instance
|
||||
* This function provides access to statistics at the root level of the library
|
||||
*
|
||||
* @param instance A Brainy instance to get statistics from
|
||||
* @param options Additional options for retrieving statistics
|
||||
* @returns Object containing counts of nouns, verbs, metadata entries, and HNSW index size
|
||||
* @throws Error if the instance is not provided or if statistics retrieval fails
|
||||
*/
|
||||
export async function getStatistics(instance, options = {}) {
|
||||
if (!instance) {
|
||||
throw new Error('Brainy instance must be provided to getStatistics');
|
||||
}
|
||||
try {
|
||||
return await instance.getStatistics(options);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to get statistics:', error);
|
||||
throw new Error(`Failed to get statistics: ${error}`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=statistics.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"statistics.js","sourceRoot":"","sources":["../../src/utils/statistics.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAC/B,QAAgB,EAChB,UAEI,EAAE;IAcN,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACxE,CAAC;IAED,IAAI,CAAC;QACD,OAAO,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAA;QACjD,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,EAAE,CAAC,CAAA;IACzD,CAAC;AACL,CAAC"}
|
||||
92
.recovery-workspace/dist-backup-20250910-141917/utils/statisticsCollector.d.ts
vendored
Normal file
92
.recovery-workspace/dist-backup-20250910-141917/utils/statisticsCollector.d.ts
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Lightweight statistics collector for Brainy
|
||||
* Designed to have minimal performance impact even with millions of entries
|
||||
*/
|
||||
import { StatisticsData } from '../coreTypes.js';
|
||||
export declare class StatisticsCollector {
|
||||
private contentTypes;
|
||||
private oldestTimestamp;
|
||||
private newestTimestamp;
|
||||
private updateTimestamps;
|
||||
private searchMetrics;
|
||||
private verbTypes;
|
||||
private storageSizeCache;
|
||||
private throttlingMetrics;
|
||||
private readonly MAX_TIMESTAMPS;
|
||||
private readonly MAX_SEARCH_TERMS;
|
||||
private readonly SIZE_UPDATE_INTERVAL;
|
||||
/**
|
||||
* Track content type (very lightweight)
|
||||
*/
|
||||
trackContentType(type: string): void;
|
||||
/**
|
||||
* Track data update timestamp (lightweight)
|
||||
*/
|
||||
trackUpdate(timestamp?: number): void;
|
||||
/**
|
||||
* Track search performance (lightweight)
|
||||
*/
|
||||
trackSearch(searchTerm: string, durationMs: number): void;
|
||||
/**
|
||||
* Track verb type (lightweight)
|
||||
*/
|
||||
trackVerbType(type: string): void;
|
||||
/**
|
||||
* Update storage size estimates (called periodically, not on every operation)
|
||||
*/
|
||||
updateStorageSizes(sizes: {
|
||||
nouns: number;
|
||||
verbs: number;
|
||||
metadata: number;
|
||||
index: number;
|
||||
}): void;
|
||||
/**
|
||||
* Track a throttling event
|
||||
*/
|
||||
trackThrottlingEvent(reason: string, service?: string): void;
|
||||
/**
|
||||
* Clear throttling state after successful operations
|
||||
*/
|
||||
clearThrottlingState(): void;
|
||||
/**
|
||||
* Track delayed operation
|
||||
*/
|
||||
trackDelayedOperation(delayMs: number): void;
|
||||
/**
|
||||
* Track retried operation
|
||||
*/
|
||||
trackRetriedOperation(): void;
|
||||
/**
|
||||
* Track operation failed due to throttling
|
||||
*/
|
||||
trackFailedDueToThrottling(): void;
|
||||
/**
|
||||
* Update throttling metrics from storage adapter
|
||||
*/
|
||||
updateThrottlingMetrics(metrics: {
|
||||
currentlyThrottled: boolean;
|
||||
lastThrottleTime: number;
|
||||
consecutiveThrottleEvents: number;
|
||||
currentBackoffMs: number;
|
||||
totalThrottleEvents: number;
|
||||
throttleEventsByHour: number[];
|
||||
throttleReasons: Record<string, number>;
|
||||
delayedOperations: number;
|
||||
retriedOperations: number;
|
||||
failedDueToThrottling: number;
|
||||
totalDelayMs: number;
|
||||
}): void;
|
||||
/**
|
||||
* Get comprehensive statistics
|
||||
*/
|
||||
getStatistics(): Partial<StatisticsData>;
|
||||
/**
|
||||
* Merge statistics from storage (for distributed systems)
|
||||
*/
|
||||
mergeFromStorage(stored: Partial<StatisticsData>): void;
|
||||
/**
|
||||
* Reset statistics (for testing)
|
||||
*/
|
||||
reset(): void;
|
||||
private pruneSearchTerms;
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
/**
|
||||
* Lightweight statistics collector for Brainy
|
||||
* Designed to have minimal performance impact even with millions of entries
|
||||
*/
|
||||
export class StatisticsCollector {
|
||||
constructor() {
|
||||
// Content type tracking (lightweight counters)
|
||||
this.contentTypes = new Map();
|
||||
// Data freshness tracking (only track timestamps, not full data)
|
||||
this.oldestTimestamp = Date.now();
|
||||
this.newestTimestamp = Date.now();
|
||||
this.updateTimestamps = [];
|
||||
// Search performance tracking (rolling window)
|
||||
this.searchMetrics = {
|
||||
totalSearches: 0,
|
||||
totalSearchTimeMs: 0,
|
||||
searchTimestamps: [],
|
||||
topSearchTerms: new Map()
|
||||
};
|
||||
// Verb type tracking
|
||||
this.verbTypes = new Map();
|
||||
// Storage size estimates (updated periodically, not on every operation)
|
||||
this.storageSizeCache = {
|
||||
lastUpdated: 0,
|
||||
sizes: {
|
||||
nouns: 0,
|
||||
verbs: 0,
|
||||
metadata: 0,
|
||||
index: 0
|
||||
}
|
||||
};
|
||||
// Throttling metrics
|
||||
this.throttlingMetrics = {
|
||||
currentlyThrottled: false,
|
||||
lastThrottleTime: 0,
|
||||
consecutiveThrottleEvents: 0,
|
||||
currentBackoffMs: 1000,
|
||||
totalThrottleEvents: 0,
|
||||
throttleEventsByHour: new Array(24).fill(0),
|
||||
throttleReasons: new Map(),
|
||||
delayedOperations: 0,
|
||||
retriedOperations: 0,
|
||||
failedDueToThrottling: 0,
|
||||
totalDelayMs: 0,
|
||||
serviceThrottling: new Map()
|
||||
};
|
||||
this.MAX_TIMESTAMPS = 1000; // Keep last 1000 timestamps
|
||||
this.MAX_SEARCH_TERMS = 100; // Track top 100 search terms
|
||||
this.SIZE_UPDATE_INTERVAL = 60000; // Update sizes every minute
|
||||
}
|
||||
/**
|
||||
* Track content type (very lightweight)
|
||||
*/
|
||||
trackContentType(type) {
|
||||
this.contentTypes.set(type, (this.contentTypes.get(type) || 0) + 1);
|
||||
}
|
||||
/**
|
||||
* Track data update timestamp (lightweight)
|
||||
*/
|
||||
trackUpdate(timestamp) {
|
||||
const ts = timestamp || Date.now();
|
||||
// Update oldest/newest
|
||||
if (ts < this.oldestTimestamp)
|
||||
this.oldestTimestamp = ts;
|
||||
if (ts > this.newestTimestamp)
|
||||
this.newestTimestamp = ts;
|
||||
// Add to rolling window
|
||||
this.updateTimestamps.push({ timestamp: ts, count: 1 });
|
||||
// Keep window size limited
|
||||
if (this.updateTimestamps.length > this.MAX_TIMESTAMPS) {
|
||||
this.updateTimestamps.shift();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Track search performance (lightweight)
|
||||
*/
|
||||
trackSearch(searchTerm, durationMs) {
|
||||
this.searchMetrics.totalSearches++;
|
||||
this.searchMetrics.totalSearchTimeMs += durationMs;
|
||||
// Add to rolling window
|
||||
this.searchMetrics.searchTimestamps.push({
|
||||
timestamp: Date.now(),
|
||||
count: 1
|
||||
});
|
||||
// Keep window size limited
|
||||
if (this.searchMetrics.searchTimestamps.length > this.MAX_TIMESTAMPS) {
|
||||
this.searchMetrics.searchTimestamps.shift();
|
||||
}
|
||||
// Track search term (limit to top N)
|
||||
const termCount = (this.searchMetrics.topSearchTerms.get(searchTerm) || 0) + 1;
|
||||
this.searchMetrics.topSearchTerms.set(searchTerm, termCount);
|
||||
// Prune if too many terms
|
||||
if (this.searchMetrics.topSearchTerms.size > this.MAX_SEARCH_TERMS * 2) {
|
||||
this.pruneSearchTerms();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Track verb type (lightweight)
|
||||
*/
|
||||
trackVerbType(type) {
|
||||
this.verbTypes.set(type, (this.verbTypes.get(type) || 0) + 1);
|
||||
}
|
||||
/**
|
||||
* Update storage size estimates (called periodically, not on every operation)
|
||||
*/
|
||||
updateStorageSizes(sizes) {
|
||||
this.storageSizeCache = {
|
||||
lastUpdated: Date.now(),
|
||||
sizes
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Track a throttling event
|
||||
*/
|
||||
trackThrottlingEvent(reason, service) {
|
||||
this.throttlingMetrics.currentlyThrottled = true;
|
||||
this.throttlingMetrics.consecutiveThrottleEvents++;
|
||||
this.throttlingMetrics.lastThrottleTime = Date.now();
|
||||
this.throttlingMetrics.totalThrottleEvents++;
|
||||
// Track by hour
|
||||
const hourIndex = new Date().getHours();
|
||||
this.throttlingMetrics.throttleEventsByHour[hourIndex]++;
|
||||
// Track reason
|
||||
const reasonCount = this.throttlingMetrics.throttleReasons.get(reason) || 0;
|
||||
this.throttlingMetrics.throttleReasons.set(reason, reasonCount + 1);
|
||||
// Track service-level throttling
|
||||
if (service) {
|
||||
const serviceInfo = this.throttlingMetrics.serviceThrottling.get(service) || {
|
||||
throttleCount: 0,
|
||||
lastThrottle: 0,
|
||||
status: 'normal'
|
||||
};
|
||||
serviceInfo.throttleCount++;
|
||||
serviceInfo.lastThrottle = Date.now();
|
||||
serviceInfo.status = 'throttled';
|
||||
this.throttlingMetrics.serviceThrottling.set(service, serviceInfo);
|
||||
}
|
||||
// Exponential backoff
|
||||
this.throttlingMetrics.currentBackoffMs = Math.min(this.throttlingMetrics.currentBackoffMs * 2, 30000 // Max 30 seconds
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Clear throttling state after successful operations
|
||||
*/
|
||||
clearThrottlingState() {
|
||||
if (this.throttlingMetrics.consecutiveThrottleEvents > 0) {
|
||||
this.throttlingMetrics.consecutiveThrottleEvents = 0;
|
||||
this.throttlingMetrics.currentBackoffMs = 1000; // Reset to initial backoff
|
||||
this.throttlingMetrics.currentlyThrottled = false;
|
||||
// Update service statuses
|
||||
for (const [, info] of this.throttlingMetrics.serviceThrottling) {
|
||||
if (info.status === 'throttled') {
|
||||
info.status = 'recovering';
|
||||
}
|
||||
else if (info.status === 'recovering') {
|
||||
const timeSinceThrottle = Date.now() - info.lastThrottle;
|
||||
if (timeSinceThrottle > 60000) { // 1 minute recovery period
|
||||
info.status = 'normal';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Track delayed operation
|
||||
*/
|
||||
trackDelayedOperation(delayMs) {
|
||||
this.throttlingMetrics.delayedOperations++;
|
||||
this.throttlingMetrics.totalDelayMs += delayMs;
|
||||
}
|
||||
/**
|
||||
* Track retried operation
|
||||
*/
|
||||
trackRetriedOperation() {
|
||||
this.throttlingMetrics.retriedOperations++;
|
||||
}
|
||||
/**
|
||||
* Track operation failed due to throttling
|
||||
*/
|
||||
trackFailedDueToThrottling() {
|
||||
this.throttlingMetrics.failedDueToThrottling++;
|
||||
}
|
||||
/**
|
||||
* Update throttling metrics from storage adapter
|
||||
*/
|
||||
updateThrottlingMetrics(metrics) {
|
||||
this.throttlingMetrics.currentlyThrottled = metrics.currentlyThrottled;
|
||||
this.throttlingMetrics.lastThrottleTime = metrics.lastThrottleTime;
|
||||
this.throttlingMetrics.consecutiveThrottleEvents = metrics.consecutiveThrottleEvents;
|
||||
this.throttlingMetrics.currentBackoffMs = metrics.currentBackoffMs;
|
||||
this.throttlingMetrics.totalThrottleEvents = metrics.totalThrottleEvents;
|
||||
this.throttlingMetrics.throttleEventsByHour = [...metrics.throttleEventsByHour];
|
||||
// Update throttle reasons map
|
||||
this.throttlingMetrics.throttleReasons.clear();
|
||||
for (const [reason, count] of Object.entries(metrics.throttleReasons)) {
|
||||
this.throttlingMetrics.throttleReasons.set(reason, count);
|
||||
}
|
||||
this.throttlingMetrics.delayedOperations = metrics.delayedOperations;
|
||||
this.throttlingMetrics.retriedOperations = metrics.retriedOperations;
|
||||
this.throttlingMetrics.failedDueToThrottling = metrics.failedDueToThrottling;
|
||||
this.throttlingMetrics.totalDelayMs = metrics.totalDelayMs;
|
||||
}
|
||||
/**
|
||||
* Get comprehensive statistics
|
||||
*/
|
||||
getStatistics() {
|
||||
const now = Date.now();
|
||||
const hourAgo = now - 3600000;
|
||||
const dayAgo = now - 86400000;
|
||||
const weekAgo = now - 604800000;
|
||||
const monthAgo = now - 2592000000;
|
||||
// Calculate data freshness
|
||||
const updatesLastHour = this.updateTimestamps.filter(t => t.timestamp > hourAgo).length;
|
||||
const updatesLastDay = this.updateTimestamps.filter(t => t.timestamp > dayAgo).length;
|
||||
// Calculate age distribution
|
||||
const ageDistribution = {
|
||||
last24h: 0,
|
||||
last7d: 0,
|
||||
last30d: 0,
|
||||
older: 0
|
||||
};
|
||||
// Estimate based on update patterns (not scanning all data)
|
||||
const totalUpdates = this.updateTimestamps.length;
|
||||
if (totalUpdates > 0) {
|
||||
const recentUpdates = this.updateTimestamps.filter(t => t.timestamp > dayAgo).length;
|
||||
const weekUpdates = this.updateTimestamps.filter(t => t.timestamp > weekAgo).length;
|
||||
const monthUpdates = this.updateTimestamps.filter(t => t.timestamp > monthAgo).length;
|
||||
ageDistribution.last24h = Math.round((recentUpdates / totalUpdates) * 100);
|
||||
ageDistribution.last7d = Math.round(((weekUpdates - recentUpdates) / totalUpdates) * 100);
|
||||
ageDistribution.last30d = Math.round(((monthUpdates - weekUpdates) / totalUpdates) * 100);
|
||||
ageDistribution.older = 100 - ageDistribution.last24h - ageDistribution.last7d - ageDistribution.last30d;
|
||||
}
|
||||
// Calculate search metrics
|
||||
const searchesLastHour = this.searchMetrics.searchTimestamps.filter(t => t.timestamp > hourAgo).length;
|
||||
const searchesLastDay = this.searchMetrics.searchTimestamps.filter(t => t.timestamp > dayAgo).length;
|
||||
const avgSearchTime = this.searchMetrics.totalSearches > 0
|
||||
? this.searchMetrics.totalSearchTimeMs / this.searchMetrics.totalSearches
|
||||
: 0;
|
||||
// Get top search terms
|
||||
const topSearchTerms = Array.from(this.searchMetrics.topSearchTerms.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10)
|
||||
.map(([term]) => term);
|
||||
// Calculate storage metrics
|
||||
const totalSize = Object.values(this.storageSizeCache.sizes).reduce((a, b) => a + b, 0);
|
||||
// Calculate average delay for throttling
|
||||
const averageDelayMs = this.throttlingMetrics.delayedOperations > 0
|
||||
? this.throttlingMetrics.totalDelayMs / this.throttlingMetrics.delayedOperations
|
||||
: 0;
|
||||
// Convert service throttling map to record
|
||||
const serviceThrottlingRecord = {};
|
||||
for (const [service, info] of this.throttlingMetrics.serviceThrottling) {
|
||||
serviceThrottlingRecord[service] = {
|
||||
throttleCount: info.throttleCount,
|
||||
lastThrottle: new Date(info.lastThrottle).toISOString(),
|
||||
status: info.status
|
||||
};
|
||||
}
|
||||
return {
|
||||
contentTypes: Object.fromEntries(this.contentTypes),
|
||||
dataFreshness: {
|
||||
oldestEntry: new Date(this.oldestTimestamp).toISOString(),
|
||||
newestEntry: new Date(this.newestTimestamp).toISOString(),
|
||||
updatesLastHour,
|
||||
updatesLastDay,
|
||||
ageDistribution
|
||||
},
|
||||
storageMetrics: {
|
||||
totalSizeBytes: totalSize,
|
||||
nounsSizeBytes: this.storageSizeCache.sizes.nouns,
|
||||
verbsSizeBytes: this.storageSizeCache.sizes.verbs,
|
||||
metadataSizeBytes: this.storageSizeCache.sizes.metadata,
|
||||
indexSizeBytes: this.storageSizeCache.sizes.index
|
||||
},
|
||||
searchMetrics: {
|
||||
totalSearches: this.searchMetrics.totalSearches,
|
||||
averageSearchTimeMs: avgSearchTime,
|
||||
searchesLastHour,
|
||||
searchesLastDay,
|
||||
topSearchTerms
|
||||
},
|
||||
verbStatistics: {
|
||||
totalVerbs: Array.from(this.verbTypes.values()).reduce((a, b) => a + b, 0),
|
||||
verbTypes: Object.fromEntries(this.verbTypes),
|
||||
averageConnectionsPerVerb: 2 // Verbs connect 2 nouns
|
||||
},
|
||||
throttlingMetrics: {
|
||||
storage: {
|
||||
currentlyThrottled: this.throttlingMetrics.currentlyThrottled,
|
||||
lastThrottleTime: this.throttlingMetrics.lastThrottleTime > 0
|
||||
? new Date(this.throttlingMetrics.lastThrottleTime).toISOString()
|
||||
: undefined,
|
||||
consecutiveThrottleEvents: this.throttlingMetrics.consecutiveThrottleEvents,
|
||||
currentBackoffMs: this.throttlingMetrics.currentBackoffMs,
|
||||
totalThrottleEvents: this.throttlingMetrics.totalThrottleEvents,
|
||||
throttleEventsByHour: [...this.throttlingMetrics.throttleEventsByHour],
|
||||
throttleReasons: Object.fromEntries(this.throttlingMetrics.throttleReasons)
|
||||
},
|
||||
operationImpact: {
|
||||
delayedOperations: this.throttlingMetrics.delayedOperations,
|
||||
retriedOperations: this.throttlingMetrics.retriedOperations,
|
||||
failedDueToThrottling: this.throttlingMetrics.failedDueToThrottling,
|
||||
averageDelayMs,
|
||||
totalDelayMs: this.throttlingMetrics.totalDelayMs
|
||||
},
|
||||
serviceThrottling: Object.keys(serviceThrottlingRecord).length > 0
|
||||
? serviceThrottlingRecord
|
||||
: undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Merge statistics from storage (for distributed systems)
|
||||
*/
|
||||
mergeFromStorage(stored) {
|
||||
// Merge content types
|
||||
if (stored.contentTypes) {
|
||||
for (const [type, count] of Object.entries(stored.contentTypes)) {
|
||||
this.contentTypes.set(type, count);
|
||||
}
|
||||
}
|
||||
// Merge verb types
|
||||
if (stored.verbStatistics?.verbTypes) {
|
||||
for (const [type, count] of Object.entries(stored.verbStatistics.verbTypes)) {
|
||||
this.verbTypes.set(type, count);
|
||||
}
|
||||
}
|
||||
// Merge search metrics
|
||||
if (stored.searchMetrics) {
|
||||
this.searchMetrics.totalSearches = stored.searchMetrics.totalSearches || 0;
|
||||
this.searchMetrics.totalSearchTimeMs = (stored.searchMetrics.averageSearchTimeMs || 0) * this.searchMetrics.totalSearches;
|
||||
}
|
||||
// Merge data freshness
|
||||
if (stored.dataFreshness) {
|
||||
this.oldestTimestamp = new Date(stored.dataFreshness.oldestEntry).getTime();
|
||||
this.newestTimestamp = new Date(stored.dataFreshness.newestEntry).getTime();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reset statistics (for testing)
|
||||
*/
|
||||
reset() {
|
||||
this.contentTypes.clear();
|
||||
this.verbTypes.clear();
|
||||
this.updateTimestamps = [];
|
||||
this.searchMetrics = {
|
||||
totalSearches: 0,
|
||||
totalSearchTimeMs: 0,
|
||||
searchTimestamps: [],
|
||||
topSearchTerms: new Map()
|
||||
};
|
||||
this.oldestTimestamp = Date.now();
|
||||
this.newestTimestamp = Date.now();
|
||||
}
|
||||
pruneSearchTerms() {
|
||||
// Keep only top N search terms
|
||||
const sorted = Array.from(this.searchMetrics.topSearchTerms.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, this.MAX_SEARCH_TERMS);
|
||||
this.searchMetrics.topSearchTerms.clear();
|
||||
for (const [term, count] of sorted) {
|
||||
this.searchMetrics.topSearchTerms.set(term, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=statisticsCollector.js.map
|
||||
File diff suppressed because one or more lines are too long
7
.recovery-workspace/dist-backup-20250910-141917/utils/textEncoding.d.ts
vendored
Normal file
7
.recovery-workspace/dist-backup-20250910-141917/utils/textEncoding.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Apply TextEncoder/TextDecoder patches for Node.js compatibility
|
||||
* Simplified version for Transformers.js/ONNX Runtime
|
||||
*/
|
||||
export declare function applyTensorFlowPatch(): Promise<void>;
|
||||
export declare function getTextEncoder(): TextEncoder;
|
||||
export declare function getTextDecoder(): TextDecoder;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { isNode } from './environment.js';
|
||||
// Simplified TextEncoder/TextDecoder utilities for Node.js compatibility
|
||||
// No longer needs complex TensorFlow.js patches - only basic TextEncoder/TextDecoder
|
||||
/**
|
||||
* Flag to track if the patch has been applied
|
||||
*/
|
||||
let patchApplied = false;
|
||||
/**
|
||||
* Apply TextEncoder/TextDecoder patches for Node.js compatibility
|
||||
* Simplified version for Transformers.js/ONNX Runtime
|
||||
*/
|
||||
export async function applyTensorFlowPatch() {
|
||||
// Apply patches for all non-browser environments that might need TextEncoder/TextDecoder
|
||||
const isBrowserEnv = typeof window !== 'undefined' && typeof document !== 'undefined';
|
||||
if (isBrowserEnv || patchApplied) {
|
||||
return; // Browser environments don't need these patches, and don't patch twice
|
||||
}
|
||||
if (!isNode()) {
|
||||
return; // Only patch Node.js environments
|
||||
}
|
||||
try {
|
||||
console.log('Brainy: Applying TextEncoder/TextDecoder patch for Node.js');
|
||||
// Get the appropriate global object
|
||||
const globalObj = (() => {
|
||||
if (typeof globalThis !== 'undefined')
|
||||
return globalThis;
|
||||
if (typeof global !== 'undefined')
|
||||
return global;
|
||||
return {};
|
||||
})();
|
||||
// Make sure TextEncoder and TextDecoder are available globally
|
||||
if (!globalObj.TextEncoder) {
|
||||
globalObj.TextEncoder = TextEncoder;
|
||||
}
|
||||
if (!globalObj.TextDecoder) {
|
||||
globalObj.TextDecoder = TextDecoder;
|
||||
}
|
||||
// Also set them on the global object for older code
|
||||
if (typeof global !== 'undefined') {
|
||||
if (!global.TextEncoder) {
|
||||
global.TextEncoder = TextEncoder;
|
||||
}
|
||||
if (!global.TextDecoder) {
|
||||
global.TextDecoder = TextDecoder;
|
||||
}
|
||||
}
|
||||
patchApplied = true;
|
||||
console.log('Brainy: TextEncoder/TextDecoder patches applied successfully');
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Brainy: Failed to apply TextEncoder/TextDecoder patch:', error);
|
||||
}
|
||||
}
|
||||
export function getTextEncoder() {
|
||||
return new TextEncoder();
|
||||
}
|
||||
export function getTextDecoder() {
|
||||
return new TextDecoder();
|
||||
}
|
||||
// Apply patch immediately if in Node.js
|
||||
if (isNode()) {
|
||||
applyTensorFlowPatch().catch((error) => {
|
||||
console.warn('Failed to apply TextEncoder/TextDecoder patch at module load:', error);
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=textEncoding.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"textEncoding.js","sourceRoot":"","sources":["../../src/utils/textEncoding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,yEAAyE;AACzE,qFAAqF;AAErF;;GAEG;AACH,IAAI,YAAY,GAAG,KAAK,CAAA;AAExB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,yFAAyF;IACzF,MAAM,YAAY,GAAG,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,CAAA;IACrF,IAAI,YAAY,IAAI,YAAY,EAAE,CAAC;QACjC,OAAM,CAAC,uEAAuE;IAChF,CAAC;IAED,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACd,OAAM,CAAC,kCAAkC;IAC3C,CAAC;IAED,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAA;QAEzE,oCAAoC;QACpC,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE;YACtB,IAAI,OAAO,UAAU,KAAK,WAAW;gBAAE,OAAO,UAAU,CAAA;YACxD,IAAI,OAAO,MAAM,KAAK,WAAW;gBAAE,OAAO,MAAM,CAAA;YAChD,OAAO,EAAS,CAAA;QAClB,CAAC,CAAC,EAAE,CAAA;QAEJ,+DAA+D;QAC/D,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;YAC3B,SAAS,CAAC,WAAW,GAAG,WAAW,CAAA;QACrC,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;YAC3B,SAAS,CAAC,WAAW,GAAG,WAAW,CAAA;QACrC,CAAC;QAED,oDAAoD;QACpD,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBACxB,MAAM,CAAC,WAAW,GAAG,WAAW,CAAA;YAClC,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBACxB,MAAM,CAAC,WAAW,GAAG,WAAW,CAAA;YAClC,CAAC;QACH,CAAC;QAED,YAAY,GAAG,IAAI,CAAA;QACnB,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAA;IAC7E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,wDAAwD,EAAE,KAAK,CAAC,CAAA;IAC/E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,WAAW,EAAE,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,WAAW,EAAE,CAAA;AAC1B,CAAC;AAED,wCAAwC;AACxC,IAAI,MAAM,EAAE,EAAE,CAAC;IACb,oBAAoB,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACrC,OAAO,CAAC,IAAI,CAAC,+DAA+D,EAAE,KAAK,CAAC,CAAA;IACtF,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
||||
30
.recovery-workspace/dist-backup-20250910-141917/utils/typeUtils.d.ts
vendored
Normal file
30
.recovery-workspace/dist-backup-20250910-141917/utils/typeUtils.d.ts
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Type Utilities
|
||||
*
|
||||
* This module provides utility functions for working with the Brainy type system,
|
||||
* particularly for accessing lists of noun and verb types.
|
||||
*/
|
||||
/**
|
||||
* Returns an array of all available noun types
|
||||
*
|
||||
* @returns {string[]} Array of all noun type values
|
||||
*/
|
||||
export declare function getNounTypes(): string[];
|
||||
/**
|
||||
* Returns an array of all available verb types
|
||||
*
|
||||
* @returns {string[]} Array of all verb type values
|
||||
*/
|
||||
export declare function getVerbTypes(): string[];
|
||||
/**
|
||||
* Returns a map of noun type keys to their string values
|
||||
*
|
||||
* @returns {Record<string, string>} Map of noun type keys to values
|
||||
*/
|
||||
export declare function getNounTypeMap(): Record<string, string>;
|
||||
/**
|
||||
* Returns a map of verb type keys to their string values
|
||||
*
|
||||
* @returns {Record<string, string>} Map of verb type keys to values
|
||||
*/
|
||||
export declare function getVerbTypeMap(): Record<string, string>;
|
||||
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