import Database from "better-sqlite3";
import path from "path";
import fs from "fs/promises";
import crypto from "crypto";
export interface Memory {
id: string;
key: string;
content: string;
metadata?: Record<string, any>;
timestamp: string;
}
export class MemoryStore {
private db!: Database.Database;
private dataDir: string;
constructor(dataDir: string) {
this.dataDir = dataDir;
}
async initialize(): Promise<void> {
// Ensure data directory exists
await fs.mkdir(this.dataDir, { recursive: true });
// Initialize SQLite database
const dbPath = path.join(this.dataDir, "memories.db");
this.db = new Database(dbPath);
// Create tables if they don't exist
this.db.exec(`
CREATE TABLE IF NOT EXISTS memories (
id TEXT PRIMARY KEY,
key TEXT UNIQUE NOT NULL,
content TEXT NOT NULL,
metadata TEXT,
timestamp TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_memories_key ON memories(key);
CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
`);
}
async set(
key: string,
content: string,
metadata?: Record<string, any>
): Promise<Memory> {
const id = crypto.randomUUID();
const timestamp = new Date().toISOString();
const stmt = this.db.prepare(`
INSERT OR REPLACE INTO memories (id, key, content, metadata, timestamp)
VALUES (?, ?, ?, ?, ?)
`);
stmt.run(
id,
key,
content,
metadata ? JSON.stringify(metadata) : null,
timestamp
);
return {
id,
key,
content,
metadata,
timestamp,
};
}
async get(key: string): Promise<Memory | null> {
const stmt = this.db.prepare(`
SELECT * FROM memories WHERE key = ?
`);
const row = stmt.get(key) as any;
if (!row) return null;
return {
id: row.id,
key: row.key,
content: row.content,
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
timestamp: row.timestamp,
};
}
async getById(id: string): Promise<Memory | null> {
const stmt = this.db.prepare(`
SELECT * FROM memories WHERE id = ?
`);
const row = stmt.get(id) as any;
if (!row) return null;
return {
id: row.id,
key: row.key,
content: row.content,
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
timestamp: row.timestamp,
};
}
async delete(key: string): Promise<boolean> {
const stmt = this.db.prepare(`
DELETE FROM memories WHERE key = ?
`);
const result = stmt.run(key);
return result.changes > 0;
}
async list(limit: number = 100, offset: number = 0): Promise<Memory[]> {
const stmt = this.db.prepare(`
SELECT * FROM memories
ORDER BY timestamp DESC
LIMIT ? OFFSET ?
`);
const rows = stmt.all(limit, offset) as any[];
return rows.map((row) => ({
id: row.id,
key: row.key,
content: row.content,
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
timestamp: row.timestamp,
}));
}
async search(query: string): Promise<Memory[]> {
const stmt = this.db.prepare(`
SELECT * FROM memories
WHERE content LIKE ? OR key LIKE ?
ORDER BY timestamp DESC
LIMIT 20
`);
const searchPattern = `%${query}%`;
const rows = stmt.all(searchPattern, searchPattern) as any[];
return rows.map((row) => ({
id: row.id,
key: row.key,
content: row.content,
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
timestamp: row.timestamp,
}));
}
close(): void {
if (this.db) {
this.db.close();
}
}
}