File size: 4,487 Bytes
6111b2b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | /**
* 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;
}
|