mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
- 073_memory_vec.sql: creates memory_vec_meta singleton table (active_dim, embedding_signature, last_reset_at, vec_loaded) and adds needs_reindex column to memories table with a partial index; idempotent via CREATE IF NOT EXISTS + INSERT OR IGNORE + migration runner's duplicate-column-name guard - src/lib/db/memoryVec.ts: implements 6 CRUD functions per §3.8 contract (getMemoryVecMeta, setMemoryVecMeta, markMemoryNeedsReindex, markAllMemoriesNeedReindex, getMemoryReindexQueue, countMemoryReindexPending) - src/lib/localDb.ts: adds re-export block for the 6 functions (Hard Rule #2) - .env.example: documents 7 new MEMORY_* env vars per §3.9 - tests/unit/memory-vec-meta.test.ts: 7 tests (meta get/set, migration idempotency) - tests/unit/memory-needs-reindex.test.ts: 12 tests (mark/unmark, markAll, queue)
139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
/**
|
|
* db/memoryVec.ts — CRUD for memory vector metadata and reindex state.
|
|
*
|
|
* Plan 21 — Memory Engine Redesign.
|
|
* Raw SQL is allowed here (Hard Rule #5 — src/lib/db/ domain module).
|
|
*
|
|
* This module manages:
|
|
* - `memory_vec_meta`: singleton row tracking the active embedding dim/signature/reset
|
|
* - `memories.needs_reindex`: flag for lazy backfill of missing/stale vectors
|
|
*/
|
|
|
|
import { getDbInstance } from "./core";
|
|
|
|
// ──────────────── Types ────────────────
|
|
|
|
export interface MemoryVecMeta {
|
|
activeDim: number | null;
|
|
embeddingSignature: string | null;
|
|
lastResetAt: string | null;
|
|
vecLoaded: boolean;
|
|
}
|
|
|
|
// ──────────────── memory_vec_meta ────────────────
|
|
|
|
/**
|
|
* Get the singleton memory_vec_meta row.
|
|
* Returns defaults if the row is absent (e.g. migration not yet applied on
|
|
* an in-memory test DB that ran without the migration file).
|
|
*/
|
|
export function getMemoryVecMeta(): MemoryVecMeta {
|
|
const db = getDbInstance();
|
|
const row = db
|
|
.prepare(
|
|
"SELECT active_dim, embedding_signature, last_reset_at, vec_loaded FROM memory_vec_meta WHERE id = 1"
|
|
)
|
|
.get() as
|
|
| {
|
|
active_dim: number | null;
|
|
embedding_signature: string | null;
|
|
last_reset_at: string | null;
|
|
vec_loaded: number;
|
|
}
|
|
| undefined;
|
|
|
|
if (!row) {
|
|
return {
|
|
activeDim: null,
|
|
embeddingSignature: null,
|
|
lastResetAt: null,
|
|
vecLoaded: false,
|
|
};
|
|
}
|
|
|
|
return {
|
|
activeDim: row.active_dim,
|
|
embeddingSignature: row.embedding_signature,
|
|
lastResetAt: row.last_reset_at,
|
|
vecLoaded: row.vec_loaded === 1,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update one or more fields in the singleton memory_vec_meta row.
|
|
* Uses INSERT OR REPLACE to handle the case where the row is missing
|
|
* (e.g. called before or during migration on a test DB).
|
|
*/
|
|
export function setMemoryVecMeta(meta: Partial<MemoryVecMeta>): void {
|
|
const db = getDbInstance();
|
|
|
|
// Read current values first so we can merge (partial update pattern).
|
|
const current = getMemoryVecMeta();
|
|
|
|
const activeDim = "activeDim" in meta ? meta.activeDim ?? null : current.activeDim;
|
|
const embeddingSignature =
|
|
"embeddingSignature" in meta
|
|
? meta.embeddingSignature ?? null
|
|
: current.embeddingSignature;
|
|
const lastResetAt =
|
|
"lastResetAt" in meta ? meta.lastResetAt ?? null : current.lastResetAt;
|
|
const vecLoaded =
|
|
"vecLoaded" in meta ? (meta.vecLoaded ? 1 : 0) : current.vecLoaded ? 1 : 0;
|
|
|
|
db.prepare(
|
|
`INSERT OR REPLACE INTO memory_vec_meta
|
|
(id, active_dim, embedding_signature, last_reset_at, vec_loaded)
|
|
VALUES (1, ?, ?, ?, ?)`
|
|
).run(activeDim, embeddingSignature, lastResetAt, vecLoaded);
|
|
}
|
|
|
|
// ──────────────── memories.needs_reindex ────────────────
|
|
|
|
/**
|
|
* Mark a single memory as needing reindex (or clear the flag).
|
|
*/
|
|
export function markMemoryNeedsReindex(id: string, needs: boolean): void {
|
|
const db = getDbInstance();
|
|
db.prepare("UPDATE memories SET needs_reindex = ? WHERE id = ?").run(needs ? 1 : 0, id);
|
|
}
|
|
|
|
/**
|
|
* Mark ALL memories as needing reindex.
|
|
* Returns the number of rows affected.
|
|
*/
|
|
export function markAllMemoriesNeedReindex(): number {
|
|
const db = getDbInstance();
|
|
const result = db.prepare("UPDATE memories SET needs_reindex = 1").run();
|
|
return result.changes;
|
|
}
|
|
|
|
/**
|
|
* Get a batch of memories that need reindex, ordered by creation date ascending.
|
|
* Returns id, content, and key for each memory so the vector can be regenerated.
|
|
*/
|
|
export function getMemoryReindexQueue(
|
|
limit: number
|
|
): Array<{ id: string; content: string; key: string }> {
|
|
const db = getDbInstance();
|
|
return db
|
|
.prepare(
|
|
`SELECT id, content, COALESCE(key, '') AS key
|
|
FROM memories
|
|
WHERE needs_reindex = 1
|
|
ORDER BY created_at ASC
|
|
LIMIT ?`
|
|
)
|
|
.all(limit) as Array<{ id: string; content: string; key: string }>;
|
|
}
|
|
|
|
/**
|
|
* Count how many memories currently have needs_reindex = 1.
|
|
*/
|
|
export function countMemoryReindexPending(): number {
|
|
const db = getDbInstance();
|
|
const row = db
|
|
.prepare("SELECT COUNT(*) AS cnt FROM memories WHERE needs_reindex = 1")
|
|
.get() as { cnt: number };
|
|
return row.cnt;
|
|
}
|