@clduab11/gemini-flow
Version:
Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.
1,061 lines (940 loc) • 32.2 kB
text/typescript
/**
* SQLite Memory Manager with Fallback Support
*
* Implements 12 specialized tables for persistent memory coordination
* across agent swarms with optimized performance and cross-platform compatibility
*
* Fallback hierarchy:
* 1. better-sqlite3 (performance optimized)
* 2. sqlite3 (Node.js compatible)
* 3. sql.js (WASM cross-platform)
*/
import { Logger } from "../utils/logger.js";
import { EventEmitter } from "events";
import path from "path";
import fs from "fs/promises";
import {
SQLiteDatabase,
SQLiteImplementation,
createSQLiteDatabase,
detectSQLiteImplementations,
} from "./sqlite-adapter.js";
export interface MemoryEntry {
id?: number;
key: string;
value: any;
namespace: string;
metadata?: any;
ttl?: number;
created_at?: number;
updated_at?: number;
access_count?: number;
}
export interface NamespaceInfo {
namespace: string;
keyCount: number;
totalSize: number;
lastAccessed: number;
created: number;
}
export interface NamespaceOperations {
store(entry: MemoryEntry): Promise<void>;
retrieve(key: string, namespace?: string): Promise<any>;
delete(key: string, namespace?: string): Promise<boolean>;
search(
pattern: string,
namespace?: string,
options?: SearchOptions,
): Promise<MemoryEntry[]>;
list(namespace?: string, includeMetadata?: boolean): Promise<MemoryEntry[]>;
cleanup(namespace?: string): Promise<number>;
getNamespaceInfo(namespace?: string): Promise<NamespaceInfo[]>;
deleteNamespace(namespace: string): Promise<number>;
}
export interface SearchOptions {
limit?: number;
sortBy?: "created_at" | "updated_at" | "access_count" | "key";
sortOrder?: "asc" | "desc";
includeExpired?: boolean;
keyPattern?: string;
valuePattern?: string;
}
export class NamespaceUtils {
/**
* Validates namespace format (hierarchical paths allowed)
*/
static validateNamespace(namespace: string): boolean {
if (!namespace || typeof namespace !== "string") {
return false;
}
// Allow alphanumeric, dots, slashes, hyphens, underscores
const validPattern = /^[a-zA-Z0-9._/-]+$/;
return validPattern.test(namespace) && namespace.length <= 255;
}
/**
* Normalizes namespace path (removes duplicates slashes, trims)
*/
static normalizeNamespace(namespace: string): string {
if (!namespace) return "default";
return (
namespace
.trim()
.replace(/\/+/g, "/") // Replace multiple slashes with single
.replace(/^\//g, "") // Remove leading slash
.replace(/\/$/, "") || // Remove trailing slash
"default"
);
}
/**
* Gets parent namespace from hierarchical path
*/
static getParentNamespace(namespace: string): string | null {
const normalized = this.normalizeNamespace(namespace);
const lastSlash = normalized.lastIndexOf("/");
if (lastSlash === -1) {
return null; // Root level namespace
}
return normalized.substring(0, lastSlash) || "default";
}
/**
* Gets namespace depth (number of levels)
*/
static getNamespaceDepth(namespace: string): number {
const normalized = this.normalizeNamespace(namespace);
if (normalized === "default") return 0;
return (normalized.match(/\//g) || []).length + 1;
}
/**
* Checks if namespace matches pattern (supports wildcards)
*/
static matchesPattern(namespace: string, pattern: string): boolean {
const normalizedNamespace = this.normalizeNamespace(namespace);
const normalizedPattern = this.normalizeNamespace(pattern);
// Convert pattern to regex (support * and **)
const regexPattern = normalizedPattern
.replace(/\*\*/g, "§DOUBLE_WILDCARD§") // Temporary placeholder
.replace(/\*/g, "[^/]*") // Single * matches anything except /
.replace(/§DOUBLE_WILDCARD§/g, ".*"); // ** matches everything including /
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(normalizedNamespace);
}
/**
* Lists all child namespaces
*/
static getChildNamespaces(
parentNamespace: string,
allNamespaces: string[],
): string[] {
const normalized = this.normalizeNamespace(parentNamespace);
const prefix = normalized === "default" ? "" : normalized + "/";
return allNamespaces
.filter((ns) => {
const normalizedNs = this.normalizeNamespace(ns);
return (
normalizedNs.startsWith(prefix) &&
normalizedNs !== normalized &&
normalizedNs.indexOf("/", prefix.length) === -1
); // Direct children only
})
.map((ns) => this.normalizeNamespace(ns));
}
}
export class SQLiteMemoryManager
extends EventEmitter
implements NamespaceOperations
{
private db: SQLiteDatabase;
private logger: Logger;
private cleanupInterval?: ReturnType<typeof setInterval>;
private implementation: SQLiteImplementation = "sql.js";
private constructor(
db: SQLiteDatabase,
implementation: SQLiteImplementation,
) {
super();
this.logger = new Logger("SQLiteMemory");
this.db = db;
this.implementation = implementation;
this.logger.info(`Using SQLite implementation: ${this.implementation}`);
// Configure database optimization
try {
this.db.pragma("journal_mode = WAL");
this.db.pragma("synchronous = NORMAL");
this.db.pragma("cache_size = 10000");
this.db.pragma("temp_store = memory");
} catch (error) {
// Some implementations may not support all pragmas
this.logger.warn("Some pragma optimizations not supported:", error);
}
this.initializeTables();
this.startCleanupTask();
}
/**
* Create a new SQLiteMemoryManager instance with fallback support
*/
static async create(
dbPath: string = ".swarm/memory.db",
preferredImpl?: SQLiteImplementation,
): Promise<SQLiteMemoryManager> {
const logger = new Logger("SQLiteMemory");
try {
// Ensure directory exists
const dir = path.dirname(dbPath);
await fs.mkdir(dir, { recursive: true }).catch(() => {});
// Detect available implementations
const detection = await detectSQLiteImplementations();
logger.info("SQLite implementations detected:", {
available: detection.available,
recommended: detection.recommended,
errors: Object.keys(detection.errors),
});
// Create database with fallback
const db = await createSQLiteDatabase(dbPath, preferredImpl);
const implementation = db.name as SQLiteImplementation;
return new SQLiteMemoryManager(db, implementation);
} catch (error) {
logger.error("Failed to initialize SQLite database:", error);
throw new Error(`SQLite initialization failed: ${error}`);
}
}
/**
* Initialize all 12 specialized tables
*/
private initializeTables() {
// 1. Agents table - Agent state and configuration
this.db.exec(`
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
name TEXT NOT NULL,
status TEXT DEFAULT 'inactive',
capabilities TEXT,
configuration TEXT,
metrics TEXT,
last_active INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
CREATE INDEX IF NOT EXISTS idx_agents_type ON agents(type);
`);
// 2. Swarms table - Swarm coordination and topology
this.db.exec(`
CREATE TABLE IF NOT EXISTS swarms (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
topology TEXT NOT NULL,
queen_id TEXT,
worker_count INTEGER DEFAULT 0,
status TEXT DEFAULT 'initializing',
configuration TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_swarms_status ON swarms(status);
`);
// 3. Tasks table - Task orchestration and tracking
this.db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
swarm_id TEXT,
description TEXT NOT NULL,
status TEXT DEFAULT 'pending',
priority INTEGER DEFAULT 5,
assignee_id TEXT,
dependencies TEXT,
result TEXT,
error TEXT,
started_at INTEGER,
completed_at INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (swarm_id) REFERENCES swarms(id)
);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_swarm ON tasks(swarm_id);
`);
// 4. Memory store table - Key-value persistent storage
this.db.exec(`
CREATE TABLE IF NOT EXISTS memory_store (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL,
value TEXT NOT NULL,
namespace TEXT DEFAULT 'default',
metadata TEXT,
ttl INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now')),
access_count INTEGER DEFAULT 0,
UNIQUE(key, namespace)
);
CREATE INDEX IF NOT EXISTS idx_memory_key ON memory_store(key);
CREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory_store(namespace);
CREATE INDEX IF NOT EXISTS idx_memory_ttl ON memory_store(ttl);
`);
// 5. Coordination events table - Cross-agent coordination
this.db.exec(`
CREATE TABLE IF NOT EXISTS coordination_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
source_agent TEXT NOT NULL,
target_agents TEXT,
payload TEXT,
status TEXT DEFAULT 'pending',
created_at INTEGER DEFAULT (strftime('%s', 'now')),
processed_at INTEGER
);
CREATE INDEX IF NOT EXISTS idx_coord_status ON coordination_events(status);
CREATE INDEX IF NOT EXISTS idx_coord_source ON coordination_events(source_agent);
`);
// 6. Neural patterns table - Learning and pattern storage
this.db.exec(`
CREATE TABLE IF NOT EXISTS neural_patterns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pattern_type TEXT NOT NULL,
pattern_data TEXT NOT NULL,
confidence REAL DEFAULT 0.0,
usage_count INTEGER DEFAULT 0,
success_rate REAL DEFAULT 0.0,
metadata TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_patterns_type ON neural_patterns(pattern_type);
CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON neural_patterns(confidence);
`);
// 7. Metrics table - Performance and analytics
this.db.exec(`
CREATE TABLE IF NOT EXISTS metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
metric_name TEXT NOT NULL,
metric_value REAL NOT NULL,
unit TEXT,
source TEXT,
tags TEXT,
timestamp INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_metrics_name ON metrics(metric_name);
CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON metrics(timestamp);
`);
// 8. Bottlenecks table - Performance bottleneck tracking
this.db.exec(`
CREATE TABLE IF NOT EXISTS bottlenecks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
component TEXT NOT NULL,
bottleneck_type TEXT NOT NULL,
severity INTEGER DEFAULT 5,
impact_score REAL DEFAULT 0.0,
resolution_status TEXT DEFAULT 'detected',
details TEXT,
detected_at INTEGER DEFAULT (strftime('%s', 'now')),
resolved_at INTEGER
);
CREATE INDEX IF NOT EXISTS idx_bottlenecks_status ON bottlenecks(resolution_status);
CREATE INDEX IF NOT EXISTS idx_bottlenecks_severity ON bottlenecks(severity);
`);
// 9. Sessions table - Session management
this.db.exec(`
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
user_id TEXT,
swarm_id TEXT,
status TEXT DEFAULT 'active',
context TEXT,
metadata TEXT,
started_at INTEGER DEFAULT (strftime('%s', 'now')),
ended_at INTEGER,
FOREIGN KEY (swarm_id) REFERENCES swarms(id)
);
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
`);
// 10. Hooks table - Automation hooks configuration
this.db.exec(`
CREATE TABLE IF NOT EXISTS hooks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hook_type TEXT NOT NULL,
event_pattern TEXT NOT NULL,
action TEXT NOT NULL,
configuration TEXT,
enabled INTEGER DEFAULT 1,
execution_count INTEGER DEFAULT 0,
last_executed INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_hooks_type ON hooks(hook_type);
CREATE INDEX IF NOT EXISTS idx_hooks_enabled ON hooks(enabled);
`);
// 11. GitHub integrations table - GitHub-specific data
this.db.exec(`
CREATE TABLE IF NOT EXISTS github_integrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
repo_name TEXT NOT NULL,
integration_type TEXT NOT NULL,
configuration TEXT,
last_sync INTEGER,
sync_status TEXT DEFAULT 'pending',
metadata TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_github_repo ON github_integrations(repo_name);
CREATE INDEX IF NOT EXISTS idx_github_type ON github_integrations(integration_type);
`);
// 12. Google Workspace table - Google integration data
this.db.exec(`
CREATE TABLE IF NOT EXISTS google_workspace (
id INTEGER PRIMARY KEY AUTOINCREMENT,
service_type TEXT NOT NULL,
resource_id TEXT,
resource_name TEXT,
sync_status TEXT DEFAULT 'pending',
last_sync INTEGER,
configuration TEXT,
metadata TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_workspace_service ON google_workspace(service_type);
CREATE INDEX IF NOT EXISTS idx_workspace_status ON google_workspace(sync_status);
`);
this.logger.info("All 12 SQLite tables initialized successfully");
}
/**
* Store memory entry with namespace validation and hierarchy support
*/
async store(entry: MemoryEntry): Promise<void> {
try {
// Validate and normalize namespace
const namespace = NamespaceUtils.normalizeNamespace(
entry.namespace || "default",
);
if (!NamespaceUtils.validateNamespace(namespace)) {
throw new Error(`Invalid namespace format: ${entry.namespace}`);
}
// Enhanced entry with normalized namespace
const normalizedEntry = {
...entry,
namespace,
};
// Handle different SQL syntax for UPSERT across implementations
let sql = "";
if (this.implementation === "better-sqlite3") {
sql = `
INSERT INTO memory_store (key, value, namespace, metadata, ttl)
VALUES (, , , , )
ON CONFLICT(key, namespace) DO UPDATE SET
value = ,
metadata = ,
ttl = ,
updated_at = strftime('%s', 'now'),
access_count = access_count + 1
`;
} else {
// Fallback for sqlite3 and sql.js
sql = `
INSERT OR REPLACE INTO memory_store (key, value, namespace, metadata, ttl, access_count)
VALUES (?, ?, ?, ?, ?,
COALESCE((SELECT access_count + 1 FROM memory_store WHERE key = ? AND namespace = ?), 1)
)
`;
}
const stmt = this.db.prepare(sql);
if (this.implementation === "better-sqlite3") {
stmt.run({
key: normalizedEntry.key,
value: JSON.stringify(normalizedEntry.value),
namespace: namespace,
metadata: normalizedEntry.metadata
? JSON.stringify(normalizedEntry.metadata)
: null,
ttl: normalizedEntry.ttl
? Math.floor(Date.now() / 1000) + normalizedEntry.ttl
: null,
});
} else {
await stmt.run(
normalizedEntry.key,
JSON.stringify(normalizedEntry.value),
namespace,
normalizedEntry.metadata
? JSON.stringify(normalizedEntry.metadata)
: null,
normalizedEntry.ttl
? Math.floor(Date.now() / 1000) + normalizedEntry.ttl
: null,
normalizedEntry.key,
namespace,
);
}
this.emit("stored", normalizedEntry);
this.logger.debug(`Stored entry in namespace: ${namespace}`);
} catch (error) {
this.logger.error("Failed to store memory entry:", error);
throw error;
}
}
/**
* Retrieve memory entry with namespace support and wildcard matching
*/
async retrieve(key: string, namespace: string = "default"): Promise<any> {
try {
// Validate and normalize namespace
const normalizedNamespace = NamespaceUtils.normalizeNamespace(namespace);
if (!NamespaceUtils.validateNamespace(normalizedNamespace)) {
throw new Error(`Invalid namespace format: ${namespace}`);
}
// Handle different SQL syntax across implementations
let sql = "";
if (this.implementation === "better-sqlite3") {
sql = `
UPDATE memory_store
SET access_count = access_count + 1
WHERE key = ? AND namespace = ?
AND (ttl IS NULL OR ttl > strftime('%s', 'now'))
RETURNING value, metadata, namespace
`;
} else {
// Fallback for sqlite3 and sql.js (no RETURNING clause)
sql = `
SELECT value, metadata, namespace FROM memory_store
WHERE key = ? AND namespace = ?
AND (ttl IS NULL OR ttl > strftime('%s', 'now'))
`;
}
const stmt = this.db.prepare(sql);
let row: any;
if (this.implementation === "better-sqlite3") {
row = stmt.get(key, normalizedNamespace);
} else {
row = await stmt.get(key, normalizedNamespace);
// Update access count separately for sqlite3/sql.js
if (row) {
const updateStmt = this.db.prepare(`
UPDATE memory_store SET access_count = access_count + 1
WHERE key = ? AND namespace = ?
`);
await updateStmt.run(key, normalizedNamespace);
}
}
if (!row) {
return null;
}
this.emit("retrieved", { key, namespace: normalizedNamespace });
this.logger.debug(
`Retrieved key '${key}' from namespace: ${normalizedNamespace}`,
);
return {
value: JSON.parse(row.value),
metadata: row.metadata ? JSON.parse(row.metadata) : null,
namespace: row.namespace,
};
} catch (error) {
this.logger.error("Failed to retrieve memory entry:", error);
throw error;
}
}
/**
* Enhanced search with namespace patterns and advanced filtering
*/
async search(
pattern: string,
namespace?: string,
options: SearchOptions = {},
): Promise<MemoryEntry[]> {
try {
const {
limit = 100,
sortBy = "access_count",
sortOrder = "desc",
includeExpired = false,
keyPattern,
valuePattern,
} = options;
let query = `
SELECT key, value, namespace, metadata, created_at, updated_at, access_count
FROM memory_store
WHERE 1=1
`;
const params: any[] = [];
// Add TTL filter unless explicitly including expired
if (!includeExpired) {
query += " AND (ttl IS NULL OR ttl > strftime('%s', 'now'))";
}
// Add key pattern filter
if (keyPattern || pattern) {
query += " AND key LIKE ?";
params.push(`%${keyPattern || pattern}%`);
}
// Add value pattern filter (search within JSON)
if (valuePattern) {
query += " AND value LIKE ?";
params.push(`%${valuePattern}%`);
}
// Add namespace filter with wildcard support
if (namespace) {
const normalizedNamespace =
NamespaceUtils.normalizeNamespace(namespace);
if (namespace.includes("*")) {
// Wildcard namespace matching
const namespacePattern = normalizedNamespace
.replace(/\*/g, "%")
.replace(/\?/g, "_");
query += " AND namespace LIKE ?";
params.push(namespacePattern);
} else {
// Exact namespace match
query += " AND namespace = ?";
params.push(normalizedNamespace);
}
}
// Add sorting
const validSortColumns = [
"created_at",
"updated_at",
"access_count",
"key",
];
const validSortOrder = ["asc", "desc"];
if (
validSortColumns.includes(sortBy) &&
validSortOrder.includes(sortOrder)
) {
query += ` ORDER BY ${sortBy} ${sortOrder.toUpperCase()}`;
} else {
query += " ORDER BY access_count DESC";
}
// Add limit
query += ` LIMIT ${Math.min(Math.max(1, limit), 1000)}`;
const stmt = this.db.prepare(query);
const rows = (await stmt.all(...params)) as any[];
this.logger.debug(
`Search found ${rows.length} entries with pattern: ${pattern}`,
);
return rows.map((row) => ({
key: row.key,
value: JSON.parse(row.value),
namespace: row.namespace,
metadata: row.metadata ? JSON.parse(row.metadata) : null,
created_at: row.created_at,
updated_at: row.updated_at,
access_count: row.access_count,
}));
} catch (error) {
this.logger.error("Failed to search memory entries:", error);
throw error;
}
}
/**
* Record metric
*/
async recordMetric(name: string, value: number, unit?: string, tags?: any) {
try {
const stmt = this.db.prepare(`
INSERT INTO metrics (metric_name, metric_value, unit, tags)
VALUES (?, ?, ?, ?)
`);
await stmt.run(name, value, unit, tags ? JSON.stringify(tags) : null);
} catch (error) {
this.logger.error("Failed to record metric:", error);
throw error;
}
}
/**
* Get metrics summary
*/
async getMetricsSummary(name: string, timeRange?: number): Promise<any> {
try {
const since = timeRange ? Math.floor(Date.now() / 1000) - timeRange : 0;
const stmt = this.db.prepare(`
SELECT
COUNT(*) as count,
AVG(metric_value) as avg,
MIN(metric_value) as min,
MAX(metric_value) as max,
SUM(metric_value) as sum
FROM metrics
WHERE metric_name = ?
AND timestamp > ?
`);
return await stmt.get(name, since);
} catch (error) {
this.logger.error("Failed to get metrics summary:", error);
throw error;
}
}
/**
* Cleanup expired entries
*/
private startCleanupTask() {
this.cleanupInterval = setInterval(async () => {
try {
const stmt = this.db.prepare(`
DELETE FROM memory_store
WHERE ttl IS NOT NULL AND ttl < strftime('%s', 'now')
`);
const result = await stmt.run();
if (result.changes && result.changes > 0) {
this.logger.debug(`Cleaned up ${result.changes} expired entries`);
}
} catch (error) {
this.logger.error("Cleanup task failed:", error);
}
}, 60000); // Run every minute
}
/**
* Get current implementation info
*/
async getImplementationInfo(): Promise<{
name: SQLiteImplementation;
available: SQLiteImplementation[];
}> {
const detection = await detectSQLiteImplementations();
return {
name: this.implementation,
available: detection.available,
};
}
/**
* Test database connection and functionality
*/
async testConnection(): Promise<boolean> {
try {
const stmt = this.db.prepare("SELECT 1 as test");
const result = await stmt.get();
return result && result.test === 1;
} catch (error) {
this.logger.error("Database connection test failed:", error);
return false;
}
}
/**
* Delete a specific memory entry
*/
async delete(key: string, namespace: string = "default"): Promise<boolean> {
try {
const normalizedNamespace = NamespaceUtils.normalizeNamespace(namespace);
const stmt = this.db.prepare(`
DELETE FROM memory_store
WHERE key = ? AND namespace = ?
`);
const result = await stmt.run(key, normalizedNamespace);
const deleted = (result.changes || 0) > 0;
if (deleted) {
this.emit("deleted", { key, namespace: normalizedNamespace });
this.logger.debug(
`Deleted key '${key}' from namespace: ${normalizedNamespace}`,
);
}
return deleted;
} catch (error) {
this.logger.error("Failed to delete memory entry:", error);
throw error;
}
}
/**
* List all entries in a namespace
*/
async list(
namespace?: string,
includeMetadata: boolean = false,
): Promise<MemoryEntry[]> {
try {
let query = `
SELECT key, value, namespace, metadata, created_at, updated_at, access_count
FROM memory_store
WHERE (ttl IS NULL OR ttl > strftime('%s', 'now'))
`;
const params: any[] = [];
if (namespace) {
const normalizedNamespace =
NamespaceUtils.normalizeNamespace(namespace);
if (namespace.includes("*")) {
// Wildcard namespace matching
const namespacePattern = normalizedNamespace
.replace(/\*/g, "%")
.replace(/\?/g, "_");
query += " AND namespace LIKE ?";
params.push(namespacePattern);
} else {
query += " AND namespace = ?";
params.push(normalizedNamespace);
}
}
query += " ORDER BY namespace, key";
const stmt = this.db.prepare(query);
const rows = (await stmt.all(...params)) as any[];
return rows.map((row) => ({
key: row.key,
value: includeMetadata ? JSON.parse(row.value) : "[hidden]",
namespace: row.namespace,
metadata: row.metadata ? JSON.parse(row.metadata) : null,
created_at: row.created_at,
updated_at: row.updated_at,
access_count: row.access_count,
}));
} catch (error) {
this.logger.error("Failed to list memory entries:", error);
throw error;
}
}
/**
* Cleanup expired entries in a namespace
*/
async cleanup(namespace?: string): Promise<number> {
try {
let query = `
DELETE FROM memory_store
WHERE ttl IS NOT NULL AND ttl < strftime('%s', 'now')
`;
const params: any[] = [];
if (namespace) {
const normalizedNamespace =
NamespaceUtils.normalizeNamespace(namespace);
query += " AND namespace = ?";
params.push(normalizedNamespace);
}
const stmt = this.db.prepare(query);
const result = await stmt.run(...params);
const cleaned = result.changes || 0;
if (cleaned > 0) {
this.emit("cleanup", { namespace, count: cleaned });
this.logger.info(
`Cleaned up ${cleaned} expired entries${namespace ? ` in namespace: ${namespace}` : ""}`,
);
}
return cleaned;
} catch (error) {
this.logger.error("Failed to cleanup memory entries:", error);
throw error;
}
}
/**
* Get namespace information and statistics
*/
async getNamespaceInfo(namespace?: string): Promise<NamespaceInfo[]> {
try {
let query = `
SELECT
namespace,
COUNT(*) as keyCount,
SUM(LENGTH(value)) as totalSize,
MAX(updated_at) as lastAccessed,
MIN(created_at) as created
FROM memory_store
WHERE (ttl IS NULL OR ttl > strftime('%s', 'now'))
`;
const params: any[] = [];
if (namespace) {
const normalizedNamespace =
NamespaceUtils.normalizeNamespace(namespace);
if (namespace.includes("*")) {
const namespacePattern = normalizedNamespace
.replace(/\*/g, "%")
.replace(/\?/g, "_");
query += " AND namespace LIKE ?";
params.push(namespacePattern);
} else {
query += " AND namespace = ?";
params.push(normalizedNamespace);
}
}
query += " GROUP BY namespace ORDER BY namespace";
const stmt = this.db.prepare(query);
const rows = (await stmt.all(...params)) as any[];
return rows.map((row) => ({
namespace: row.namespace,
keyCount: row.keyCount,
totalSize: row.totalSize || 0,
lastAccessed: row.lastAccessed,
created: row.created,
}));
} catch (error) {
this.logger.error("Failed to get namespace info:", error);
throw error;
}
}
/**
* Delete entire namespace and all its entries
*/
async deleteNamespace(namespace: string): Promise<number> {
try {
const normalizedNamespace = NamespaceUtils.normalizeNamespace(namespace);
if (normalizedNamespace === "default") {
throw new Error("Cannot delete default namespace");
}
const stmt = this.db.prepare(`
DELETE FROM memory_store
WHERE namespace = ?
`);
const result = await stmt.run(normalizedNamespace);
const deleted = result.changes || 0;
if (deleted > 0) {
this.emit("namespaceDeleted", {
namespace: normalizedNamespace,
count: deleted,
});
this.logger.info(
`Deleted namespace '${normalizedNamespace}' with ${deleted} entries`,
);
}
return deleted;
} catch (error) {
this.logger.error("Failed to delete namespace:", error);
throw error;
}
}
/**
* Get namespace-specific metrics and analytics
*/
async getNamespaceMetrics(
namespace?: string,
timeRange?: number,
): Promise<any> {
try {
const since = timeRange ? Math.floor(Date.now() / 1000) - timeRange : 0;
let query = `
SELECT
namespace,
COUNT(*) as totalEntries,
AVG(access_count) as avgAccessCount,
MAX(access_count) as maxAccessCount,
COUNT(CASE WHEN ttl IS NOT NULL THEN 1 END) as entriesWithTTL,
AVG(LENGTH(value)) as avgValueSize,
MAX(LENGTH(value)) as maxValueSize
FROM memory_store
WHERE created_at > ?
AND (ttl IS NULL OR ttl > strftime('%s', 'now'))
`;
const params: any[] = [since];
if (namespace) {
const normalizedNamespace =
NamespaceUtils.normalizeNamespace(namespace);
query += " AND namespace = ?";
params.push(normalizedNamespace);
}
query += " GROUP BY namespace ORDER BY totalEntries DESC";
const stmt = this.db.prepare(query);
const rows = (await stmt.all(...params)) as any[];
return rows.map((row) => ({
namespace: row.namespace,
totalEntries: row.totalEntries,
avgAccessCount: Math.round(row.avgAccessCount * 100) / 100,
maxAccessCount: row.maxAccessCount,
entriesWithTTL: row.entriesWithTTL,
avgValueSize: Math.round(row.avgValueSize || 0),
maxValueSize: row.maxValueSize || 0,
}));
} catch (error) {
this.logger.error("Failed to get namespace metrics:", error);
throw error;
}
}
/**
* Close database connection
*/
close() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.db.close();
this.logger.info(`SQLite database closed (${this.implementation})`);
}
}