Files
OmniRoute/src/lib/db/memoryVec.ts
Hernan Javier Ardila Sanchez fd26e601a2 fix(dashboard): use lightweight ping endpoint for MaintenanceBanner (fixes #3040) (#3043)
Integrated into release/v3.8.8. Applied review fixes: moved the SELECT 1 into a pingDb() db helper (no raw SQL in route, Hard Rule #5) + the 503 catch no longer leaks err.message (Hard Rule #12). Thanks @herjarsa!
2026-06-01 14:30:17 -03:00

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;
}