Skip to main content
Glama
repository.ts6.22 kB
import { v4 as uuidv4 } from 'uuid'; import type { Memory, MemoryInput, SearchOptions, SearchResult } from '../types/index.js'; import { DatabaseConnection } from './connection.js'; export class MemoryRepository { private db; constructor(dbPathOrConnection?: string | DatabaseConnection) { if (typeof dbPathOrConnection === 'string') { this.db = DatabaseConnection.getInstance(dbPathOrConnection).getDb(); } else { this.db = (dbPathOrConnection || DatabaseConnection.getInstance()).getDb(); } } async create(input: MemoryInput, embedding?: number[]): Promise<Memory> { const id = uuidv4(); const now = new Date(); const insertMemory = this.db.prepare(` INSERT INTO memories (id, content, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?) `); insertMemory.run( id, input.content, input.metadata ? JSON.stringify(input.metadata) : null, now.toISOString(), now.toISOString(), ); if (embedding) { const insertEmbedding = this.db.prepare(` INSERT INTO memory_embeddings (memory_id, embedding) VALUES (?, ?) `); const vectorBuffer = Buffer.from(new Float32Array(embedding).buffer); insertEmbedding.run(id, vectorBuffer); } return this.findById(id) as Memory; } findById(id: string): Memory | null { const stmt = this.db.prepare(` SELECT id, content, metadata, created_at, updated_at FROM memories WHERE id = ? `); const row = stmt.get(id) as | { id: string; content: string; metadata: string | null; created_at: string; updated_at: string; } | undefined; if (!row) return null; return this.rowToMemory(row); } findAll(limit = 100): Memory[] { const stmt = this.db.prepare(` SELECT id, content, metadata, created_at, updated_at FROM memories ORDER BY created_at DESC LIMIT ? `); const rows = stmt.all(limit) as { id: string; content: string; metadata: string | null; created_at: string; updated_at: string; }[]; return rows.map(row => this.rowToMemory(row)); } async update( id: string, input: Partial<MemoryInput>, embedding?: number[], ): Promise<Memory | null> { const existing = this.findById(id); if (!existing) return null; const updates: string[] = []; const values: (string | number)[] = []; if (input.content !== undefined) { updates.push('content = ?'); values.push(input.content); } if (input.metadata !== undefined) { updates.push('metadata = ?'); values.push(input.metadata ? JSON.stringify(input.metadata) : ''); } if (updates.length > 0) { values.push(id); const stmt = this.db.prepare(` UPDATE memories SET ${updates.join(', ')} WHERE id = ? `); stmt.run(...values); } if (embedding) { const updateEmbedding = this.db.prepare(` INSERT OR REPLACE INTO memory_embeddings (memory_id, embedding) VALUES (?, ?) `); const vectorBuffer = Buffer.from(new Float32Array(embedding).buffer); updateEmbedding.run(id, vectorBuffer); } return this.findById(id); } delete(id: string): boolean { const deleteEmbedding = this.db.prepare( 'DELETE FROM memory_embeddings WHERE memory_id = ?', ); const deleteMemory = this.db.prepare('DELETE FROM memories WHERE id = ?'); deleteEmbedding.run(id); const result = deleteMemory.run(id); return result.changes > 0; } clearAll(): number { const deleteEmbeddings = this.db.prepare('DELETE FROM memory_embeddings'); const deleteMemories = this.db.prepare('DELETE FROM memories'); deleteEmbeddings.run(); const result = deleteMemories.run(); return result.changes; } searchSimilar(queryEmbedding: number[], options: SearchOptions = {}): SearchResult[] { const { limit = 10, threshold } = options; const stmt = this.db.prepare(` SELECT m.id, m.content, m.metadata, m.created_at, m.updated_at, distance FROM memory_embeddings me JOIN memories m ON m.id = me.memory_id WHERE me.embedding MATCH ? AND k = ? ORDER BY distance ASC `); const queryBuffer = Buffer.from(new Float32Array(queryEmbedding).buffer); const rows = stmt.all(queryBuffer, limit) as { id: string; content: string; metadata: string | null; created_at: string; updated_at: string; distance: number; }[]; const results: SearchResult[] = rows.map(row => { const distance: number = row.distance; const similarity = Math.max(0, 1 - distance / 2); return { memory: this.rowToMemory(row), similarity, }; }); if (typeof threshold === 'number') { return results.filter(r => r.similarity >= threshold); } return results; } count(): number { const stmt = this.db.prepare('SELECT COUNT(*) as count FROM memories'); const result = stmt.get() as { count: number }; return result.count; } findWithoutEmbeddings(limit = 100): { id: string; content: string }[] { const stmt = this.db.prepare(` SELECT m.id as id, m.content as content FROM memories m LEFT JOIN memory_embeddings me ON me.memory_id = m.id WHERE me.memory_id IS NULL ORDER BY m.created_at ASC LIMIT ? `); const rows = stmt.all(limit) as { id: string; content: string }[]; return rows; } upsertEmbedding(memoryId: string, embedding: number[]): void { const stmt = this.db.prepare(` INSERT OR REPLACE INTO memory_embeddings (memory_id, embedding) VALUES (?, ?) `); const vectorBuffer = Buffer.from(new Float32Array(embedding).buffer); stmt.run(memoryId, vectorBuffer); } countWithoutEmbeddings(): number { const stmt = this.db.prepare(` SELECT COUNT(1) as count FROM memories m LEFT JOIN memory_embeddings me ON me.memory_id = m.id WHERE me.memory_id IS NULL `); const row = stmt.get() as { count: number }; return row.count; } private rowToMemory(row: { id: string; content: string; metadata: string | null; created_at: string; updated_at: string; }): Memory { return { id: row.id, content: row.content, metadata: row.metadata ? JSON.parse(row.metadata) : undefined, created_at: new Date(row.created_at), updated_at: new Date(row.updated_at), }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/designly1/mcpmem'

If you have feedback or need assistance with the MCP directory API, please join our Discord server