UNPKG

@codai/memorai

Version:

Universal Database & Storage Service for CODAI Ecosystem - CBD Backend

524 lines 19.6 kB
/** * MemoraiService - Universal Database & Storage Service * * Main service class that orchestrates all MEMORAI functionality: * - Database operations (SQL, NoSQL, Vector) * - File & Blob storage * - AI Memory management * - Real-time synchronization * - Cross-app data sharing */ import { EventEmitter } from 'events'; import { createMemoraiConfig } from '../config'; import { DatabaseService } from './DatabaseService'; import { StorageService } from './StorageService'; import { MemoryService } from './MemoryService'; import { SyncService } from './SyncService'; import { CacheService } from './CacheService'; import { AnalyticsService } from './AnalyticsService'; export class MemoraiService extends EventEmitter { constructor(config = {}) { super(); this.isInitialized = false; // Create complete configuration this.config = createMemoraiConfig(config, process.env.NODE_ENV); // Initialize services this.database = new DatabaseService(this.config.database); this.storage = new StorageService(this.config.storage); this.memory = new MemoryService(this.config.vectorDB, this.config.ai); this.sync = new SyncService(this.config.realtime); this.cache = new CacheService(this.config.cache); this.analytics = new AnalyticsService(); this.setupEventHandlers(); } // ==================== SINGLETON PATTERN ==================== static getInstance(config) { if (!MemoraiService.instance) { MemoraiService.instance = new MemoraiService(config); } return MemoraiService.instance; } static async create(config) { const instance = MemoraiService.getInstance(config); await instance.initialize(); return instance; } // ==================== INITIALIZATION ==================== async initialize() { if (this.isInitialized) return; try { // Initialize services in order await this.database.initialize(); await this.storage.initialize(); await this.memory.initialize(); await this.sync.initialize(); await this.cache.initialize(); await this.analytics.initialize(); this.isInitialized = true; this.emit('initialized', { timestamp: new Date() }); console.log('🧠 MEMORAI Service initialized successfully'); } catch (error) { console.error('❌ Failed to initialize MEMORAI Service:', error); throw error; } } async shutdown() { if (!this.isInitialized) return; try { // Shutdown services in reverse order await this.analytics.shutdown(); await this.cache.shutdown(); await this.sync.shutdown(); await this.memory.shutdown(); await this.storage.shutdown(); await this.database.shutdown(); this.isInitialized = false; this.emit('shutdown', { timestamp: new Date() }); console.log('🔌 MEMORAI Service shutdown completed'); } catch (error) { console.error('❌ Error during MEMORAI Service shutdown:', error); throw error; } } // ==================== DATABASE OPERATIONS ==================== async query(query) { try { const results = await this.database.execute(query); // Cache results if cacheable if (query.operation === 'select' && results.length > 0) { const cacheKey = this.generateQueryCacheKey(query); await this.cache.set(cacheKey, results, { ttl: 300 }); // 5 minutes } this.emit('database:query', { query, resultCount: results.length }); return { success: true, data: results, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Database query error:', error); return { success: false, error: error instanceof Error ? error.message : 'Database query failed', timestamp: new Date(), requestId: this.generateRequestId() }; } } async insert(table, data) { return this.query({ table, operation: 'insert', data: data }); } async update(table, id, data) { return this.query({ table, operation: 'update', conditions: [{ field: 'id', operator: '=', value: id }], data: data }); } async delete(table, id) { const result = await this.query({ table, operation: 'delete', conditions: [{ field: 'id', operator: '=', value: id }] }); return { ...result, data: result.success }; } async find(table, conditions, options) { try { // Check cache first const cacheKey = this.generateFindCacheKey(table, conditions, options); const cachedResult = await this.cache.get(cacheKey); if (cachedResult) { this.emit('cache:hit', { key: cacheKey }); return { success: true, data: cachedResult, pagination: await this.calculatePagination(table, options?.limit, options?.offset), timestamp: new Date(), requestId: this.generateRequestId() }; } const results = await this.database.execute({ table, operation: 'select', conditions, limit: options?.limit, offset: options?.offset, orderBy: options?.orderBy }); // Cache results await this.cache.set(cacheKey, results, { ttl: 300 }); return { success: true, data: results, pagination: await this.calculatePagination(table, options?.limit, options?.offset), timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Find operation error:', error); return { success: false, error: error instanceof Error ? error.message : 'Find operation failed', data: [], pagination: { page: 1, limit: options?.limit || 10, total: 0, totalPages: 0, hasNextPage: false, hasPreviousPage: false }, timestamp: new Date(), requestId: this.generateRequestId() }; } } // ==================== MEMORY OPERATIONS ==================== async storeMemory(memory) { try { const storedMemory = await this.memory.create(memory); // Sync to other apps if needed if (storedMemory.isShared) { const syncOp = { userId: storedMemory.userId, appId: storedMemory.appId, operation: 'insert', table: 'memories', recordId: storedMemory.id, data: storedMemory, status: 'pending', retryCount: 0, priority: storedMemory.importance * 10 }; await this.sync.scheduleOperation(syncOp); } this.emit('memory:stored', { memory: storedMemory }); return { success: true, data: storedMemory, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Store memory error:', error); return { success: false, error: error instanceof Error ? error.message : 'Failed to store memory', timestamp: new Date(), requestId: this.generateRequestId() }; } } async searchMemories(query) { try { const results = await this.memory.search(query); this.emit('memory:searched', { query, resultCount: results.length }); return { success: true, data: results, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Search memories error:', error); return { success: false, error: error instanceof Error ? error.message : 'Memory search failed', data: [], timestamp: new Date(), requestId: this.generateRequestId() }; } } async getMemory(id, userId) { try { const memory = await this.memory.get(id, userId); if (memory) { this.emit('memory:accessed', { memoryId: id, userId }); } return { success: true, data: memory, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Get memory error:', error); return { success: false, error: error instanceof Error ? error.message : 'Failed to get memory', timestamp: new Date(), requestId: this.generateRequestId() }; } } // ==================== STORAGE OPERATIONS ==================== async uploadFile(file, userId) { try { // Generate path based on user and date const date = new Date(); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const path = `uploads/${userId}/${year}/${month}/${file.filename}`; const uploadedFile = await this.storage.upload(file, path); // Store file metadata in database await this.database.execute({ table: 'storage_files', operation: 'insert', data: { ...uploadedFile, userId } }); this.emit('storage:uploaded', { file: uploadedFile, userId }); return { success: true, data: uploadedFile, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Upload file error:', error); return { success: false, error: error instanceof Error ? error.message : 'File upload failed', timestamp: new Date(), requestId: this.generateRequestId() }; } } async downloadFile(fileId, userId) { try { // Get file metadata const fileResult = await this.find('storage_files', [ { field: 'id', operator: '=', value: fileId }, { field: 'userId', operator: '=', value: userId } ]); if (!fileResult.success || !fileResult.data || fileResult.data.length === 0) { return { success: false, error: 'File not found or access denied', timestamp: new Date(), requestId: this.generateRequestId() }; } const file = fileResult.data[0]; const buffer = await this.storage.download(file.path); // Update download count await this.update('storage_files', fileId, { downloadCount: file.downloadCount + 1 }); this.emit('storage:downloaded', { fileId, userId }); return { success: true, data: buffer, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Download file error:', error); return { success: false, error: error instanceof Error ? error.message : 'File download failed', timestamp: new Date(), requestId: this.generateRequestId() }; } } async deleteFile(fileId, userId) { try { // Get file metadata const fileResult = await this.find('storage_files', [ { field: 'id', operator: '=', value: fileId }, { field: 'userId', operator: '=', value: userId } ]); if (!fileResult.success || !fileResult.data || fileResult.data.length === 0) { return { success: false, error: 'File not found or access denied', timestamp: new Date(), requestId: this.generateRequestId() }; } const file = fileResult.data[0]; // Delete from storage provider await this.storage.delete(file.path); // Delete from database await this.delete('storage_files', fileId); this.emit('storage:deleted', { fileId, userId }); return { success: true, data: true, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Delete file error:', error); return { success: false, error: error instanceof Error ? error.message : 'File deletion failed', timestamp: new Date(), requestId: this.generateRequestId() }; } } // ==================== ANALYTICS OPERATIONS ==================== async trackEvent(event) { try { await this.analytics.track(event); this.emit('analytics:tracked', { event }); return { success: true, data: true, timestamp: new Date(), requestId: this.generateRequestId() }; } catch (error) { console.error('Track event error:', error); return { success: false, error: error instanceof Error ? error.message : 'Event tracking failed', timestamp: new Date(), requestId: this.generateRequestId() }; } } // ==================== CACHE OPERATIONS ==================== async cacheGet(key) { return this.cache.get(key); } async cacheSet(key, value, options) { return this.cache.set(key, value, options); } async cacheDelete(key) { return this.cache.delete(key); } async cacheClear(pattern) { return this.cache.clear(pattern); } // ==================== SERVICE HEALTH ==================== async getHealth() { const services = {}; // Check each service const serviceChecks = [ { name: 'database', service: this.database }, { name: 'storage', service: this.storage }, { name: 'memory', service: this.memory }, { name: 'sync', service: this.sync }, { name: 'cache', service: this.cache }, { name: 'analytics', service: this.analytics } ]; for (const { name, service } of serviceChecks) { try { const start = Date.now(); const health = await service.getHealth?.() || { status: 'unknown' }; const latency = Date.now() - start; services[name] = { status: health.status || 'unknown', latency }; } catch (error) { services[name] = { status: 'error', error: error instanceof Error ? error.message : 'Unknown error' }; } } // Determine overall status const statuses = Object.values(services).map(s => s.status); const overallStatus = statuses.includes('error') || statuses.includes('unhealthy') ? 'unhealthy' : statuses.includes('degraded') ? 'degraded' : 'healthy'; return { status: overallStatus, services, timestamp: new Date() }; } // ==================== PRIVATE METHODS ==================== setupEventHandlers() { // Forward service events const services = [this.database, this.storage, this.memory, this.sync, this.cache, this.analytics]; services.forEach(service => { if (service && typeof service.on === 'function') { service.on('error', (error) => this.emit('service:error', { service: service.constructor.name, error })); service.on('warning', (warning) => this.emit('service:warning', { service: service.constructor.name, warning })); } }); } generateQueryCacheKey(query) { const key = `query:${query.table}:${query.operation}:${JSON.stringify({ conditions: query.conditions, fields: query.fields, limit: query.limit, offset: query.offset, orderBy: query.orderBy })}`; return Buffer.from(key).toString('base64'); } generateFindCacheKey(table, conditions, options) { const key = `find:${table}:${JSON.stringify({ conditions, options })}`; return Buffer.from(key).toString('base64'); } async calculatePagination(table, limit, offset) { const pageLimit = limit || 10; const pageOffset = offset || 0; const currentPage = Math.floor(pageOffset / pageLimit) + 1; // Get total count (this should be optimized with a separate count query) const countResult = await this.database.execute({ table, operation: 'select', fields: ['COUNT(*) as count'] }); const total = countResult[0]?.count || 0; const totalPages = Math.ceil(total / pageLimit); return { page: currentPage, limit: pageLimit, total, totalPages, hasNextPage: currentPage < totalPages, hasPreviousPage: currentPage > 1 }; } generateRequestId() { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // ==================== GETTERS ==================== get isReady() { return this.isInitialized; } get configuration() { return { ...this.config }; // Return copy to prevent mutation } } // Export singleton instance export const memorai = MemoraiService.getInstance(); export default MemoraiService; //# sourceMappingURL=MemoraiService.js.map