UNPKG

@codai/memorai

Version:

Universal Database & Storage Service for CODAI Ecosystem - CBD Backend

647 lines (552 loc) 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 type { MemoraiConfig, Memory, MemoryQuery, MemorySearchResult, StorageFile, StorageUpload, DatabaseQuery, SyncOperation, APIResponse, PaginatedResponse, AnalyticsEvent, CacheEntry, CacheOptions } from '../types' import { DEFAULT_CONFIG, 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 { private static instance: MemoraiService private config: MemoraiConfig private isInitialized = false // Core Services public readonly database: DatabaseService public readonly storage: StorageService public readonly memory: MemoryService public readonly sync: SyncService public readonly cache: CacheService public readonly analytics: AnalyticsService private constructor(config: Partial<MemoraiConfig> = {}) { super() // Create complete configuration this.config = createMemoraiConfig( config, process.env.NODE_ENV as 'development' | 'production' | 'test' ) // 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?: Partial<MemoraiConfig>): MemoraiService { if (!MemoraiService.instance) { MemoraiService.instance = new MemoraiService(config) } return MemoraiService.instance } static async create(config?: Partial<MemoraiConfig>): Promise<MemoraiService> { const instance = MemoraiService.getInstance(config) await instance.initialize() return instance } // ==================== INITIALIZATION ==================== async initialize(): Promise<void> { 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(): Promise<void> { 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<T = any>(query: DatabaseQuery): Promise<APIResponse<T[]>> { 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<T = any>(table: string, data: Partial<T>): Promise<APIResponse<T>> { return this.query({ table, operation: 'insert', data: data as Record<string, any> }) as Promise<APIResponse<T>> } async update<T = any>(table: string, id: string, data: Partial<T>): Promise<APIResponse<T>> { return this.query({ table, operation: 'update', conditions: [{ field: 'id', operator: '=', value: id }], data: data as Record<string, any> }) as Promise<APIResponse<T>> } async delete(table: string, id: string): Promise<APIResponse<boolean>> { const result = await this.query({ table, operation: 'delete', conditions: [{ field: 'id', operator: '=', value: id }] }) return { ...result, data: result.success } } async find<T = any>( table: string, conditions?: DatabaseQuery['conditions'], options?: { limit?: number; offset?: number; orderBy?: DatabaseQuery['orderBy'] } ): Promise<PaginatedResponse<T>> { try { // Check cache first const cacheKey = this.generateFindCacheKey(table, conditions, options) const cachedResult = await this.cache.get<T[]>(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: Partial<Memory>): Promise<APIResponse<Memory>> { try { const storedMemory = await this.memory.create(memory) // Sync to other apps if needed if (storedMemory.isShared) { const syncOp: Partial<SyncOperation> = { 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 as SyncOperation) } 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: MemoryQuery): Promise<APIResponse<MemorySearchResult[]>> { 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: string, userId: string): Promise<APIResponse<Memory | null>> { 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: StorageUpload, userId: string): Promise<APIResponse<StorageFile>> { 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: string, userId: string): Promise<APIResponse<Buffer>> { try { // Get file metadata const fileResult = await this.find<StorageFile>('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: string, userId: string): Promise<APIResponse<boolean>> { try { // Get file metadata const fileResult = await this.find<StorageFile>('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: AnalyticsEvent): Promise<APIResponse<boolean>> { 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<T = any>(key: string): Promise<T | null> { return this.cache.get<T>(key) } async cacheSet<T = any>(key: string, value: T, options?: CacheOptions): Promise<void> { return this.cache.set(key, value, options) } async cacheDelete(key: string): Promise<boolean> { return this.cache.delete(key) } async cacheClear(pattern?: string): Promise<number> { return this.cache.clear(pattern) } // ==================== SERVICE HEALTH ==================== async getHealth(): Promise<{ status: 'healthy' | 'degraded' | 'unhealthy' services: Record<string, { status: string; latency?: number; error?: string }> timestamp: Date }> { const services: Record<string, { status: string; latency?: number; error?: string }> = {} // 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 as any).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 ==================== private setupEventHandlers(): void { // 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: any) => this.emit('service:error', { service: service.constructor.name, error })) service.on('warning', (warning: any) => this.emit('service:warning', { service: service.constructor.name, warning })) } }) } private generateQueryCacheKey(query: DatabaseQuery): string { 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') } private generateFindCacheKey( table: string, conditions?: DatabaseQuery['conditions'], options?: { limit?: number; offset?: number; orderBy?: DatabaseQuery['orderBy'] } ): string { const key = `find:${table}:${JSON.stringify({ conditions, options })}` return Buffer.from(key).toString('base64') } private async calculatePagination( table: string, limit?: number, offset?: number ): Promise<PaginatedResponse<any>['pagination']> { 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 } } private generateRequestId(): string { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` } // ==================== GETTERS ==================== get isReady(): boolean { return this.isInitialized } get configuration(): MemoraiConfig { return { ...this.config } // Return copy to prevent mutation } } // Export singleton instance export const memorai = MemoraiService.getInstance() export default MemoraiService