@oh-my-pi/pi-mnemosyne 15.6.0
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 +107 -0
- package/dist/types/cli.d.ts +35 -0
- package/dist/types/config.d.ts +77 -0
- package/dist/types/core/aaak.d.ts +55 -0
- package/dist/types/core/annotations.d.ts +75 -0
- package/dist/types/core/banks.d.ts +33 -0
- package/dist/types/core/beam/consolidate.d.ts +32 -0
- package/dist/types/core/beam/helpers.d.ts +59 -0
- package/dist/types/core/beam/index.d.ts +59 -0
- package/dist/types/core/beam/recall.d.ts +32 -0
- package/dist/types/core/beam/schema.d.ts +2 -0
- package/dist/types/core/beam/store.d.ts +35 -0
- package/dist/types/core/beam/types.d.ts +233 -0
- package/dist/types/core/binary-vectors.d.ts +54 -0
- package/dist/types/core/chat-normalize.d.ts +13 -0
- package/dist/types/core/content-sanitizer.d.ts +18 -0
- package/dist/types/core/cost-log.d.ts +13 -0
- package/dist/types/core/embeddings.d.ts +35 -0
- package/dist/types/core/entities.d.ts +7 -0
- package/dist/types/core/episodic-graph.d.ts +89 -0
- package/dist/types/core/extraction/client.d.ts +31 -0
- package/dist/types/core/extraction/diagnostics.d.ts +51 -0
- package/dist/types/core/extraction/prompts.d.ts +2 -0
- package/dist/types/core/extraction.d.ts +6 -0
- package/dist/types/core/index.d.ts +4 -0
- package/dist/types/core/llm-backends.d.ts +21 -0
- package/dist/types/core/local-llm.d.ts +15 -0
- package/dist/types/core/memory.d.ts +160 -0
- package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
- package/dist/types/core/migrations/index.d.ts +1 -0
- package/dist/types/core/mmr.d.ts +8 -0
- package/dist/types/core/orchestrator.d.ts +20 -0
- package/dist/types/core/patterns.d.ts +61 -0
- package/dist/types/core/plugins.d.ts +109 -0
- package/dist/types/core/polyphonic-recall.d.ts +66 -0
- package/dist/types/core/query-cache.d.ts +47 -0
- package/dist/types/core/query-intent.d.ts +20 -0
- package/dist/types/core/recall-diagnostics.d.ts +48 -0
- package/dist/types/core/runtime-options.d.ts +61 -0
- package/dist/types/core/shmr.d.ts +56 -0
- package/dist/types/core/streaming.d.ts +136 -0
- package/dist/types/core/synonyms.d.ts +46 -0
- package/dist/types/core/temporal-parser.d.ts +16 -0
- package/dist/types/core/token-counter.d.ts +8 -0
- package/dist/types/core/triples.d.ts +63 -0
- package/dist/types/core/typed-memory.d.ts +39 -0
- package/dist/types/core/veracity-consolidation.d.ts +60 -0
- package/dist/types/core/weibull.d.ts +96 -0
- package/dist/types/db.d.ts +16 -0
- package/dist/types/diagnose.d.ts +24 -0
- package/dist/types/dr/index.d.ts +1 -0
- package/dist/types/dr/recovery.d.ts +68 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/mcp-server.d.ts +40 -0
- package/dist/types/mcp-tools.d.ts +484 -0
- package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
- package/dist/types/migrations/index.d.ts +1 -0
- package/dist/types/types.d.ts +145 -0
- package/dist/types/util/datetime.d.ts +8 -0
- package/dist/types/util/env.d.ts +10 -0
- package/dist/types/util/ids.d.ts +3 -0
- package/dist/types/util/lru.d.ts +12 -0
- package/dist/types/util/regex.d.ts +10 -0
- package/package.json +82 -0
- package/src/cli.ts +390 -0
- package/src/config.ts +326 -0
- package/src/core/aaak.ts +142 -0
- package/src/core/annotations.ts +457 -0
- package/src/core/banks.ts +133 -0
- package/src/core/beam/consolidate.ts +963 -0
- package/src/core/beam/helpers.ts +920 -0
- package/src/core/beam/index.ts +353 -0
- package/src/core/beam/recall.ts +1091 -0
- package/src/core/beam/schema.ts +423 -0
- package/src/core/beam/store.ts +818 -0
- package/src/core/beam/types.ts +268 -0
- package/src/core/binary-vectors.ts +336 -0
- package/src/core/chat-normalize.ts +160 -0
- package/src/core/content-sanitizer.ts +136 -0
- package/src/core/cost-log.ts +103 -0
- package/src/core/embeddings.ts +490 -0
- package/src/core/entities.ts +259 -0
- package/src/core/episodic-graph.ts +708 -0
- package/src/core/extraction/client.ts +162 -0
- package/src/core/extraction/diagnostics.ts +193 -0
- package/src/core/extraction/prompts.ts +31 -0
- package/src/core/extraction.ts +335 -0
- package/src/core/index.ts +30 -0
- package/src/core/llm-backends.ts +51 -0
- package/src/core/local-llm.ts +436 -0
- package/src/core/memory.ts +617 -0
- package/src/core/migrations/e6-triplestore-split.ts +211 -0
- package/src/core/migrations/index.ts +1 -0
- package/src/core/mmr.ts +71 -0
- package/src/core/orchestrator.ts +53 -0
- package/src/core/patterns.ts +484 -0
- package/src/core/plugins.ts +375 -0
- package/src/core/polyphonic-recall.ts +563 -0
- package/src/core/query-cache.ts +370 -0
- package/src/core/query-intent.ts +139 -0
- package/src/core/recall-diagnostics.ts +157 -0
- package/src/core/runtime-options.ts +108 -0
- package/src/core/shmr.ts +471 -0
- package/src/core/streaming.ts +419 -0
- package/src/core/synonyms.ts +197 -0
- package/src/core/temporal-parser.ts +363 -0
- package/src/core/token-counter.ts +30 -0
- package/src/core/triples.ts +452 -0
- package/src/core/typed-memory.ts +407 -0
- package/src/core/veracity-consolidation.ts +477 -0
- package/src/core/weibull.ts +124 -0
- package/src/db.ts +128 -0
- package/src/diagnose.ts +174 -0
- package/src/dr/index.ts +1 -0
- package/src/dr/recovery.ts +405 -0
- package/src/index.ts +32 -0
- package/src/mcp-server.ts +155 -0
- package/src/mcp-tools.ts +961 -0
- package/src/migrations/e6-triplestore-split.ts +1 -0
- package/src/migrations/index.ts +1 -0
- package/src/types.ts +157 -0
- package/src/util/datetime.ts +69 -0
- package/src/util/env.ts +65 -0
- package/src/util/ids.ts +19 -0
- package/src/util/lru.ts +48 -0
- package/src/util/regex.ts +165 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { Database, type SQLQueryBindings } from "bun:sqlite";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { closeQuietly, type DatabasePath, openDatabase } from "../db";
|
|
6
|
+
|
|
7
|
+
export interface TripleRow {
|
|
8
|
+
id: number;
|
|
9
|
+
subject: string;
|
|
10
|
+
predicate: string;
|
|
11
|
+
object: string;
|
|
12
|
+
valid_from: string;
|
|
13
|
+
valid_until: string | null;
|
|
14
|
+
source: string | null;
|
|
15
|
+
confidence: number | null;
|
|
16
|
+
created_at: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TripleWriteOptions {
|
|
20
|
+
readonly validFrom?: string | null;
|
|
21
|
+
readonly valid_from?: string | null;
|
|
22
|
+
readonly source?: string | null;
|
|
23
|
+
readonly confidence?: number | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TripleQueryOptions {
|
|
27
|
+
readonly subject?: string | null;
|
|
28
|
+
readonly predicate?: string | null;
|
|
29
|
+
readonly object?: string | null;
|
|
30
|
+
readonly asOf?: string | null;
|
|
31
|
+
readonly as_of?: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TripleImportStats {
|
|
35
|
+
inserted: number;
|
|
36
|
+
skipped: number;
|
|
37
|
+
overwritten: number;
|
|
38
|
+
imported_renumbered: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type TripleImportRow = Partial<Omit<TripleRow, "id">> & { readonly id?: number | null };
|
|
42
|
+
|
|
43
|
+
const TRIPLE_COLUMNS = "id, subject, predicate, object, valid_from, valid_until, source, confidence, created_at";
|
|
44
|
+
const CONTENT_FIELDS = [
|
|
45
|
+
"subject",
|
|
46
|
+
"predicate",
|
|
47
|
+
"object",
|
|
48
|
+
"valid_from",
|
|
49
|
+
"valid_until",
|
|
50
|
+
"source",
|
|
51
|
+
"confidence",
|
|
52
|
+
"created_at",
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
type ContentField = (typeof CONTENT_FIELDS)[number];
|
|
56
|
+
type ContentSnapshot = Record<ContentField, string | number | null | undefined>;
|
|
57
|
+
|
|
58
|
+
interface ImportBindingRow {
|
|
59
|
+
readonly subject: string | null;
|
|
60
|
+
readonly predicate: string | null;
|
|
61
|
+
readonly object: string | null;
|
|
62
|
+
readonly valid_from: string | null;
|
|
63
|
+
readonly valid_until: string | null;
|
|
64
|
+
readonly source: string;
|
|
65
|
+
readonly confidence: number;
|
|
66
|
+
readonly created_at: string | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type ProcessEnv = Record<string, string | undefined>;
|
|
70
|
+
type SerializableDatabase = Database & { serialize(): Uint8Array };
|
|
71
|
+
|
|
72
|
+
function homeDir(env: ProcessEnv = process.env): string {
|
|
73
|
+
return env.HOME && env.HOME.length > 0 ? env.HOME : homedir();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function legacyDataDir(env: ProcessEnv = process.env): string {
|
|
77
|
+
return join(homeDir(env), ".hermes", "mnemosyne", "data");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function defaultDataDir(env: ProcessEnv = process.env): string {
|
|
81
|
+
return env.MNEMOSYNE_DATA_DIR && env.MNEMOSYNE_DATA_DIR.length > 0 ? env.MNEMOSYNE_DATA_DIR : legacyDataDir(env);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function defaultTripleDbPath(env: ProcessEnv = process.env): string {
|
|
85
|
+
return join(defaultDataDir(env), "triples.db");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function legacyTripleDbPath(env: ProcessEnv = process.env): string {
|
|
89
|
+
return join(legacyDataDir(env), "triples.db");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function copyLegacyDb(source: string, destination: string): void {
|
|
93
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
94
|
+
const tempPath = join(
|
|
95
|
+
dirname(destination),
|
|
96
|
+
`.${destination.split(/[\\/]/).at(-1) ?? "triples.db"}.${process.pid}.tmp`,
|
|
97
|
+
);
|
|
98
|
+
let sourceDb: Database | null = null;
|
|
99
|
+
try {
|
|
100
|
+
sourceDb = openDatabase(source, { create: false, readwrite: false, pragmas: false });
|
|
101
|
+
writeFileSync(tempPath, (sourceDb as SerializableDatabase).serialize());
|
|
102
|
+
if (!existsSync(destination)) copyFileSync(tempPath, destination);
|
|
103
|
+
} finally {
|
|
104
|
+
closeQuietly(sourceDb);
|
|
105
|
+
try {
|
|
106
|
+
unlinkSync(tempPath);
|
|
107
|
+
} catch {
|
|
108
|
+
// Best-effort cleanup; a failed copy should surface as the original error.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function resolveDefaultTripleDb(env: ProcessEnv = process.env): string {
|
|
114
|
+
const destination = defaultTripleDbPath(env);
|
|
115
|
+
const legacy = legacyTripleDbPath(env);
|
|
116
|
+
if (destination !== legacy && !existsSync(destination) && existsSync(legacy)) copyLegacyDb(legacy, destination);
|
|
117
|
+
return destination;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function initTriples(dbOrPath?: Database | DatabasePath | null): void {
|
|
121
|
+
let db: Database;
|
|
122
|
+
let owned = false;
|
|
123
|
+
if (dbOrPath instanceof Database) {
|
|
124
|
+
db = dbOrPath;
|
|
125
|
+
} else {
|
|
126
|
+
db = openDatabase(dbOrPath ?? resolveDefaultTripleDb());
|
|
127
|
+
owned = true;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
db.run(`
|
|
131
|
+
CREATE TABLE IF NOT EXISTS triples (
|
|
132
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
133
|
+
subject TEXT NOT NULL,
|
|
134
|
+
predicate TEXT NOT NULL,
|
|
135
|
+
object TEXT NOT NULL,
|
|
136
|
+
valid_from TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
137
|
+
valid_until TEXT,
|
|
138
|
+
source TEXT,
|
|
139
|
+
confidence REAL DEFAULT 1.0,
|
|
140
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
141
|
+
)
|
|
142
|
+
`);
|
|
143
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_triples_subject ON triples(subject)");
|
|
144
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_triples_predicate ON triples(predicate)");
|
|
145
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_triples_object ON triples(object)");
|
|
146
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_triples_valid_from ON triples(valid_from)");
|
|
147
|
+
} finally {
|
|
148
|
+
if (owned) closeQuietly(db);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function today(): string {
|
|
153
|
+
return new Date().toISOString().slice(0, 10);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeOptions(options?: TripleWriteOptions | string | null): Required<TripleWriteOptions> {
|
|
157
|
+
if (typeof options === "string") {
|
|
158
|
+
return { validFrom: options, valid_from: options, source: "inferred", confidence: 1.0 };
|
|
159
|
+
}
|
|
160
|
+
const validFrom = options?.validFrom ?? options?.valid_from ?? null;
|
|
161
|
+
return {
|
|
162
|
+
validFrom,
|
|
163
|
+
valid_from: validFrom,
|
|
164
|
+
source: options?.source ?? "inferred",
|
|
165
|
+
confidence: options?.confidence ?? 1.0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function rowToTriple(row: unknown): TripleRow {
|
|
170
|
+
return row as TripleRow;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function normalizeContent(item: TripleImportRow): ContentSnapshot {
|
|
174
|
+
const bindings = normalizeImportBindings(item);
|
|
175
|
+
return {
|
|
176
|
+
subject: bindings.subject,
|
|
177
|
+
predicate: bindings.predicate,
|
|
178
|
+
object: bindings.object,
|
|
179
|
+
valid_from: bindings.valid_from,
|
|
180
|
+
valid_until: bindings.valid_until,
|
|
181
|
+
source: bindings.source,
|
|
182
|
+
confidence: bindings.confidence,
|
|
183
|
+
created_at: bindings.created_at,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function requiredImportText(value: string | null | undefined): string | null {
|
|
188
|
+
return value ?? null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function normalizeImportBindings(item: TripleImportRow): ImportBindingRow {
|
|
192
|
+
return {
|
|
193
|
+
subject: requiredImportText(item.subject),
|
|
194
|
+
predicate: requiredImportText(item.predicate),
|
|
195
|
+
object: requiredImportText(item.object),
|
|
196
|
+
valid_from: requiredImportText(item.valid_from),
|
|
197
|
+
valid_until: item.valid_until ?? null,
|
|
198
|
+
source: item.source ?? "imported",
|
|
199
|
+
confidence: item.confidence ?? 1.0,
|
|
200
|
+
created_at: item.created_at ?? null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function contentFromRow(row: TripleRow): ContentSnapshot {
|
|
205
|
+
return {
|
|
206
|
+
subject: row.subject,
|
|
207
|
+
predicate: row.predicate,
|
|
208
|
+
object: row.object,
|
|
209
|
+
valid_from: row.valid_from,
|
|
210
|
+
valid_until: row.valid_until,
|
|
211
|
+
source: row.source,
|
|
212
|
+
confidence: row.confidence,
|
|
213
|
+
created_at: row.created_at,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function sameContent(left: ContentSnapshot, right: ContentSnapshot): boolean {
|
|
218
|
+
for (const field of CONTENT_FIELDS) {
|
|
219
|
+
if ((left[field] ?? null) !== (right[field] ?? null)) return false;
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export class TripleStore {
|
|
225
|
+
readonly dbPath: DatabasePath;
|
|
226
|
+
readonly conn: Database;
|
|
227
|
+
#ownsConnection: boolean;
|
|
228
|
+
|
|
229
|
+
constructor(dbPath?: DatabasePath | Database | null) {
|
|
230
|
+
if (dbPath instanceof Database) {
|
|
231
|
+
this.dbPath = ":memory:";
|
|
232
|
+
this.conn = dbPath;
|
|
233
|
+
this.#ownsConnection = false;
|
|
234
|
+
initTriples(this.conn);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.dbPath = dbPath ?? resolveDefaultTripleDb();
|
|
238
|
+
this.conn = openDatabase(this.dbPath);
|
|
239
|
+
this.#ownsConnection = true;
|
|
240
|
+
initTriples(this.conn);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
close(): void {
|
|
244
|
+
if (!this.#ownsConnection) return;
|
|
245
|
+
this.#ownsConnection = false;
|
|
246
|
+
closeQuietly(this.conn);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
add(subject: string, predicate: string, object: string, options?: TripleWriteOptions | string | null): number {
|
|
250
|
+
const normalized = normalizeOptions(options);
|
|
251
|
+
const validFrom = normalized.validFrom ?? today();
|
|
252
|
+
this.conn.run("UPDATE triples SET valid_until = ? WHERE subject = ? AND predicate = ? AND valid_until IS NULL", [
|
|
253
|
+
validFrom,
|
|
254
|
+
subject,
|
|
255
|
+
predicate,
|
|
256
|
+
]);
|
|
257
|
+
const result = this.conn.run(
|
|
258
|
+
"INSERT INTO triples (subject, predicate, object, valid_from, source, confidence) VALUES (?, ?, ?, ?, ?, ?)",
|
|
259
|
+
[subject, predicate, object, validFrom, normalized.source, normalized.confidence],
|
|
260
|
+
);
|
|
261
|
+
return Number(result.lastInsertRowid);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
query(options?: TripleQueryOptions): TripleRow[];
|
|
265
|
+
query(subject?: string | null, predicate?: string | null, object?: string | null, asOf?: string | null): TripleRow[];
|
|
266
|
+
query(
|
|
267
|
+
optionsOrSubject?: TripleQueryOptions | string | null,
|
|
268
|
+
predicate?: string | null,
|
|
269
|
+
object?: string | null,
|
|
270
|
+
asOf?: string | null,
|
|
271
|
+
): TripleRow[] {
|
|
272
|
+
const options: TripleQueryOptions =
|
|
273
|
+
typeof optionsOrSubject === "object" && optionsOrSubject !== null
|
|
274
|
+
? optionsOrSubject
|
|
275
|
+
: { subject: optionsOrSubject, predicate, object, asOf };
|
|
276
|
+
const conditions: string[] = [];
|
|
277
|
+
const params: (string | number)[] = [];
|
|
278
|
+
if (options.subject) {
|
|
279
|
+
conditions.push("subject = ?");
|
|
280
|
+
params.push(options.subject);
|
|
281
|
+
}
|
|
282
|
+
if (options.predicate) {
|
|
283
|
+
conditions.push("predicate = ?");
|
|
284
|
+
params.push(options.predicate);
|
|
285
|
+
}
|
|
286
|
+
if (options.object) {
|
|
287
|
+
conditions.push("object = ?");
|
|
288
|
+
params.push(options.object);
|
|
289
|
+
}
|
|
290
|
+
const effectiveAsOf = options.asOf ?? options.as_of ?? today();
|
|
291
|
+
conditions.push("valid_from <= ?");
|
|
292
|
+
params.push(effectiveAsOf);
|
|
293
|
+
conditions.push("(valid_until IS NULL OR valid_until > ?)");
|
|
294
|
+
params.push(effectiveAsOf);
|
|
295
|
+
const where = conditions.join(" AND ");
|
|
296
|
+
return this.conn
|
|
297
|
+
.query(`SELECT ${TRIPLE_COLUMNS} FROM triples WHERE ${where} ORDER BY valid_from DESC`)
|
|
298
|
+
.all(...params)
|
|
299
|
+
.map(rowToTriple);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
queryByPredicate(predicate: string, object?: string | null, subject?: string | null): TripleRow[] {
|
|
303
|
+
const conditions = ["predicate = ?"];
|
|
304
|
+
const params: string[] = [predicate];
|
|
305
|
+
if (object) {
|
|
306
|
+
conditions.push("object = ?");
|
|
307
|
+
params.push(object);
|
|
308
|
+
}
|
|
309
|
+
if (subject) {
|
|
310
|
+
conditions.push("subject = ?");
|
|
311
|
+
params.push(subject);
|
|
312
|
+
}
|
|
313
|
+
return this.conn
|
|
314
|
+
.query(`SELECT ${TRIPLE_COLUMNS} FROM triples WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC`)
|
|
315
|
+
.all(...params)
|
|
316
|
+
.map(rowToTriple);
|
|
317
|
+
}
|
|
318
|
+
getDistinctObjects(predicate: string): string[] {
|
|
319
|
+
return this.conn
|
|
320
|
+
.query("SELECT DISTINCT object FROM triples WHERE predicate = ? ORDER BY object")
|
|
321
|
+
.all(predicate)
|
|
322
|
+
.map(row => (row as { object: string }).object);
|
|
323
|
+
}
|
|
324
|
+
exportAll(): TripleRow[] {
|
|
325
|
+
return this.conn.query(`SELECT ${TRIPLE_COLUMNS} FROM triples ORDER BY id`).all().map(rowToTriple);
|
|
326
|
+
}
|
|
327
|
+
importAll(triples: readonly TripleImportRow[], force = false): TripleImportStats {
|
|
328
|
+
const stats: TripleImportStats = {
|
|
329
|
+
inserted: 0,
|
|
330
|
+
skipped: 0,
|
|
331
|
+
overwritten: 0,
|
|
332
|
+
imported_renumbered: 0,
|
|
333
|
+
};
|
|
334
|
+
const seen = new Set<number>();
|
|
335
|
+
for (const item of triples) {
|
|
336
|
+
if (item.id === undefined || item.id === null) continue;
|
|
337
|
+
if (seen.has(item.id))
|
|
338
|
+
throw new Error(
|
|
339
|
+
`import_all: duplicate id ${item.id} in the imported batch. Deduplicate the input before calling.`,
|
|
340
|
+
);
|
|
341
|
+
seen.add(item.id);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.conn.run("BEGIN IMMEDIATE");
|
|
345
|
+
try {
|
|
346
|
+
const existing = new Map<number, ContentSnapshot>();
|
|
347
|
+
for (const row of this.conn.query(`SELECT ${TRIPLE_COLUMNS} FROM triples`).all().map(rowToTriple)) {
|
|
348
|
+
existing.set(row.id, contentFromRow(row));
|
|
349
|
+
}
|
|
350
|
+
const explicitNoCollision: TripleImportRow[] = [];
|
|
351
|
+
const noId: TripleImportRow[] = [];
|
|
352
|
+
const collisions: TripleImportRow[] = [];
|
|
353
|
+
for (const item of triples) {
|
|
354
|
+
const id = item.id;
|
|
355
|
+
if (id === undefined || id === null) noId.push(item);
|
|
356
|
+
else if (existing.has(id)) collisions.push(item);
|
|
357
|
+
else explicitNoCollision.push(item);
|
|
358
|
+
}
|
|
359
|
+
for (const item of explicitNoCollision) {
|
|
360
|
+
this.#insertWithId(item, item.id as number);
|
|
361
|
+
stats.inserted++;
|
|
362
|
+
}
|
|
363
|
+
for (const item of noId) {
|
|
364
|
+
this.#insertWithoutId(item);
|
|
365
|
+
stats.inserted++;
|
|
366
|
+
}
|
|
367
|
+
for (const item of collisions) {
|
|
368
|
+
const id = item.id as number;
|
|
369
|
+
if (force) {
|
|
370
|
+
this.conn.run("DELETE FROM triples WHERE id = ?", [id]);
|
|
371
|
+
this.#insertWithId(item, id);
|
|
372
|
+
stats.overwritten++;
|
|
373
|
+
} else if (sameContent(normalizeContent(item), existing.get(id) as ContentSnapshot)) {
|
|
374
|
+
stats.skipped++;
|
|
375
|
+
} else {
|
|
376
|
+
try {
|
|
377
|
+
this.#insertWithoutId(item);
|
|
378
|
+
stats.imported_renumbered++;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
if (!(error instanceof Error) || !error.message.toLowerCase().includes("constraint")) throw error;
|
|
381
|
+
stats.skipped++;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
this.conn.run("COMMIT");
|
|
386
|
+
return stats;
|
|
387
|
+
} catch (error) {
|
|
388
|
+
try {
|
|
389
|
+
this.conn.run("ROLLBACK");
|
|
390
|
+
} catch {
|
|
391
|
+
// Preserve the original error.
|
|
392
|
+
}
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
#insertWithId(item: TripleImportRow, id: number): void {
|
|
397
|
+
const bindings = normalizeImportBindings(item);
|
|
398
|
+
const params: SQLQueryBindings[] = [
|
|
399
|
+
id,
|
|
400
|
+
bindings.subject,
|
|
401
|
+
bindings.predicate,
|
|
402
|
+
bindings.object,
|
|
403
|
+
bindings.valid_from,
|
|
404
|
+
bindings.valid_until,
|
|
405
|
+
bindings.source,
|
|
406
|
+
bindings.confidence,
|
|
407
|
+
bindings.created_at,
|
|
408
|
+
];
|
|
409
|
+
this.conn.run(`INSERT INTO triples (${TRIPLE_COLUMNS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, params);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
#insertWithoutId(item: TripleImportRow): void {
|
|
413
|
+
const bindings = normalizeImportBindings(item);
|
|
414
|
+
const params: SQLQueryBindings[] = [
|
|
415
|
+
bindings.subject,
|
|
416
|
+
bindings.predicate,
|
|
417
|
+
bindings.object,
|
|
418
|
+
bindings.valid_from,
|
|
419
|
+
bindings.valid_until,
|
|
420
|
+
bindings.source,
|
|
421
|
+
bindings.confidence,
|
|
422
|
+
bindings.created_at,
|
|
423
|
+
];
|
|
424
|
+
this.conn.run(
|
|
425
|
+
"INSERT INTO triples (subject, predicate, object, valid_from, valid_until, source, confidence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
426
|
+
params,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function addTriple(
|
|
432
|
+
subject: string,
|
|
433
|
+
predicate: string,
|
|
434
|
+
object: string,
|
|
435
|
+
options?: TripleWriteOptions & { readonly dbPath?: DatabasePath | null },
|
|
436
|
+
): number {
|
|
437
|
+
const store = new TripleStore(options?.dbPath ?? null);
|
|
438
|
+
try {
|
|
439
|
+
return store.add(subject, predicate, object, options);
|
|
440
|
+
} finally {
|
|
441
|
+
store.close();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function queryTriples(options?: TripleQueryOptions & { readonly dbPath?: DatabasePath | null }): TripleRow[] {
|
|
446
|
+
const store = new TripleStore(options?.dbPath ?? null);
|
|
447
|
+
try {
|
|
448
|
+
return store.query(options);
|
|
449
|
+
} finally {
|
|
450
|
+
store.close();
|
|
451
|
+
}
|
|
452
|
+
}
|