@codai/memorai
Version:
Universal Database & Storage Service for CODAI Ecosystem - CBD Backend
524 lines • 19.6 kB
JavaScript
/**
* 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