jay-code
Version:
Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability
1,655 lines (1,425 loc) • 61.2 kB
text/typescript
/**
* Advanced Memory Management System with comprehensive capabilities
* Includes indexing, compression, cross-agent sharing, and intelligent cleanup
*/
import { EventEmitter } from 'node:events';
import { promises as fs } from 'node:fs';
import { createHash } from 'node:crypto';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { ILogger } from '../core/logger.js';
import { generateId } from '../utils/helpers.js';
// === INTERFACES ===
export interface MemoryEntry {
id: string;
key: string;
value: any;
type: string;
namespace: string;
tags: string[];
metadata: Record<string, any>;
owner: string;
accessLevel: 'private' | 'shared' | 'public';
createdAt: Date;
updatedAt: Date;
lastAccessedAt: Date;
expiresAt?: Date;
version: number;
size: number;
compressed: boolean;
checksum: string;
references: string[];
dependencies: string[];
}
export interface MemoryIndex {
keys: Map<string, string[]>; // key -> entryIds
tags: Map<string, string[]>; // tag -> entryIds
types: Map<string, string[]>; // type -> entryIds
namespaces: Map<string, string[]>; // namespace -> entryIds
owners: Map<string, string[]>; // owner -> entryIds
fullText: Map<string, string[]>; // word -> entryIds
}
export interface QueryOptions {
namespace?: string;
type?: string;
tags?: string[];
owner?: string;
accessLevel?: 'private' | 'shared' | 'public';
keyPattern?: string;
valueSearch?: string;
fullTextSearch?: string;
createdAfter?: Date;
createdBefore?: Date;
updatedAfter?: Date;
updatedBefore?: Date;
lastAccessedAfter?: Date;
lastAccessedBefore?: Date;
sizeGreaterThan?: number;
sizeLessThan?: number;
includeExpired?: boolean;
limit?: number;
offset?: number;
sortBy?: 'key' | 'createdAt' | 'updatedAt' | 'lastAccessedAt' | 'size' | 'type';
sortOrder?: 'asc' | 'desc';
aggregateBy?: 'namespace' | 'type' | 'owner' | 'tags';
includeMetadata?: boolean;
}
export interface ExportOptions {
format: 'json' | 'csv' | 'xml' | 'yaml';
namespace?: string;
type?: string;
includeMetadata?: boolean;
compression?: boolean;
encryption?: {
enabled: boolean;
algorithm?: string;
key?: string;
};
filtering?: QueryOptions;
}
export interface ImportOptions {
format: 'json' | 'csv' | 'xml' | 'yaml';
namespace?: string;
conflictResolution: 'overwrite' | 'skip' | 'merge' | 'rename';
validation?: boolean;
transformation?: {
keyMapping?: Record<string, string>;
valueTransformation?: (value: any) => any;
metadataExtraction?: (entry: any) => Record<string, any>;
};
dryRun?: boolean;
}
export interface MemoryStatistics {
overview: {
totalEntries: number;
totalSize: number;
compressedEntries: number;
compressionRatio: number;
indexSize: number;
memoryUsage: number;
diskUsage: number;
};
distribution: {
byNamespace: Record<string, { count: number; size: number }>;
byType: Record<string, { count: number; size: number }>;
byOwner: Record<string, { count: number; size: number }>;
byAccessLevel: Record<string, { count: number; size: number }>;
};
temporal: {
entriesCreatedLast24h: number;
entriesUpdatedLast24h: number;
entriesAccessedLast24h: number;
oldestEntry?: Date;
newestEntry?: Date;
};
performance: {
averageQueryTime: number;
averageWriteTime: number;
cacheHitRatio: number;
indexEfficiency: number;
};
health: {
expiredEntries: number;
orphanedReferences: number;
duplicateKeys: number;
corruptedEntries: number;
recommendedCleanup: boolean;
};
optimization: {
suggestions: string[];
potentialSavings: {
compression: number;
cleanup: number;
deduplication: number;
};
indexOptimization: string[];
};
}
export interface CleanupOptions {
dryRun?: boolean;
removeExpired?: boolean;
removeOlderThan?: number; // days
removeUnaccessed?: number; // days since last access
removeOrphaned?: boolean;
removeDuplicates?: boolean;
compressEligible?: boolean;
archiveOld?: {
enabled: boolean;
olderThan: number; // days
archivePath: string;
};
retentionPolicies?: {
namespace: string;
maxAge?: number; // days
maxCount?: number;
sizeLimit?: number; // bytes
}[];
}
export interface RetentionPolicy {
id: string;
name: string;
namespace?: string;
type?: string;
tags?: string[];
maxAge?: number; // days
maxCount?: number;
sizeLimit?: number; // bytes
priority: number;
enabled: boolean;
}
// === MAIN CLASS ===
export class AdvancedMemoryManager extends EventEmitter {
private readonly dataPath: string;
private readonly indexPath: string;
private readonly backupPath: string;
private readonly archivePath: string;
private entries = new Map<string, MemoryEntry>();
private index: MemoryIndex;
private cache = new Map<string, { entry: MemoryEntry; expiry: number }>();
private retentionPolicies = new Map<string, RetentionPolicy>();
private logger: ILogger;
private config: {
maxMemorySize: number;
cacheSize: number;
cacheTtl: number;
autoCompress: boolean;
autoCleanup: boolean;
cleanupInterval: number;
indexingEnabled: boolean;
persistenceEnabled: boolean;
compressionThreshold: number;
backupRetention: number;
};
private statistics: MemoryStatistics;
private operationMetrics = new Map<string, { count: number; totalTime: number }>();
private cleanupInterval?: NodeJS.Timeout;
constructor(
config: Partial<typeof AdvancedMemoryManager.prototype.config> = {},
logger: ILogger,
) {
super();
this.logger = logger;
this.config = {
maxMemorySize: 1024 * 1024 * 1024, // 1GB
cacheSize: 10000,
cacheTtl: 300000, // 5 minutes
autoCompress: true,
autoCleanup: true,
cleanupInterval: 3600000, // 1 hour
indexingEnabled: true,
persistenceEnabled: true,
compressionThreshold: 1024, // 1KB
backupRetention: 7, // days
...config,
};
// Setup file paths
const __dirname = dirname(fileURLToPath(import.meta.url));
this.dataPath = join(process.cwd(), 'memory', 'data');
this.indexPath = join(process.cwd(), 'memory', 'index');
this.backupPath = join(process.cwd(), 'memory', 'backups');
this.archivePath = join(process.cwd(), 'memory', 'archive');
this.index = this.createEmptyIndex();
this.statistics = this.initializeStatistics();
}
// === INITIALIZATION ===
async initialize(): Promise<void> {
this.logger.info('Initializing Advanced Memory Manager');
// Create directories
await Promise.all([
fs.mkdir(this.dataPath, { recursive: true }),
fs.mkdir(this.indexPath, { recursive: true }),
fs.mkdir(this.backupPath, { recursive: true }),
fs.mkdir(this.archivePath, { recursive: true }),
]);
// Load persisted data
if (this.config.persistenceEnabled) {
await this.loadPersistedData();
}
// Start automatic cleanup if enabled
if (this.config.autoCleanup) {
this.startAutoCleanup();
}
this.emit('memory:initialized');
this.logger.info('Advanced Memory Manager initialized successfully');
}
async shutdown(): Promise<void> {
this.logger.info('Shutting down Advanced Memory Manager');
// Stop cleanup interval
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
// Persist data
if (this.config.persistenceEnabled) {
await this.persistData();
}
// Create backup
await this.createBackup();
this.emit('memory:shutdown');
}
// === CORE OPERATIONS ===
async store(
key: string,
value: any,
options: {
namespace?: string;
type?: string;
tags?: string[];
metadata?: Record<string, any>;
owner?: string;
accessLevel?: 'private' | 'shared' | 'public';
ttl?: number;
compress?: boolean;
} = {},
): Promise<string> {
const startTime = Date.now();
try {
const entryId = generateId('entry');
const now = new Date();
// Process value (compression, serialization)
const processedValue = await this.processValue(value, options.compress);
const size = this.calculateSize(processedValue);
// Create entry
const entry: MemoryEntry = {
id: entryId,
key,
value: processedValue.value,
type: options.type || this.inferType(value),
namespace: options.namespace || 'default',
tags: options.tags || [],
metadata: options.metadata || {},
owner: options.owner || 'system',
accessLevel: options.accessLevel || 'shared',
createdAt: now,
updatedAt: now,
lastAccessedAt: now,
expiresAt: options.ttl ? new Date(now.getTime() + options.ttl) : undefined,
version: 1,
size,
compressed: processedValue.compressed,
checksum: this.calculateChecksum(processedValue.value),
references: [],
dependencies: [],
};
// Store entry
this.entries.set(entryId, entry);
// Update index
if (this.config.indexingEnabled) {
this.updateIndex(entry, 'create');
}
// Update cache
this.updateCache(key, entry);
// Apply retention policies
await this.applyRetentionPolicies(entry);
this.logger.debug('Memory entry stored', { entryId, key, namespace: entry.namespace });
this.emit('memory:entry-stored', { entry });
this.recordMetric('store', Date.now() - startTime);
return entryId;
} catch (error) {
this.recordMetric('store-error', Date.now() - startTime);
throw error;
}
}
async retrieve(
key: string,
options: {
namespace?: string;
updateLastAccessed?: boolean;
} = {},
): Promise<MemoryEntry | null> {
const startTime = Date.now();
try {
// Check cache first
const cached = this.cache.get(key);
if (cached && cached.expiry > Date.now()) {
this.recordMetric('retrieve-cache', Date.now() - startTime);
return cached.entry;
}
// Search in entries
const entry = this.findEntryByKey(key, options.namespace);
if (!entry) {
this.recordMetric('retrieve-miss', Date.now() - startTime);
return null;
}
// Check if expired
if (entry.expiresAt && entry.expiresAt < new Date()) {
await this.deleteEntry(entry.id);
this.recordMetric('retrieve-expired', Date.now() - startTime);
return null;
}
// Update last accessed
if (options.updateLastAccessed !== false) {
entry.lastAccessedAt = new Date();
}
// Decompress if needed
if (entry.compressed) {
entry.value = await this.decompressValue(entry.value);
}
// Update cache
this.updateCache(key, entry);
this.recordMetric('retrieve', Date.now() - startTime);
return entry;
} catch (error) {
this.recordMetric('retrieve-error', Date.now() - startTime);
throw error;
}
}
async update(
key: string,
value: any,
options: {
namespace?: string;
merge?: boolean;
updateMetadata?: Record<string, any>;
} = {},
): Promise<boolean> {
const startTime = Date.now();
try {
const entry = await this.retrieve(key, { namespace: options.namespace });
if (!entry) {
this.recordMetric('update-not-found', Date.now() - startTime);
return false;
}
// Process new value
const processedValue = await this.processValue(value, entry.compressed);
// Update entry
if (options.merge && typeof entry.value === 'object' && typeof value === 'object') {
entry.value = { ...entry.value, ...processedValue.value };
} else {
entry.value = processedValue.value;
}
entry.updatedAt = new Date();
entry.lastAccessedAt = new Date();
entry.version++;
entry.size = this.calculateSize(entry.value);
entry.checksum = this.calculateChecksum(entry.value);
if (options.updateMetadata) {
entry.metadata = { ...entry.metadata, ...options.updateMetadata };
}
// Update index
if (this.config.indexingEnabled) {
this.updateIndex(entry, 'update');
}
// Update cache
this.updateCache(key, entry);
this.logger.debug('Memory entry updated', { entryId: entry.id, key });
this.emit('memory:entry-updated', { entry });
this.recordMetric('update', Date.now() - startTime);
return true;
} catch (error) {
this.recordMetric('update-error', Date.now() - startTime);
throw error;
}
}
async deleteEntry(entryId: string): Promise<boolean> {
const startTime = Date.now();
try {
const entry = this.entries.get(entryId);
if (!entry) {
this.recordMetric('delete-not-found', Date.now() - startTime);
return false;
}
// Remove from storage
this.entries.delete(entryId);
// Update index
if (this.config.indexingEnabled) {
this.updateIndex(entry, 'delete');
}
// Remove from cache
this.cache.delete(entry.key);
this.logger.debug('Memory entry deleted', { entryId, key: entry.key });
this.emit('memory:entry-deleted', { entryId });
this.recordMetric('delete', Date.now() - startTime);
return true;
} catch (error) {
this.recordMetric('delete-error', Date.now() - startTime);
throw error;
}
}
// === ADVANCED QUERY OPERATIONS ===
async query(options: QueryOptions = {}): Promise<{
entries: MemoryEntry[];
total: number;
aggregations?: Record<string, any>;
}> {
const startTime = Date.now();
try {
let candidateEntries: MemoryEntry[] = [];
// Use index for efficient querying if enabled
if (this.config.indexingEnabled) {
candidateEntries = this.queryWithIndex(options);
} else {
candidateEntries = Array.from(this.entries.values());
}
// Apply filters
let filteredEntries = candidateEntries.filter((entry) => {
return this.matchesQuery(entry, options);
});
// Remove expired entries
if (!options.includeExpired) {
filteredEntries = filteredEntries.filter((entry) => {
if (entry.expiresAt && entry.expiresAt < new Date()) {
// Schedule for deletion
setTimeout(() => this.deleteEntry(entry.id), 0);
return false;
}
return true;
});
}
const total = filteredEntries.length;
// Apply sorting
if (options.sortBy) {
filteredEntries.sort((a, b) => {
const aVal = this.getPropertyValue(a, options.sortBy!);
const bVal = this.getPropertyValue(b, options.sortBy!);
const multiplier = options.sortOrder === 'desc' ? -1 : 1;
if (aVal < bVal) return -1 * multiplier;
if (aVal > bVal) return 1 * multiplier;
return 0;
});
}
// Apply pagination
const offset = options.offset || 0;
const limit = options.limit || filteredEntries.length;
const paginatedEntries = filteredEntries.slice(offset, offset + limit);
// Update last accessed times
paginatedEntries.forEach((entry) => {
entry.lastAccessedAt = new Date();
});
// Generate aggregations if requested
let aggregations: Record<string, any> | undefined;
if (options.aggregateBy) {
aggregations = this.generateAggregations(filteredEntries, options.aggregateBy);
}
this.recordMetric('query', Date.now() - startTime);
return {
entries: paginatedEntries,
total,
aggregations,
};
} catch (error) {
this.recordMetric('query-error', Date.now() - startTime);
throw error;
}
}
// === EXPORT OPERATIONS ===
async export(
filePath: string,
options: ExportOptions,
): Promise<{
entriesExported: number;
fileSize: number;
checksum: string;
}> {
const startTime = Date.now();
try {
this.logger.info('Starting memory export', { filePath, format: options.format });
// Query entries to export
const queryResult = await this.query(options.filtering || {});
const entries = queryResult.entries;
if (entries.length === 0) {
throw new Error('No entries found matching export criteria');
}
// Prepare export data
let exportData: any;
switch (options.format) {
case 'json':
exportData = this.prepareJsonExport(entries, options);
break;
case 'csv':
exportData = this.prepareCsvExport(entries, options);
break;
case 'xml':
exportData = this.prepareXmlExport(entries, options);
break;
case 'yaml':
exportData = this.prepareYamlExport(entries, options);
break;
default:
throw new Error(`Unsupported export format: ${options.format}`);
}
// Apply compression if requested
if (options.compression) {
exportData = await this.compressData(exportData);
}
// Apply encryption if requested
if (options.encryption?.enabled) {
exportData = await this.encryptData(exportData, options.encryption);
}
// Write to file
await fs.mkdir(dirname(filePath), { recursive: true });
await fs.writeFile(filePath, exportData);
// Calculate file stats
const stats = await fs.stat(filePath);
const checksum = this.calculateChecksum(exportData);
this.logger.info('Memory export completed', {
entriesExported: entries.length,
fileSize: stats.size,
checksum,
});
this.emit('memory:exported', {
filePath,
entriesExported: entries.length,
fileSize: stats.size,
});
this.recordMetric('export', Date.now() - startTime);
return {
entriesExported: entries.length,
fileSize: stats.size,
checksum,
};
} catch (error) {
this.recordMetric('export-error', Date.now() - startTime);
throw error;
}
}
// === IMPORT OPERATIONS ===
async import(
filePath: string,
options: ImportOptions,
): Promise<{
entriesImported: number;
entriesSkipped: number;
entriesUpdated: number;
conflicts: string[];
}> {
const startTime = Date.now();
try {
this.logger.info('Starting memory import', { filePath, format: options.format });
// Read and parse file
const fileContent = await fs.readFile(filePath, 'utf-8');
let importData: any[];
switch (options.format) {
case 'json':
importData = this.parseJsonImport(fileContent);
break;
case 'csv':
importData = this.parseCsvImport(fileContent);
break;
case 'xml':
importData = this.parseXmlImport(fileContent);
break;
case 'yaml':
importData = this.parseYamlImport(fileContent);
break;
default:
throw new Error(`Unsupported import format: ${options.format}`);
}
// Validate data if requested
if (options.validation) {
importData = this.validateImportData(importData);
}
// Apply transformations if provided
if (options.transformation) {
importData = this.transformImportData(importData, options.transformation);
}
// Process imports
const results = {
entriesImported: 0,
entriesSkipped: 0,
entriesUpdated: 0,
conflicts: [] as string[],
};
for (const item of importData) {
if (options.dryRun) {
// Dry run - just check for conflicts
const existing = this.findEntryByKey(item.key, item.namespace);
if (existing) {
results.conflicts.push(
`Key '${item.key}' already exists in namespace '${item.namespace}'`,
);
}
continue;
}
try {
const result = await this.importSingleEntry(item, options);
switch (result.action) {
case 'imported':
results.entriesImported++;
break;
case 'updated':
results.entriesUpdated++;
break;
case 'skipped':
results.entriesSkipped++;
break;
case 'conflict':
results.conflicts.push(result.message || 'Unknown conflict');
break;
}
} catch (error) {
results.conflicts.push(
`Error importing '${item.key}': ${error instanceof Error ? error.message : String(error)}`,
);
}
}
this.logger.info('Memory import completed', results);
this.emit('memory:imported', results);
this.recordMetric('import', Date.now() - startTime);
return results;
} catch (error) {
this.recordMetric('import-error', Date.now() - startTime);
throw error;
}
}
// === STATISTICS AND ANALYTICS ===
async getStatistics(): Promise<MemoryStatistics> {
const startTime = Date.now();
try {
const stats = this.calculateStatistics();
this.recordMetric('stats', Date.now() - startTime);
return stats;
} catch (error) {
this.recordMetric('stats-error', Date.now() - startTime);
throw error;
}
}
// === CLEANUP OPERATIONS ===
async cleanup(options: CleanupOptions = {}): Promise<{
entriesRemoved: number;
entriesArchived: number;
entriesCompressed: number;
spaceSaved: number;
actions: string[];
}> {
const startTime = Date.now();
try {
this.logger.info('Starting memory cleanup', options);
const results = {
entriesRemoved: 0,
entriesArchived: 0,
entriesCompressed: 0,
spaceSaved: 0,
actions: [] as string[],
};
// Get all entries for processing
const allEntries = Array.from(this.entries.values());
const now = new Date();
// Phase 1: Remove expired entries
if (options.removeExpired !== false) {
const expiredEntries = allEntries.filter(
(entry) => entry.expiresAt && entry.expiresAt < now,
);
for (const entry of expiredEntries) {
if (!options.dryRun) {
await this.deleteEntry(entry.id);
}
results.entriesRemoved++;
results.spaceSaved += entry.size;
}
if (expiredEntries.length > 0) {
results.actions.push(`Removed ${expiredEntries.length} expired entries`);
}
}
// Phase 2: Remove old entries
if (options.removeOlderThan) {
const cutoffDate = new Date(now.getTime() - options.removeOlderThan * 24 * 60 * 60 * 1000);
const oldEntries = allEntries.filter((entry) => entry.createdAt < cutoffDate);
for (const entry of oldEntries) {
if (!options.dryRun) {
await this.deleteEntry(entry.id);
}
results.entriesRemoved++;
results.spaceSaved += entry.size;
}
if (oldEntries.length > 0) {
results.actions.push(
`Removed ${oldEntries.length} entries older than ${options.removeOlderThan} days`,
);
}
}
// Phase 3: Remove unaccessed entries
if (options.removeUnaccessed) {
const cutoffDate = new Date(now.getTime() - options.removeUnaccessed * 24 * 60 * 60 * 1000);
const unaccessedEntries = allEntries.filter((entry) => entry.lastAccessedAt < cutoffDate);
for (const entry of unaccessedEntries) {
if (!options.dryRun) {
await this.deleteEntry(entry.id);
}
results.entriesRemoved++;
results.spaceSaved += entry.size;
}
if (unaccessedEntries.length > 0) {
results.actions.push(
`Removed ${unaccessedEntries.length} entries not accessed in ${options.removeUnaccessed} days`,
);
}
}
// Phase 4: Archive old entries
if (options.archiveOld?.enabled) {
const cutoffDate = new Date(
now.getTime() - options.archiveOld.olderThan * 24 * 60 * 60 * 1000,
);
const archiveEntries = allEntries.filter(
(entry) => entry.createdAt < cutoffDate && !entry.expiresAt, // Don't archive entries that will expire
);
if (archiveEntries.length > 0 && !options.dryRun) {
await this.archiveEntries(archiveEntries, options.archiveOld.archivePath);
}
results.entriesArchived = archiveEntries.length;
if (archiveEntries.length > 0) {
results.actions.push(`Archived ${archiveEntries.length} old entries`);
}
}
// Phase 5: Compress eligible entries
if (options.compressEligible !== false && this.config.autoCompress) {
const uncompressedEntries = allEntries.filter(
(entry) => !entry.compressed && entry.size > this.config.compressionThreshold,
);
for (const entry of uncompressedEntries) {
if (!options.dryRun) {
const originalSize = entry.size;
const compressedValue = await this.compressValue(entry.value);
entry.value = compressedValue;
entry.compressed = true;
entry.size = this.calculateSize(compressedValue);
results.spaceSaved += originalSize - entry.size;
}
results.entriesCompressed++;
}
if (uncompressedEntries.length > 0) {
results.actions.push(`Compressed ${uncompressedEntries.length} entries`);
}
}
// Phase 6: Apply retention policies
if (options.retentionPolicies) {
for (const policy of options.retentionPolicies) {
const policyResults = await this.applyRetentionPolicy(policy, options.dryRun);
results.entriesRemoved += policyResults.removed;
results.spaceSaved += policyResults.spaceSaved;
if (policyResults.removed > 0) {
results.actions.push(
`Retention policy '${policy.namespace}': removed ${policyResults.removed} entries`,
);
}
}
}
// Phase 7: Remove orphaned references
if (options.removeOrphaned !== false) {
const orphanedCount = await this.cleanupOrphanedReferences(options.dryRun);
if (orphanedCount > 0) {
results.actions.push(`Cleaned up ${orphanedCount} orphaned references`);
}
}
// Phase 8: Remove duplicates
if (options.removeDuplicates) {
const duplicatesResults = await this.removeDuplicateEntries(options.dryRun);
results.entriesRemoved += duplicatesResults.removed;
results.spaceSaved += duplicatesResults.spaceSaved;
if (duplicatesResults.removed > 0) {
results.actions.push(`Removed ${duplicatesResults.removed} duplicate entries`);
}
}
// Rebuild index if significant changes
if (results.entriesRemoved + results.entriesArchived > 100 && !options.dryRun) {
await this.rebuildIndex();
results.actions.push('Rebuilt search index');
}
this.logger.info('Memory cleanup completed', results);
this.emit('memory:cleanup-completed', results);
this.recordMetric('cleanup', Date.now() - startTime);
return results;
} catch (error) {
this.recordMetric('cleanup-error', Date.now() - startTime);
throw error;
}
}
// === UTILITY METHODS ===
private createEmptyIndex(): MemoryIndex {
return {
keys: new Map(),
tags: new Map(),
types: new Map(),
namespaces: new Map(),
owners: new Map(),
fullText: new Map(),
};
}
private async processValue(
value: any,
compress?: boolean,
): Promise<{ value: any; compressed: boolean }> {
let processedValue = value;
let isCompressed = false;
// Auto-compress if enabled and value is large enough
if (
(compress || this.config.autoCompress) &&
this.calculateSize(value) > this.config.compressionThreshold
) {
processedValue = await this.compressValue(value);
isCompressed = true;
}
return { value: processedValue, compressed: isCompressed };
}
private async compressValue(value: any): Promise<any> {
// Placeholder for compression implementation
// In a real implementation, you would use a compression library like zlib
return JSON.stringify(value);
}
private async decompressValue(value: any): Promise<any> {
// Placeholder for decompression implementation
try {
return JSON.parse(value);
} catch {
return value;
}
}
private calculateSize(value: any): number {
return JSON.stringify(value).length;
}
private calculateChecksum(value: any): string {
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
}
private inferType(value: any): string {
if (Array.isArray(value)) return 'array';
if (value === null) return 'null';
return typeof value;
}
private findEntryByKey(key: string, namespace?: string): MemoryEntry | undefined {
for (const entry of this.entries.values()) {
if (entry.key === key && (!namespace || entry.namespace === namespace)) {
return entry;
}
}
return undefined;
}
private updateIndex(entry: MemoryEntry, operation: 'create' | 'update' | 'delete'): void {
if (!this.config.indexingEnabled) return;
const { id, key, tags, type, namespace, owner, value } = entry;
if (operation === 'delete') {
// Remove from all indices
this.removeFromIndex(this.index.keys, key, id);
tags.forEach((tag) => this.removeFromIndex(this.index.tags, tag, id));
this.removeFromIndex(this.index.types, type, id);
this.removeFromIndex(this.index.namespaces, namespace, id);
this.removeFromIndex(this.index.owners, owner, id);
// Remove from full-text index
const words = this.extractWords(value);
words.forEach((word) => this.removeFromIndex(this.index.fullText, word, id));
} else {
// Add to indices
this.addToIndex(this.index.keys, key, id);
tags.forEach((tag) => this.addToIndex(this.index.tags, tag, id));
this.addToIndex(this.index.types, type, id);
this.addToIndex(this.index.namespaces, namespace, id);
this.addToIndex(this.index.owners, owner, id);
// Add to full-text index
const words = this.extractWords(value);
words.forEach((word) => this.addToIndex(this.index.fullText, word, id));
}
}
private addToIndex(indexMap: Map<string, string[]>, key: string, entryId: string): void {
if (!indexMap.has(key)) {
indexMap.set(key, []);
}
const entries = indexMap.get(key)!;
if (!entries.includes(entryId)) {
entries.push(entryId);
}
}
private removeFromIndex(indexMap: Map<string, string[]>, key: string, entryId: string): void {
const entries = indexMap.get(key);
if (entries) {
const index = entries.indexOf(entryId);
if (index > -1) {
entries.splice(index, 1);
}
if (entries.length === 0) {
indexMap.delete(key);
}
}
}
private extractWords(value: any): string[] {
const text = typeof value === 'string' ? value : JSON.stringify(value);
return text
.toLowerCase()
.replace(/[^\w\s]/g, ' ')
.split(/\s+/)
.filter((word) => word.length > 2);
}
private updateCache(key: string, entry: MemoryEntry): void {
if (this.cache.size >= this.config.cacheSize) {
this.evictCache();
}
this.cache.set(key, {
entry: { ...entry },
expiry: Date.now() + this.config.cacheTtl,
});
}
private evictCache(): void {
const entries = Array.from(this.cache.entries());
entries.sort((a, b) => a[1].expiry - b[1].expiry);
const toRemove = entries.slice(0, Math.floor(this.config.cacheSize * 0.1));
toRemove.forEach(([key]) => this.cache.delete(key));
}
private recordMetric(operation: string, duration: number): void {
const current = this.operationMetrics.get(operation) || { count: 0, totalTime: 0 };
current.count++;
current.totalTime += duration;
this.operationMetrics.set(operation, current);
}
private initializeStatistics(): MemoryStatistics {
return {
overview: {
totalEntries: 0,
totalSize: 0,
compressedEntries: 0,
compressionRatio: 0,
indexSize: 0,
memoryUsage: 0,
diskUsage: 0,
},
distribution: {
byNamespace: {},
byType: {},
byOwner: {},
byAccessLevel: {},
},
temporal: {
entriesCreatedLast24h: 0,
entriesUpdatedLast24h: 0,
entriesAccessedLast24h: 0,
},
performance: {
averageQueryTime: 0,
averageWriteTime: 0,
cacheHitRatio: 0,
indexEfficiency: 0,
},
health: {
expiredEntries: 0,
orphanedReferences: 0,
duplicateKeys: 0,
corruptedEntries: 0,
recommendedCleanup: false,
},
optimization: {
suggestions: [],
potentialSavings: {
compression: 0,
cleanup: 0,
deduplication: 0,
},
indexOptimization: [],
},
};
}
// === COMPLEX IMPLEMENTATION METHODS ===
// These would be fully implemented in a production system
private queryWithIndex(options: QueryOptions): MemoryEntry[] {
// Implementation would use the index for efficient querying
return Array.from(this.entries.values());
}
private matchesQuery(entry: MemoryEntry, options: QueryOptions): boolean {
// Comprehensive query matching logic
if (options.namespace && entry.namespace !== options.namespace) return false;
if (options.type && entry.type !== options.type) return false;
if (options.owner && entry.owner !== options.owner) return false;
if (options.accessLevel && entry.accessLevel !== options.accessLevel) return false;
if (options.tags && options.tags.length > 0) {
const hasAllTags = options.tags.every((tag) => entry.tags.includes(tag));
if (!hasAllTags) return false;
}
if (options.keyPattern) {
const regex = new RegExp(options.keyPattern, 'i');
if (!regex.test(entry.key)) return false;
}
if (options.valueSearch) {
const valueStr = JSON.stringify(entry.value).toLowerCase();
if (!valueStr.includes(options.valueSearch.toLowerCase())) return false;
}
// Date range checks
if (options.createdAfter && entry.createdAt < options.createdAfter) return false;
if (options.createdBefore && entry.createdAt > options.createdBefore) return false;
if (options.updatedAfter && entry.updatedAt < options.updatedAfter) return false;
if (options.updatedBefore && entry.updatedAt > options.updatedBefore) return false;
// Size checks
if (options.sizeGreaterThan && entry.size <= options.sizeGreaterThan) return false;
if (options.sizeLessThan && entry.size >= options.sizeLessThan) return false;
return true;
}
private getPropertyValue(entry: MemoryEntry, property: string): any {
switch (property) {
case 'key':
return entry.key;
case 'createdAt':
return entry.createdAt.getTime();
case 'updatedAt':
return entry.updatedAt.getTime();
case 'lastAccessedAt':
return entry.lastAccessedAt.getTime();
case 'size':
return entry.size;
case 'type':
return entry.type;
default:
return entry.key;
}
}
private generateAggregations(entries: MemoryEntry[], aggregateBy: string): Record<string, any> {
const aggregations: Record<string, any> = {};
switch (aggregateBy) {
case 'namespace':
aggregations.namespaces = this.aggregateByProperty(entries, 'namespace');
break;
case 'type':
aggregations.types = this.aggregateByProperty(entries, 'type');
break;
case 'owner':
aggregations.owners = this.aggregateByProperty(entries, 'owner');
break;
case 'tags':
aggregations.tags = this.aggregateByTags(entries);
break;
}
return aggregations;
}
private aggregateByProperty(
entries: MemoryEntry[],
property: keyof MemoryEntry,
): Record<string, { count: number; totalSize: number }> {
const result: Record<string, { count: number; totalSize: number }> = {};
for (const entry of entries) {
const value = String(entry[property]);
if (!result[value]) {
result[value] = { count: 0, totalSize: 0 };
}
result[value].count++;
result[value].totalSize += entry.size;
}
return result;
}
private aggregateByTags(
entries: MemoryEntry[],
): Record<string, { count: number; totalSize: number }> {
const result: Record<string, { count: number; totalSize: number }> = {};
for (const entry of entries) {
for (const tag of entry.tags) {
if (!result[tag]) {
result[tag] = { count: 0, totalSize: 0 };
}
result[tag].count++;
result[tag].totalSize += entry.size;
}
}
return result;
}
// === EXPORT/IMPORT HELPERS ===
private prepareJsonExport(entries: MemoryEntry[], options: ExportOptions): string {
const exportData = {
metadata: {
exportedAt: new Date().toISOString(),
version: '1.0',
totalEntries: entries.length,
format: 'json',
},
entries: options.includeMetadata
? entries
: entries.map((entry) => ({
key: entry.key,
value: entry.value,
type: entry.type,
namespace: entry.namespace,
tags: entry.tags,
})),
};
return JSON.stringify(exportData, null, 2);
}
private prepareCsvExport(entries: MemoryEntry[], options: ExportOptions): string {
// CSV export implementation
const headers = ['key', 'value', 'type', 'namespace', 'tags'];
const rows = entries.map((entry) => [
entry.key,
JSON.stringify(entry.value),
entry.type,
entry.namespace,
entry.tags.join(';'),
]);
return [headers, ...rows].map((row) => row.join(',')).join('\n');
}
private prepareXmlExport(entries: MemoryEntry[], options: ExportOptions): string {
// XML export implementation
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<memory>\n';
for (const entry of entries) {
xml += ` <entry>\n`;
xml += ` <key>${this.escapeXml(entry.key)}</key>\n`;
xml += ` <value>${this.escapeXml(JSON.stringify(entry.value))}</value>\n`;
xml += ` <type>${this.escapeXml(entry.type)}</type>\n`;
xml += ` <namespace>${this.escapeXml(entry.namespace)}</namespace>\n`;
xml += ` <tags>${this.escapeXml(entry.tags.join(','))}</tags>\n`;
xml += ` </entry>\n`;
}
xml += '</memory>';
return xml;
}
private prepareYamlExport(entries: MemoryEntry[], options: ExportOptions): string {
// YAML export implementation - simplified
let yaml = 'memory:\n';
for (const entry of entries) {
yaml += ` - key: "${entry.key}"\n`;
yaml += ` value: ${JSON.stringify(entry.value)}\n`;
yaml += ` type: "${entry.type}"\n`;
yaml += ` namespace: "${entry.namespace}"\n`;
yaml += ` tags: [${entry.tags.map((t) => `"${t}"`).join(', ')}]\n`;
}
return yaml;
}
private escapeXml(str: string): string {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
private parseJsonImport(content: string): any[] {
const data = JSON.parse(content);
return data.entries || data;
}
private parseCsvImport(content: string): any[] {
// Simple CSV parsing
const lines = content.split('\n');
const headers = lines[0].split(',');
const entries = [];
for (let i = 1; i < lines.length; i++) {
const values = lines[i].split(',');
const entry: any = {};
for (let j = 0; j < headers.length; j++) {
entry[headers[j]] = values[j];
}
entries.push(entry);
}
return entries;
}
private parseXmlImport(content: string): any[] {
// XML parsing would require a proper XML parser
throw new Error('XML import not implemented in this example');
}
private parseYamlImport(content: string): any[] {
// YAML parsing would require a YAML parser
throw new Error('YAML import not implemented in this example');
}
private validateImportData(data: any[]): any[] {
return data.filter((item) => {
return item.key && item.value !== undefined;
});
}
private transformImportData(
data: any[],
transformation: NonNullable<ImportOptions['transformation']>,
): any[] {
return data.map((item) => {
const transformed = { ...item };
// Apply key mapping
if (transformation.keyMapping) {
for (const [oldKey, newKey] of Object.entries(transformation.keyMapping)) {
if (transformed[oldKey] !== undefined) {
transformed[newKey] = transformed[oldKey];
delete transformed[oldKey];
}
}
}
// Apply value transformation
if (transformation.valueTransformation) {
transformed.value = transformation.valueTransformation(transformed.value);
}
// Extract metadata
if (transformation.metadataExtraction) {
transformed.metadata = transformation.metadataExtraction(transformed);
}
return transformed;
});
}
private async importSingleEntry(
item: any,
options: ImportOptions,
): Promise<{
action: 'imported' | 'updated' | 'skipped' | 'conflict';
message?: string;
}> {
const existing = this.findEntryByKey(item.key, item.namespace || options.namespace);
if (existing) {
switch (options.conflictResolution) {
case 'skip':
return { action: 'skipped' };
case 'overwrite':
await this.update(item.key, item.value, { namespace: item.namespace });
return { action: 'updated' };
case 'merge':
await this.update(item.key, item.value, {
namespace: item.namespace,
merge: true,
});
return { action: 'updated' };
case 'rename':
const newKey = `${item.key}_imported_${Date.now()}`;
await this.store(newKey, item.value, {
namespace: item.namespace,
type: item.type,
tags: item.tags,
metadata: item.metadata,
});
return { action: 'imported' };
default:
return {
action: 'conflict',
message: `Key '${item.key}' already exists`,
};
}
} else {
await this.store(item.key, item.value, {
namespace: item.namespace || options.namespace,
type: item.type,
tags: item.tags,
metadata: item.metadata,
});
return { action: 'imported' };
}
}
// === STATISTICS CALCULATION ===
private calculateStatistics(): MemoryStatistics {
const entries = Array.from(this.entries.values());
const now = new Date();
const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const stats: MemoryStatistics = {
overview: {
totalEntries: entries.length,
totalSize: entries.reduce((sum, entry) => sum + entry.size, 0),
compressedEntries: entries.filter((entry) => entry.compressed).length,
compressionRatio: 0,
indexSize: this.calculateIndexSize(),
memoryUsage: process.memoryUsage().heapUsed,
diskUsage: 0, // Would be calculated from actual file system
},
distribution: {
byNamespace: this.calculateDistribution(entries, 'namespace'),
byType: this.calculateDistribution(entries, 'type'),
byOwner: this.calculateDistribution(entries, 'owner'),
byAccessLevel: this.calculateDistribution(entries, 'accessLevel'),
},
temporal: {
entriesCreatedLast24h: entries.filter((e) => e.createdAt >= last24h).length,
entriesUpdatedLast24h: entries.filter((e) => e.updatedAt >= last24h).length,
entriesAccessedLast24h: entries.filter((e) => e.lastAccessedAt >= last24h).length,
oldestEntry:
entries.length > 0
? entries.reduce((oldest, entry) =>
entry.createdAt < oldest.createdAt ? entry : oldest,
).createdAt
: undefined,
newestEntry:
entries.length > 0
? entries.reduce((newest, entry) =>
entry.createdAt > newest.createdAt ? entry : newest,
).createdAt
: undefined,
},
performance: this.calculatePerformanceMetrics(),
health: this.calculateHealthMetrics(entries, now),
optimization: this.generateOptimizationSuggestions(entries),
};
// Calculate compression ratio
const uncompressedSize = entries
.filter((e) => !e.compressed)
.reduce((sum, e) => sum + e.size, 0);
const compressedSize = entries.filter((e) => e.compressed).reduce((sum, e) => sum + e.size, 0);
stats.overview.compressionRatio =
uncompressedSize > 0 ? (uncompressedSize - compressedSize) / uncompressedSize : 0;
return stats;
}
private calculateDistribution(
entries: MemoryEntry[],
property: keyof MemoryEntry,
): Record<string, { count: number; size: number }> {
const distribution: Record<string, { count: number; size: number }> = {};
for (const entry of entries) {
const value = String(entry[property]);
if (!distribution[value]) {
distribution[value] = { count: 0, size: 0 };
}
distribution[value].count++;
distribution[value].size += entry.size;
}
return distribution;
}
private calculateIndexSize(): number {
let size = 0;
for (const [, entries] of this.index.keys) {
size += entries.length * 50; // Rough estimate
}
return size;
}
private calculatePerformanceMetrics(): MemoryStatistics['performance'] {
const queryMetrics = this.operationMetrics.get('query') || { count: 0, totalTime: 0 };
const writeMetrics = this.operationMetrics.get('store') || { count: 0, totalTime: 0 };
const cacheMetrics = this.operationMetrics.get('retrieve-cache') || { count: 0, totalTime: 0 };
const totalRetrieves =
(this.operationMetrics.get('retrieve') || { count: 0 }).count + cacheMetrics.count;
return {
averageQueryTime: queryMetrics.count > 0 ? queryMetrics.totalTime / queryMetrics.count : 0,
averageWriteTime: writeMetrics.count > 0 ? writeMetrics.totalTime / writeMetrics.count : 0,
cacheHitRatio: totalRetrieves > 0 ? cacheMetrics.count / totalRetrieves : 0,
indexEfficiency: this.config.indexingEnabled ? 0.95 : 0, // Placeholder
};
}
private calculateHealthMetrics(entries: MemoryEntry[], now: Date): MemoryStatistics['health'] {
const expiredEntries = entries.filter((e) => e.expiresAt && e.expiresAt < now).length;
const duplicateKeys = this.findDuplicateKeys(entries);
return {
expiredEntries,
orphanedReferences: 0, // Would be calculated by checking references
duplicateKeys: duplicateKeys.length,
corruptedEntries: 0, // Would be calculated by validating checksums
recommendedCleanup: expiredEntries > 10 || duplicateKeys.length > 5,
};
}
private generateOptimizationSuggestions(
entries: MemoryEntry[],
): MemoryStatistics['optimization'] {
const suggestions: string[] = [];
const potentialSavings = { compression: 0, cleanup: 0, deduplication: 0 };
// Compression suggestions
const uncompressedLarge = entries.filter(
(e) => !e.compressed && e.size > this.config.compressionThreshold,
);
if (uncompressedLarge.length > 0) {
suggestions.push(`${uncompressedLarge.length} entries could benefit from compression`);
potentialSavings.compression = uncompressedLarge.reduce((sum, e) => sum + e.size * 0.6, 0);
}
// Cleanup suggestions
const now = new Date();
const oldEntries = entries.filter(
(e) => now.getTime() - e.lastAccessedAt.getTime() > 30 * 24 * 60 * 60 * 1000,
);
if (oldEntries.length > 0) {
suggestions.push(`${oldEntries.length} entries haven't been accessed in 30+ days`);
potentialSavings.cleanup = oldEntries.reduce((sum, e) => sum + e.size, 0);
}
// Deduplication suggestions
const duplicates = this.findDuplicateKeys(entries);
if (duplicates.length > 0) {
suggestions.push(`${duplicates.length} duplicate keys found`);
potentialSavings.deduplication = duplicates.reduce(
(sum, group) => sum + group.entries.slice(1).reduce((s, e) => s + e.size, 0),
0,
);
}
return {
suggestions,
potentialSavings,
indexOptimization: this.config.indexingEnabled
? ['Consider periodic index rebuilding for optimal performance']
: ['Enable indexing for better query performance'],
};
}
private findDuplicateKeys(
entries: MemoryEntry[],
): Array<{ key: string; namespace: string; entries: MemoryEntry[] }> {
const keyMap = new Map<string, MemoryEntry[]>();
for (const entry of entries) {
const compositeKey = `${entry.namespace}:${entry.key}`;
if (!keyMap.has(compositeKey)) {
keyMap.set(compositeKey, []);
}
keyMap.get(compositeKey)!.push(entry);
}
const duplicates: Array<{ key: string;