nlm-memory 0.5.18 → 0.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -17
- package/dist/cli/nlm.js +147 -80
- package/dist/cli/nlm.js.map +1 -1
- package/dist/cli/supersede.js +5 -2
- package/dist/cli/supersede.js.map +1 -1
- package/dist/core/actions/actions-log.d.ts +9 -0
- package/dist/core/actions/actions-log.js +86 -0
- package/dist/core/actions/actions-log.js.map +1 -1
- package/dist/core/digest/compose.d.ts +0 -1
- package/dist/core/digest/compose.js +0 -4
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/providers/provider-registry.d.ts +15 -0
- package/dist/core/providers/provider-registry.js +85 -0
- package/dist/core/providers/provider-registry.js.map +1 -1
- package/dist/core/recall/query-log.d.ts +1 -2
- package/dist/core/recall/query-log.js +1 -5
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/scheduler/scan-once.d.ts +4 -0
- package/dist/core/scheduler/scan-once.js +64 -0
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.js +25 -9
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/sources/source-registry.d.ts +16 -0
- package/dist/core/sources/source-registry.js +123 -0
- package/dist/core/sources/source-registry.js.map +1 -1
- package/dist/core/storage/pg-fact-store.d.ts +24 -0
- package/dist/core/storage/pg-fact-store.js +233 -0
- package/dist/core/storage/pg-fact-store.js.map +1 -0
- package/dist/core/storage/pg-session-store.d.ts +29 -0
- package/dist/core/storage/pg-session-store.js +315 -0
- package/dist/core/storage/pg-session-store.js.map +1 -0
- package/dist/core/storage/pg-storage.d.ts +36 -0
- package/dist/core/storage/pg-storage.js +87 -0
- package/dist/core/storage/pg-storage.js.map +1 -0
- package/dist/core/storage/pg-tx-context.d.ts +44 -0
- package/dist/core/storage/pg-tx-context.js +133 -0
- package/dist/core/storage/pg-tx-context.js.map +1 -0
- package/dist/core/storage/sqlite-fact-store.d.ts +13 -7
- package/dist/core/storage/sqlite-fact-store.js +38 -12
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +6 -16
- package/dist/core/storage/sqlite-session-store.js +51 -42
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +38 -0
- package/dist/core/storage/sqlite-storage.js +66 -0
- package/dist/core/storage/sqlite-storage.js.map +1 -0
- package/dist/hook/pi-extension.d.ts +43 -0
- package/dist/hook/pi-extension.js +50 -0
- package/dist/hook/pi-extension.js.map +1 -0
- package/dist/hook/prompt-recall-hook.js +1 -36
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +10 -0
- package/dist/hook/recall-over-http.js +41 -0
- package/dist/hook/recall-over-http.js.map +1 -0
- package/dist/hook/stop-hook.d.ts +1 -3
- package/dist/hook/stop-hook.js +1 -3
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +6 -5
- package/dist/http/app.js +15 -4
- package/dist/http/app.js.map +1 -1
- package/dist/install/pi.d.ts +45 -0
- package/dist/install/pi.js +107 -0
- package/dist/install/pi.js.map +1 -0
- package/dist/install/setup.js +26 -10
- package/dist/install/setup.js.map +1 -1
- package/dist/ports/fact-store.d.ts +26 -0
- package/dist/ports/storage.d.ts +45 -0
- package/dist/ports/storage.js +14 -0
- package/dist/ports/storage.js.map +1 -0
- package/nlm/README.md +73 -0
- package/nlm/index.js +280 -0
- package/nlm/package.json +8 -0
- package/package.json +9 -2
- package/plugin/scripts/prompt-recall-hook.mjs +31 -29
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PgStorage — canonical Storage adapter for PostgreSQL + pgvector.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Storage port (init/close/withTransaction). withTransaction
|
|
5
|
+
* uses the write-queue pattern: the sync callback queues SQL ops, then
|
|
6
|
+
* PgStorage flushes the queue inside a single BEGIN/COMMIT after the
|
|
7
|
+
* callback returns.
|
|
8
|
+
*
|
|
9
|
+
* pgPool() is a deprecated escape hatch for callers not yet ported to the
|
|
10
|
+
* Storage interface. Tracked for removal in #215a (PG branch).
|
|
11
|
+
*/
|
|
12
|
+
import { Pool } from "pg";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { PgFactStore } from "./pg-fact-store.js";
|
|
16
|
+
import { PgSessionStore } from "./pg-session-store.js";
|
|
17
|
+
import { PgTxBoundFactStore, PgTxBoundSessionStore } from "./pg-tx-context.js";
|
|
18
|
+
export class PgStorage {
|
|
19
|
+
facts;
|
|
20
|
+
sessions;
|
|
21
|
+
_pool;
|
|
22
|
+
_migrationsDir;
|
|
23
|
+
// Guards against re-entrant (synchronous) nesting only. Not concurrent-call-safe.
|
|
24
|
+
inTxn = false;
|
|
25
|
+
constructor(pool, migrationsDir) {
|
|
26
|
+
this._pool = pool;
|
|
27
|
+
this._migrationsDir = migrationsDir;
|
|
28
|
+
this.facts = new PgFactStore(pool);
|
|
29
|
+
this.sessions = new PgSessionStore(pool);
|
|
30
|
+
}
|
|
31
|
+
static create(opts) {
|
|
32
|
+
const pool = new Pool({ connectionString: opts.connectionString });
|
|
33
|
+
return new PgStorage(pool, opts.migrationsDir);
|
|
34
|
+
}
|
|
35
|
+
async init() {
|
|
36
|
+
const sql = readFileSync(join(this._migrationsDir, "001_initial.sql"), "utf8");
|
|
37
|
+
await this._pool.query(sql);
|
|
38
|
+
}
|
|
39
|
+
async close() {
|
|
40
|
+
await this._pool.end();
|
|
41
|
+
}
|
|
42
|
+
async withTransaction(fn) {
|
|
43
|
+
if (this.inTxn) {
|
|
44
|
+
throw new Error("PgStorage.withTransaction does not support nesting");
|
|
45
|
+
}
|
|
46
|
+
this.inTxn = true;
|
|
47
|
+
const queue = [];
|
|
48
|
+
const txFacts = new PgTxBoundFactStore(queue);
|
|
49
|
+
const txSessions = new PgTxBoundSessionStore(queue);
|
|
50
|
+
const ctx = { facts: txFacts, sessions: txSessions };
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = fn(ctx);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
this.inTxn = false;
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
this.inTxn = false;
|
|
60
|
+
if (queue.length > 0) {
|
|
61
|
+
const client = await this._pool.connect();
|
|
62
|
+
try {
|
|
63
|
+
await client.query("BEGIN");
|
|
64
|
+
for (const op of queue) {
|
|
65
|
+
await client.query(op.sql, op.params);
|
|
66
|
+
}
|
|
67
|
+
await client.query("COMMIT");
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
await client.query("ROLLBACK");
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
client.release();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* @deprecated Escape hatch for callers not yet ported to the Storage
|
|
81
|
+
* interface. Tracked for removal in #215a (PG branch).
|
|
82
|
+
*/
|
|
83
|
+
pgPool() {
|
|
84
|
+
return this._pool;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=pg-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-storage.js","sourceRoot":"","sources":["../../../src/core/storage/pg-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PgTxContext — write-queue for PgStorage.withTransaction.
|
|
3
|
+
*
|
|
4
|
+
* PgStorage.withTransaction requires a sync callback (port contract). PG
|
|
5
|
+
* transactions are async. Bridge: sync callback queues SQL ops; PgStorage
|
|
6
|
+
* flushes the queue in one BEGIN/COMMIT after the callback returns.
|
|
7
|
+
*
|
|
8
|
+
* Read methods throw — they cannot see uncommitted queue state. The
|
|
9
|
+
* FactStore/SessionStore contracts never read inside withTransaction.
|
|
10
|
+
*/
|
|
11
|
+
import type { FactListFilter, FactQuery, FactSemanticNeighbor, FactStore } from "../../ports/fact-store.js";
|
|
12
|
+
import type { KeywordNeighbor, SemanticNeighbor, SessionFilter, SessionStore } from "../../ports/session-store.js";
|
|
13
|
+
import type { Fact, FactHistoryChain, Session, SessionStatus } from "../../shared/types.js";
|
|
14
|
+
export interface QueuedOp {
|
|
15
|
+
readonly sql: string;
|
|
16
|
+
readonly params: readonly unknown[];
|
|
17
|
+
}
|
|
18
|
+
export declare class PgTxBoundFactStore implements FactStore {
|
|
19
|
+
private readonly q;
|
|
20
|
+
constructor(q: QueuedOp[]);
|
|
21
|
+
insert(fact: Fact): Promise<void>;
|
|
22
|
+
insertMany(facts: ReadonlyArray<Fact>): Promise<void>;
|
|
23
|
+
markSuperseded(oldId: string, newId: string | null): Promise<void>;
|
|
24
|
+
ingestSessionFacts(sessionId: string, facts: ReadonlyArray<Fact>): Promise<void>;
|
|
25
|
+
upsertEmbedding(factId: string, vector: Float32Array): Promise<void>;
|
|
26
|
+
getById(_id: string): Promise<Fact | null>;
|
|
27
|
+
findCurrent(_s: string, _p: string): Promise<Fact | null>;
|
|
28
|
+
list(_q: FactQuery): Promise<ReadonlyArray<Fact>>;
|
|
29
|
+
listBySession(_id: string): Promise<ReadonlyArray<Fact>>;
|
|
30
|
+
listForRecall(_f: FactListFilter): Promise<ReadonlyArray<Fact>>;
|
|
31
|
+
semanticSearch(_v: Float32Array, _n: number): Promise<ReadonlyArray<FactSemanticNeighbor>>;
|
|
32
|
+
getHistory(_s: string, _p?: string): Promise<ReadonlyArray<FactHistoryChain>>;
|
|
33
|
+
}
|
|
34
|
+
export declare class PgTxBoundSessionStore implements SessionStore {
|
|
35
|
+
private readonly q;
|
|
36
|
+
constructor(q: QueuedOp[]);
|
|
37
|
+
updateStatus(sessionId: string, status: SessionStatus): Promise<void>;
|
|
38
|
+
markSuperseded(predecessorId: string, successorId: string): Promise<void>;
|
|
39
|
+
list(_filter?: SessionFilter): Promise<ReadonlyArray<Session>>;
|
|
40
|
+
getById(_id: string): Promise<Session | null>;
|
|
41
|
+
getByIds(_ids: ReadonlyArray<string>): Promise<ReadonlyArray<Session>>;
|
|
42
|
+
semanticSearch(_v: Float32Array, _n: number): Promise<ReadonlyArray<SemanticNeighbor>>;
|
|
43
|
+
keywordSearch(_q: string, _n: number): Promise<ReadonlyArray<KeywordNeighbor>>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PgTxContext — write-queue for PgStorage.withTransaction.
|
|
3
|
+
*
|
|
4
|
+
* PgStorage.withTransaction requires a sync callback (port contract). PG
|
|
5
|
+
* transactions are async. Bridge: sync callback queues SQL ops; PgStorage
|
|
6
|
+
* flushes the queue in one BEGIN/COMMIT after the callback returns.
|
|
7
|
+
*
|
|
8
|
+
* Read methods throw — they cannot see uncommitted queue state. The
|
|
9
|
+
* FactStore/SessionStore contracts never read inside withTransaction.
|
|
10
|
+
*/
|
|
11
|
+
function noRead(method) {
|
|
12
|
+
throw new Error(`PgStorage.withTransaction: ${method} read not supported inside sync callback`);
|
|
13
|
+
}
|
|
14
|
+
export class PgTxBoundFactStore {
|
|
15
|
+
q;
|
|
16
|
+
constructor(q) {
|
|
17
|
+
this.q = q;
|
|
18
|
+
}
|
|
19
|
+
insert(fact) {
|
|
20
|
+
this.q.push(insertFactOp(fact));
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
insertMany(facts) {
|
|
24
|
+
for (const f of facts)
|
|
25
|
+
this.q.push(insertFactOp(f));
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
markSuperseded(oldId, newId) {
|
|
29
|
+
this.q.push({
|
|
30
|
+
sql: "UPDATE facts SET superseded_by = $1 WHERE id = $2",
|
|
31
|
+
params: [newId, oldId],
|
|
32
|
+
});
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
ingestSessionFacts(sessionId, facts) {
|
|
36
|
+
this.q.push({
|
|
37
|
+
sql: "DELETE FROM facts WHERE source_session_id = $1",
|
|
38
|
+
params: [sessionId],
|
|
39
|
+
});
|
|
40
|
+
for (const f of facts)
|
|
41
|
+
this.q.push(insertFactOp(f));
|
|
42
|
+
if (facts.length > 0) {
|
|
43
|
+
const values = facts
|
|
44
|
+
.map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`)
|
|
45
|
+
.join(", ");
|
|
46
|
+
const params = [];
|
|
47
|
+
for (const f of facts)
|
|
48
|
+
params.push(f.subject, f.predicate, f.id);
|
|
49
|
+
this.q.push({
|
|
50
|
+
sql: `
|
|
51
|
+
UPDATE facts AS old
|
|
52
|
+
SET superseded_by = new_f.new_id
|
|
53
|
+
FROM (VALUES ${values}) AS new_f(subject, predicate, new_id)
|
|
54
|
+
WHERE old.subject = new_f.subject
|
|
55
|
+
AND old.predicate = new_f.predicate
|
|
56
|
+
AND old.superseded_by IS NULL
|
|
57
|
+
AND old.id != new_f.new_id
|
|
58
|
+
`,
|
|
59
|
+
params,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return Promise.resolve();
|
|
63
|
+
}
|
|
64
|
+
upsertEmbedding(factId, vector) {
|
|
65
|
+
this.q.push({
|
|
66
|
+
sql: `
|
|
67
|
+
INSERT INTO fact_embeddings (fact_id, embedding)
|
|
68
|
+
VALUES ($1, $2)
|
|
69
|
+
ON CONFLICT (fact_id) DO UPDATE SET embedding = EXCLUDED.embedding
|
|
70
|
+
`,
|
|
71
|
+
params: [factId, `[${Array.from(vector).join(",")}]`],
|
|
72
|
+
});
|
|
73
|
+
return Promise.resolve();
|
|
74
|
+
}
|
|
75
|
+
getById(_id) { return noRead("FactStore.getById"); }
|
|
76
|
+
findCurrent(_s, _p) { return noRead("FactStore.findCurrent"); }
|
|
77
|
+
list(_q) { return noRead("FactStore.list"); }
|
|
78
|
+
listBySession(_id) { return noRead("FactStore.listBySession"); }
|
|
79
|
+
listForRecall(_f) { return noRead("FactStore.listForRecall"); }
|
|
80
|
+
semanticSearch(_v, _n) { return noRead("FactStore.semanticSearch"); }
|
|
81
|
+
getHistory(_s, _p) { return noRead("FactStore.getHistory"); }
|
|
82
|
+
}
|
|
83
|
+
export class PgTxBoundSessionStore {
|
|
84
|
+
q;
|
|
85
|
+
constructor(q) {
|
|
86
|
+
this.q = q;
|
|
87
|
+
}
|
|
88
|
+
updateStatus(sessionId, status) {
|
|
89
|
+
if (status === "idle") {
|
|
90
|
+
throw new Error("Cannot persist derived status 'idle'");
|
|
91
|
+
}
|
|
92
|
+
this.q.push({
|
|
93
|
+
sql: "UPDATE sessions SET status = $1, updated_at = NOW() WHERE id = $2",
|
|
94
|
+
params: [status, sessionId],
|
|
95
|
+
});
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
}
|
|
98
|
+
markSuperseded(predecessorId, successorId) {
|
|
99
|
+
this.q.push({
|
|
100
|
+
sql: `
|
|
101
|
+
INSERT INTO session_edges (from_session, to_session, kind)
|
|
102
|
+
VALUES ($1, $2, 'supersedes')
|
|
103
|
+
ON CONFLICT DO NOTHING
|
|
104
|
+
`,
|
|
105
|
+
params: [successorId, predecessorId],
|
|
106
|
+
});
|
|
107
|
+
this.q.push({
|
|
108
|
+
sql: "UPDATE sessions SET status = 'superseded', updated_at = NOW() WHERE id = $1",
|
|
109
|
+
params: [predecessorId],
|
|
110
|
+
});
|
|
111
|
+
return Promise.resolve();
|
|
112
|
+
}
|
|
113
|
+
list(_filter) { return noRead("SessionStore.list"); }
|
|
114
|
+
getById(_id) { return noRead("SessionStore.getById"); }
|
|
115
|
+
getByIds(_ids) { return noRead("SessionStore.getByIds"); }
|
|
116
|
+
semanticSearch(_v, _n) { return noRead("SessionStore.semanticSearch"); }
|
|
117
|
+
keywordSearch(_q, _n) { return noRead("SessionStore.keywordSearch"); }
|
|
118
|
+
}
|
|
119
|
+
function insertFactOp(f) {
|
|
120
|
+
return {
|
|
121
|
+
sql: `
|
|
122
|
+
INSERT INTO facts (
|
|
123
|
+
id, kind, subject, predicate, value, source_session_id,
|
|
124
|
+
source_quote, created_at, superseded_by, confidence
|
|
125
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
126
|
+
`,
|
|
127
|
+
params: [
|
|
128
|
+
f.id, f.kind, f.subject, f.predicate, f.value,
|
|
129
|
+
f.sourceSessionId, f.sourceQuote, f.createdAt, f.supersededBy, f.confidence,
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=pg-tx-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-tx-context.js","sourceRoot":"","sources":["../../../src/core/storage/pg-tx-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Surface evolution:
|
|
12
12
|
* B.1 — insert, getById, findCurrent, list, listBySession, markSuperseded
|
|
13
|
-
* B.2
|
|
13
|
+
* B.2: ingestSessionFacts (atomic session+facts ingest), embedding write helper
|
|
14
14
|
* B.3 — listForRecall (pre-filter for FactRecallService), semanticSearch,
|
|
15
15
|
* getHistory (supersedence chain inspection)
|
|
16
16
|
* B.4 — auto-supersedence on (subject, predicate) collision (deferred)
|
|
@@ -20,16 +20,21 @@ import type { FactListFilter, FactQuery, FactSemanticNeighbor, FactStore } from
|
|
|
20
20
|
import type { Fact, FactHistoryChain } from "../../shared/types.js";
|
|
21
21
|
export declare class SqliteFactStore implements FactStore {
|
|
22
22
|
private readonly db;
|
|
23
|
+
/**
|
|
24
|
+
* @internal. Construct via SqliteStorage.create(...) instead. Direct
|
|
25
|
+
* construction is preserved for the SqliteStorage adapter only; all
|
|
26
|
+
* other callers should reach FactStore via storage.facts.
|
|
27
|
+
*/
|
|
23
28
|
constructor(db: Database.Database);
|
|
24
29
|
insert(fact: Fact): Promise<void>;
|
|
25
30
|
insertMany(facts: ReadonlyArray<Fact>): Promise<void>;
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
32
|
+
* @internal. Sync row insert for use inside an already-open better-sqlite3
|
|
33
|
+
* transaction. Used only by SqliteSessionStore's inlined ingest blocks
|
|
34
|
+
* (which require sync execution inside the txn callback). External callers
|
|
35
|
+
* use insertMany() or ingestSessionFacts() via Storage.withTransaction.
|
|
31
36
|
*/
|
|
32
|
-
|
|
37
|
+
insertRowInTxn(fact: Fact): void;
|
|
33
38
|
getById(id: string): Promise<Fact | null>;
|
|
34
39
|
findCurrent(subject: string, predicate: string): Promise<Fact | null>;
|
|
35
40
|
list(query: FactQuery): Promise<ReadonlyArray<Fact>>;
|
|
@@ -42,8 +47,9 @@ export declare class SqliteFactStore implements FactStore {
|
|
|
42
47
|
* trap embedder errors so an unreachable Ollama doesn't roll back ingest.
|
|
43
48
|
* vec0 doesn't UPDATE, so this is a DELETE+INSERT pair.
|
|
44
49
|
*/
|
|
45
|
-
upsertEmbedding(factId: string, vector: Float32Array): void
|
|
50
|
+
upsertEmbedding(factId: string, vector: Float32Array): Promise<void>;
|
|
46
51
|
markSuperseded(oldId: string, newId: string | null): Promise<void>;
|
|
52
|
+
ingestSessionFacts(sessionId: string, facts: ReadonlyArray<Fact>): Promise<void>;
|
|
47
53
|
private insertStmt;
|
|
48
54
|
private toRow;
|
|
49
55
|
private rowToFact;
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Surface evolution:
|
|
12
12
|
* B.1 — insert, getById, findCurrent, list, listBySession, markSuperseded
|
|
13
|
-
* B.2
|
|
13
|
+
* B.2: ingestSessionFacts (atomic session+facts ingest), embedding write helper
|
|
14
14
|
* B.3 — listForRecall (pre-filter for FactRecallService), semanticSearch,
|
|
15
15
|
* getHistory (supersedence chain inspection)
|
|
16
16
|
* B.4 — auto-supersedence on (subject, predicate) collision (deferred)
|
|
17
17
|
*/
|
|
18
18
|
export class SqliteFactStore {
|
|
19
19
|
db;
|
|
20
|
+
/**
|
|
21
|
+
* @internal. Construct via SqliteStorage.create(...) instead. Direct
|
|
22
|
+
* construction is preserved for the SqliteStorage adapter only; all
|
|
23
|
+
* other callers should reach FactStore via storage.facts.
|
|
24
|
+
*/
|
|
20
25
|
constructor(db) {
|
|
21
26
|
this.db = db;
|
|
22
27
|
}
|
|
@@ -34,17 +39,13 @@ export class SqliteFactStore {
|
|
|
34
39
|
txn(facts.map((f) => this.toRow(f)));
|
|
35
40
|
}
|
|
36
41
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
42
|
+
* @internal. Sync row insert for use inside an already-open better-sqlite3
|
|
43
|
+
* transaction. Used only by SqliteSessionStore's inlined ingest blocks
|
|
44
|
+
* (which require sync execution inside the txn callback). External callers
|
|
45
|
+
* use insertMany() or ingestSessionFacts() via Storage.withTransaction.
|
|
41
46
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
const stmt = this.insertStmt();
|
|
46
|
-
for (const f of facts)
|
|
47
|
-
stmt.run(this.toRow(f));
|
|
47
|
+
insertRowInTxn(fact) {
|
|
48
|
+
this.insertStmt().run(this.toRow(fact));
|
|
48
49
|
}
|
|
49
50
|
async getById(id) {
|
|
50
51
|
const row = this.db
|
|
@@ -183,7 +184,7 @@ export class SqliteFactStore {
|
|
|
183
184
|
* trap embedder errors so an unreachable Ollama doesn't roll back ingest.
|
|
184
185
|
* vec0 doesn't UPDATE, so this is a DELETE+INSERT pair.
|
|
185
186
|
*/
|
|
186
|
-
upsertEmbedding(factId, vector) {
|
|
187
|
+
async upsertEmbedding(factId, vector) {
|
|
187
188
|
const blob = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
188
189
|
this.db.prepare("DELETE FROM fact_embeddings WHERE fact_id = ?").run(factId);
|
|
189
190
|
this.db
|
|
@@ -213,6 +214,31 @@ export class SqliteFactStore {
|
|
|
213
214
|
});
|
|
214
215
|
txn();
|
|
215
216
|
}
|
|
217
|
+
async ingestSessionFacts(sessionId, facts) {
|
|
218
|
+
this.db.prepare("DELETE FROM facts WHERE source_session_id = ?").run(sessionId);
|
|
219
|
+
if (facts.length === 0)
|
|
220
|
+
return;
|
|
221
|
+
const insertStmt = this.insertStmt();
|
|
222
|
+
for (const f of facts)
|
|
223
|
+
insertStmt.run(this.toRow(f));
|
|
224
|
+
const findCollisionStmt = this.db.prepare(`
|
|
225
|
+
SELECT id
|
|
226
|
+
FROM facts
|
|
227
|
+
WHERE subject = ?
|
|
228
|
+
AND predicate = ?
|
|
229
|
+
AND superseded_by IS NULL
|
|
230
|
+
AND id != ?
|
|
231
|
+
ORDER BY created_at DESC
|
|
232
|
+
LIMIT 1
|
|
233
|
+
`);
|
|
234
|
+
const markSupersededStmt = this.db.prepare("UPDATE facts SET superseded_by = ? WHERE id = ?");
|
|
235
|
+
for (const f of facts) {
|
|
236
|
+
const collision = findCollisionStmt.get(f.subject, f.predicate, f.id);
|
|
237
|
+
if (collision && collision.id !== f.id) {
|
|
238
|
+
markSupersededStmt.run(f.id, collision.id);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
216
242
|
insertStmt() {
|
|
217
243
|
return this.db.prepare(`
|
|
218
244
|
INSERT INTO facts (
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-fact-store.js","sourceRoot":"","sources":["../../../src/core/storage/sqlite-fact-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA0BH,MAAM,OAAO,eAAe;
|
|
1
|
+
{"version":3,"file":"sqlite-fact-store.js","sourceRoot":"","sources":["../../../src/core/storage/sqlite-fact-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -57,6 +57,11 @@ export interface RecentMarker {
|
|
|
57
57
|
}
|
|
58
58
|
export declare class SqliteSessionStore implements SessionStore {
|
|
59
59
|
private readonly db;
|
|
60
|
+
/**
|
|
61
|
+
* @internal. Construct via SqliteStorage.create(...) instead. Direct
|
|
62
|
+
* construction is preserved for the SqliteStorage adapter only; all
|
|
63
|
+
* other callers should reach SessionStore via storage.sessions.
|
|
64
|
+
*/
|
|
60
65
|
constructor(opts: SqliteSessionStoreOptions);
|
|
61
66
|
close(): void;
|
|
62
67
|
/**
|
|
@@ -102,22 +107,6 @@ export declare class SqliteSessionStore implements SessionStore {
|
|
|
102
107
|
* internal helpers directly so session+facts commit together.
|
|
103
108
|
*/
|
|
104
109
|
insertFactsForSession(sessionId: string, factStore: SqliteFactStore, facts: ReadonlyArray<Fact>, embedder?: import("../../ports/llm-client.js").LLMClient | null): Promise<void>;
|
|
105
|
-
/**
|
|
106
|
-
* Sync core of the fact-ingest block. Runs inside an EXISTING transaction
|
|
107
|
-
* — opens no txn of its own. Used by both `insertSession` (Phase B.2
|
|
108
|
-
* atomic ingest) and `insertFactsForSession` (Phase B.5 backfill).
|
|
109
|
-
*
|
|
110
|
-
* Behavior (mirrored across both callers):
|
|
111
|
-
* 1. DELETE prior facts attributed to this session (idempotent on
|
|
112
|
-
* backfill, drops stale rows on re-ingest).
|
|
113
|
-
* 2. Insert all new facts atomically.
|
|
114
|
-
* 3. For each, mark the prior current (subject, predicate) fact as
|
|
115
|
-
* superseded — Phase B.4 deterministic supersedence policy.
|
|
116
|
-
*
|
|
117
|
-
* Ordering: inserts before updates so the supersedence FK target exists.
|
|
118
|
-
* CASCADE-SET-NULL on `superseded_by` handles chain repair on re-ingest.
|
|
119
|
-
*/
|
|
120
|
-
private applyFactsInTxn;
|
|
121
110
|
/**
|
|
122
111
|
* Best-effort per-fact embedding. Writes `${subject} ${predicate} ${value}`
|
|
123
112
|
* embeddings to fact_embeddings via FactStore.upsertEmbedding. Per-fact
|
|
@@ -144,6 +133,7 @@ export declare class SqliteSessionStore implements SessionStore {
|
|
|
144
133
|
keywordSearch(query: string, limit: number): Promise<ReadonlyArray<KeywordNeighbor>>;
|
|
145
134
|
updateStatus(sessionId: string, status: SessionStatus): Promise<void>;
|
|
146
135
|
markSuperseded(predecessorId: string, successorId: string): Promise<void>;
|
|
136
|
+
/** @internal test-only helper; production callers use insertSession(). */
|
|
147
137
|
insertSessionForTest(session: Session): void;
|
|
148
138
|
insertEdgeForTest(fromSession: string, toSession: string, kind?: "supersedes" | "continues"): void;
|
|
149
139
|
insertEmbeddingForTest(sessionId: string, vector: Float32Array): void;
|
|
@@ -22,6 +22,11 @@ import { tokenize } from "../recall/tokenize.js";
|
|
|
22
22
|
import { chunkSessionText } from "../embedding/chunk-body.js";
|
|
23
23
|
export class SqliteSessionStore {
|
|
24
24
|
db;
|
|
25
|
+
/**
|
|
26
|
+
* @internal. Construct via SqliteStorage.create(...) instead. Direct
|
|
27
|
+
* construction is preserved for the SqliteStorage adapter only; all
|
|
28
|
+
* other callers should reach SessionStore via storage.sessions.
|
|
29
|
+
*/
|
|
25
30
|
constructor(opts) {
|
|
26
31
|
const dbPath = resolve(opts.dbPath);
|
|
27
32
|
const parent = dirname(dbPath);
|
|
@@ -158,7 +163,7 @@ export class SqliteSessionStore {
|
|
|
158
163
|
// carries new provenance (new source_session_id) and is informative
|
|
159
164
|
// history. See Section 2 of factstore-design.md.
|
|
160
165
|
//
|
|
161
|
-
// Ordering note:
|
|
166
|
+
// Ordering note: inserts FIRST so the new fact id exists in
|
|
162
167
|
// facts(id) before any UPDATE sets superseded_by = newId (the FK
|
|
163
168
|
// would reject otherwise). The DELETE above plus the CASCADE-SET-NULL
|
|
164
169
|
// on superseded_by means re-ingest naturally repairs chains: if an
|
|
@@ -166,7 +171,30 @@ export class SqliteSessionStore {
|
|
|
166
171
|
// session, deleting our prior fact unlinks the chain; the loop below
|
|
167
172
|
// re-establishes it with the freshly-inserted row.
|
|
168
173
|
if (factSink !== null) {
|
|
169
|
-
|
|
174
|
+
// Inlined ingest. See SqliteFactStore.ingestSessionFacts for the
|
|
175
|
+
// backend-agnostic version. SqliteSessionStore runs this synchronously
|
|
176
|
+
// inside the better-sqlite3 txn callback (which must be sync). The
|
|
177
|
+
// logic mirrors the port method exactly; if you change one, change
|
|
178
|
+
// the other. Tracked as a known duplication for the SQLite adapter.
|
|
179
|
+
const db = this.db;
|
|
180
|
+
db.prepare("DELETE FROM facts WHERE source_session_id = ?").run(record.id);
|
|
181
|
+
if (factSink.facts.length > 0) {
|
|
182
|
+
const factStoreImpl = factSink.factStore;
|
|
183
|
+
for (const f of factSink.facts)
|
|
184
|
+
factStoreImpl.insertRowInTxn(f);
|
|
185
|
+
const findCollisionStmt = db.prepare(`
|
|
186
|
+
SELECT id FROM facts
|
|
187
|
+
WHERE subject = ? AND predicate = ? AND superseded_by IS NULL AND id != ?
|
|
188
|
+
ORDER BY created_at DESC LIMIT 1
|
|
189
|
+
`);
|
|
190
|
+
const markSupersededStmt = db.prepare("UPDATE facts SET superseded_by = ? WHERE id = ?");
|
|
191
|
+
for (const f of factSink.facts) {
|
|
192
|
+
const collision = findCollisionStmt.get(f.subject, f.predicate, f.id);
|
|
193
|
+
if (collision && collision.id !== f.id) {
|
|
194
|
+
markSupersededStmt.run(f.id, collision.id);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
170
198
|
}
|
|
171
199
|
});
|
|
172
200
|
txn();
|
|
@@ -239,51 +267,31 @@ export class SqliteSessionStore {
|
|
|
239
267
|
async insertFactsForSession(sessionId, factStore, facts, embedder = null) {
|
|
240
268
|
const db = this.db;
|
|
241
269
|
const txn = db.transaction(() => {
|
|
242
|
-
|
|
270
|
+
// Inlined ingest. Same logic as SqliteFactStore.ingestSessionFacts.
|
|
271
|
+
// Sync because better-sqlite3 txn callbacks must be sync.
|
|
272
|
+
db.prepare("DELETE FROM facts WHERE source_session_id = ?").run(sessionId);
|
|
273
|
+
if (facts.length > 0) {
|
|
274
|
+
for (const f of facts)
|
|
275
|
+
factStore.insertRowInTxn(f);
|
|
276
|
+
const findCollisionStmt = db.prepare(`
|
|
277
|
+
SELECT id FROM facts
|
|
278
|
+
WHERE subject = ? AND predicate = ? AND superseded_by IS NULL AND id != ?
|
|
279
|
+
ORDER BY created_at DESC LIMIT 1
|
|
280
|
+
`);
|
|
281
|
+
const markSupersededStmt = db.prepare("UPDATE facts SET superseded_by = ? WHERE id = ?");
|
|
282
|
+
for (const f of facts) {
|
|
283
|
+
const collision = findCollisionStmt.get(f.subject, f.predicate, f.id);
|
|
284
|
+
if (collision && collision.id !== f.id) {
|
|
285
|
+
markSupersededStmt.run(f.id, collision.id);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
243
289
|
});
|
|
244
290
|
txn();
|
|
245
291
|
if (embedder) {
|
|
246
292
|
await this.embedFacts(factStore, facts, embedder);
|
|
247
293
|
}
|
|
248
294
|
}
|
|
249
|
-
/**
|
|
250
|
-
* Sync core of the fact-ingest block. Runs inside an EXISTING transaction
|
|
251
|
-
* — opens no txn of its own. Used by both `insertSession` (Phase B.2
|
|
252
|
-
* atomic ingest) and `insertFactsForSession` (Phase B.5 backfill).
|
|
253
|
-
*
|
|
254
|
-
* Behavior (mirrored across both callers):
|
|
255
|
-
* 1. DELETE prior facts attributed to this session (idempotent on
|
|
256
|
-
* backfill, drops stale rows on re-ingest).
|
|
257
|
-
* 2. Insert all new facts atomically.
|
|
258
|
-
* 3. For each, mark the prior current (subject, predicate) fact as
|
|
259
|
-
* superseded — Phase B.4 deterministic supersedence policy.
|
|
260
|
-
*
|
|
261
|
-
* Ordering: inserts before updates so the supersedence FK target exists.
|
|
262
|
-
* CASCADE-SET-NULL on `superseded_by` handles chain repair on re-ingest.
|
|
263
|
-
*/
|
|
264
|
-
applyFactsInTxn(sessionId, factStore, facts) {
|
|
265
|
-
const db = this.db;
|
|
266
|
-
db.prepare("DELETE FROM facts WHERE source_session_id = ?").run(sessionId);
|
|
267
|
-
factStore.insertManyInTxn(facts);
|
|
268
|
-
if (facts.length === 0)
|
|
269
|
-
return;
|
|
270
|
-
const findCollisionStmt = db.prepare(`
|
|
271
|
-
SELECT id
|
|
272
|
-
FROM facts
|
|
273
|
-
WHERE subject = ?
|
|
274
|
-
AND predicate = ?
|
|
275
|
-
AND superseded_by IS NULL
|
|
276
|
-
AND id != ?
|
|
277
|
-
ORDER BY created_at DESC
|
|
278
|
-
LIMIT 1
|
|
279
|
-
`);
|
|
280
|
-
const markSupersededStmt = db.prepare("UPDATE facts SET superseded_by = ? WHERE id = ?");
|
|
281
|
-
for (const fact of facts) {
|
|
282
|
-
const prior = findCollisionStmt.get(fact.subject, fact.predicate, fact.id);
|
|
283
|
-
if (prior)
|
|
284
|
-
markSupersededStmt.run(fact.id, prior.id);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
295
|
/**
|
|
288
296
|
* Best-effort per-fact embedding. Writes `${subject} ${predicate} ${value}`
|
|
289
297
|
* embeddings to fact_embeddings via FactStore.upsertEmbedding. Per-fact
|
|
@@ -296,7 +304,7 @@ export class SqliteSessionStore {
|
|
|
296
304
|
continue;
|
|
297
305
|
try {
|
|
298
306
|
const { vector } = await embedder.embed(factText, "document");
|
|
299
|
-
factStore.upsertEmbedding(fact.id, vector);
|
|
307
|
+
await factStore.upsertEmbedding(fact.id, vector);
|
|
300
308
|
}
|
|
301
309
|
catch {
|
|
302
310
|
// Per-fact embedding failure must not abort embedding of subsequent
|
|
@@ -482,6 +490,7 @@ export class SqliteSessionStore {
|
|
|
482
490
|
txn();
|
|
483
491
|
}
|
|
484
492
|
// ── insert helpers used by tests / future ingest path ─────────────────
|
|
493
|
+
/** @internal test-only helper; production callers use insertSession(). */
|
|
485
494
|
insertSessionForTest(session) {
|
|
486
495
|
const stmt = this.db.prepare(`
|
|
487
496
|
INSERT INTO sessions (
|