jay-code
Version:
Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability
461 lines (387 loc) • 11 kB
JavaScript
/**
* Unified Memory Manager
* Provides a single interface for all memory operations across the system
*/
import { promises as fs } from 'fs';
import path from 'path';
import { existsSync } from '../cli/node-compat.js';
export class UnifiedMemoryManager {
constructor(options = {}) {
this.config = {
primaryStore: './.jay-code/memory/unified-memory.db',
fallbackStore: './memory/memory-store.json',
configPath: './.jay-code/memory-config.json',
...options
};
this.isInitialized = false;
this.useSqlite = false;
this.db = null;
}
/**
* Initialize the memory manager
*/
async initialize() {
if (this.isInitialized) return;
// Check if we have a unified database
if (existsSync(this.config.primaryStore)) {
try {
// Try to load SQLite modules
const sqlite3Module = await import('sqlite3');
const sqliteModule = await import('sqlite');
this.sqlite3 = sqlite3Module.default;
this.sqliteOpen = sqliteModule.open;
this.useSqlite = true;
// Open database connection
this.db = await this.sqliteOpen({
filename: this.config.primaryStore,
driver: this.sqlite3.Database
});
// Enable WAL mode for better performance
await this.db.exec('PRAGMA journal_mode = WAL');
} catch (err) {
console.warn('SQLite not available, falling back to JSON store');
this.useSqlite = false;
}
}
this.isInitialized = true;
}
/**
* Store a key-value pair
*/
async store(key, value, namespace = 'default', metadata = {}) {
await this.initialize();
if (this.useSqlite) {
return await this.storeSqlite(key, value, namespace, metadata);
} else {
return await this.storeJson(key, value, namespace, metadata);
}
}
/**
* Store in SQLite database
*/
async storeSqlite(key, value, namespace, metadata) {
const timestamp = Date.now();
await this.db.run(`
INSERT OR REPLACE INTO memory_entries (key, value, namespace, timestamp, source)
VALUES (?, ?, ?, ?, ?)
`, key, value, namespace, timestamp, 'unified-manager');
return { key, value, namespace, timestamp };
}
/**
* Store in JSON file
*/
async storeJson(key, value, namespace, metadata) {
const data = await this.loadJsonData();
if (!data[namespace]) {
data[namespace] = [];
}
// Remove existing entry with same key
data[namespace] = data[namespace].filter((e) => e.key !== key);
// Add new entry
const entry = {
key,
value,
namespace,
timestamp: Date.now(),
...metadata
};
data[namespace].push(entry);
await this.saveJsonData(data);
return entry;
}
/**
* Query memory entries
*/
async query(search, options = {}) {
await this.initialize();
if (this.useSqlite) {
return await this.querySqlite(search, options);
} else {
return await this.queryJson(search, options);
}
}
/**
* Query SQLite database
*/
async querySqlite(search, options) {
const { namespace, limit = 100, offset = 0 } = options;
let query = `
SELECT * FROM memory_entries
WHERE (key LIKE ? OR value LIKE ?)
`;
const params = [`%${search}%`, `%${search}%`];
if (namespace) {
query += ' AND namespace = ?';
params.push(namespace);
}
query += ' ORDER BY timestamp DESC LIMIT ? OFFSET ?';
params.push(limit, offset);
const results = await this.db.all(query, ...params);
return results;
}
/**
* Query JSON store
*/
async queryJson(search, options) {
const data = await this.loadJsonData();
const { namespace, limit = 100, offset = 0 } = options;
const results = [];
const namespaces = namespace ? [namespace] : Object.keys(data);
for (const ns of namespaces) {
if (data[ns]) {
for (const entry of data[ns]) {
if (entry.key.includes(search) || entry.value.includes(search)) {
results.push(entry);
}
}
}
}
// Sort by timestamp (newest first)
results.sort((a, b) => b.timestamp - a.timestamp);
// Apply pagination
return results.slice(offset, offset + limit);
}
/**
* Get memory by exact key
*/
async get(key, namespace = 'default') {
await this.initialize();
if (this.useSqlite) {
const result = await this.db.get(`
SELECT * FROM memory_entries
WHERE key = ? AND namespace = ?
ORDER BY timestamp DESC
LIMIT 1
`, key, namespace);
return result;
} else {
const data = await this.loadJsonData();
if (data[namespace]) {
const entry = data[namespace].find(e => e.key === key);
return entry;
}
return null;
}
}
/**
* Delete memory entry
*/
async delete(key, namespace = 'default') {
await this.initialize();
if (this.useSqlite) {
await this.db.run(`
DELETE FROM memory_entries
WHERE key = ? AND namespace = ?
`, key, namespace);
} else {
const data = await this.loadJsonData();
if (data[namespace]) {
data[namespace] = data[namespace].filter(e => e.key !== key);
await this.saveJsonData(data);
}
}
}
/**
* Clear namespace
*/
async clearNamespace(namespace) {
await this.initialize();
if (this.useSqlite) {
const result = await this.db.run(`
DELETE FROM memory_entries
WHERE namespace = ?
`, namespace);
return result.changes;
} else {
const data = await this.loadJsonData();
const count = data[namespace] ? data[namespace].length : 0;
delete data[namespace];
await this.saveJsonData(data);
return count;
}
}
/**
* Get statistics
*/
async getStats() {
await this.initialize();
if (this.useSqlite) {
const stats = await this.db.get(`
SELECT
COUNT(*) as totalEntries,
COUNT(DISTINCT namespace) as namespaces
FROM memory_entries
`);
const namespaceStats = await this.db.all(`
SELECT namespace, COUNT(*) as count
FROM memory_entries
GROUP BY namespace
`);
// Get database size
const dbInfo = await this.db.get(`
SELECT page_count * page_size as sizeBytes
FROM pragma_page_count(), pragma_page_size()
`);
return {
totalEntries: stats.totalEntries,
namespaces: stats.namespaces,
namespaceStats: namespaceStats.reduce((acc, ns) => {
acc[ns.namespace] = ns.count;
return acc;
}, {}),
sizeBytes: dbInfo.sizeBytes,
storageType: 'sqlite'
};
} else {
const data = await this.loadJsonData();
let totalEntries = 0;
const namespaceStats = {};
for (const [namespace, entries] of Object.entries(data)) {
namespaceStats[namespace] = entries.length;
totalEntries += entries.length;
}
return {
totalEntries,
namespaces: Object.keys(data).length,
namespaceStats,
sizeBytes: new TextEncoder().encode(JSON.stringify(data)).length,
storageType: 'json'
};
}
}
/**
* List all namespaces
*/
async listNamespaces() {
await this.initialize();
if (this.useSqlite) {
const namespaces = await this.db.all(`
SELECT DISTINCT namespace, COUNT(*) as count
FROM memory_entries
GROUP BY namespace
ORDER BY namespace
`);
return namespaces;
} else {
const data = await this.loadJsonData();
return Object.keys(data).map(namespace => ({
namespace,
count: data[namespace].length
}));
}
}
/**
* Export data
*/
async export(filePath, namespace = null) {
await this.initialize();
let exportData;
if (this.useSqlite) {
let query = 'SELECT * FROM memory_entries';
const params = [];
if (namespace) {
query += ' WHERE namespace = ?';
params.push(namespace);
}
const entries = await this.db.all(query, ...params);
// Group by namespace
exportData = entries.reduce((acc, entry) => {
if (!acc[entry.namespace]) {
acc[entry.namespace] = [];
}
acc[entry.namespace].push(entry);
return acc;
}, {});
} else {
const data = await this.loadJsonData();
exportData = namespace ? { [namespace]: data[namespace] || [] } : data;
}
await fs.writeFile(filePath, JSON.stringify(exportData, null, 2));
let totalEntries = 0;
for (const entries of Object.values(exportData)) {
totalEntries += entries.length;
}
return {
namespaces: Object.keys(exportData).length,
entries: totalEntries,
size: new TextEncoder().encode(JSON.stringify(exportData)).length
};
}
/**
* Import data
*/
async import(filePath, options = {}) {
await this.initialize();
const content = await fs.readFile(filePath, 'utf8');
const importData = JSON.parse(content);
let imported = 0;
for (const [namespace, entries] of Object.entries(importData)) {
for (const entry of entries) {
await this.store(
entry.key,
entry.value,
entry.namespace || namespace,
{ timestamp: entry.timestamp, source: filePath }
);
imported++;
}
}
return { imported };
}
/**
* Load JSON data
*/
async loadJsonData() {
try {
const content = await fs.readFile(this.config.fallbackStore, 'utf8');
return JSON.parse(content);
} catch {
return {};
}
}
/**
* Save JSON data
*/
async saveJsonData(data) {
await fs.mkdir(path.dirname(this.config.fallbackStore), { recursive: true });
await fs.writeFile(this.config.fallbackStore, JSON.stringify(data, null, 2));
}
/**
* Close database connection
*/
async close() {
if (this.db) {
await this.db.close();
this.db = null;
this.isInitialized = false;
}
}
/**
* Check if using unified store
*/
isUnified() {
return this.useSqlite;
}
/**
* Get storage info
*/
getStorageInfo() {
return {
type: this.useSqlite ? 'sqlite' : 'json',
path: this.useSqlite ? this.config.primaryStore : this.config.fallbackStore,
unified: this.useSqlite
};
}
}
// Singleton instance
let instance = null;
/**
* Get unified memory manager instance
*/
export function getUnifiedMemory(options = {}) {
if (!instance) {
instance = new UnifiedMemoryManager(options);
}
return instance;
}
export default UnifiedMemoryManager;