wolbarg 0.4.0 → 0.5.1
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/CHANGELOG.md +20 -0
- package/README.md +226 -88
- package/dist/index.cjs +1199 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +282 -2
- package/dist/index.d.ts +282 -2
- package/dist/index.js +1192 -74
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs6 from "node:fs";
|
|
2
|
+
import path7 from "node:path";
|
|
2
3
|
import { createHash } from 'crypto';
|
|
3
4
|
import fs from 'fs/promises';
|
|
4
|
-
import fs5 from "node:fs";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
6
|
import * as sqliteVec from 'sqlite-vec';
|
|
7
7
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
@@ -85,6 +85,20 @@ var ProviderNotConfiguredError = class extends ConfigurationError {
|
|
|
85
85
|
this.provider = provider;
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
|
+
var GraphCheckpointNotSupportedError = class extends WolbargError {
|
|
89
|
+
constructor(backend, operation) {
|
|
90
|
+
super(
|
|
91
|
+
`graph checkpoint not supported for network-backed graph providers (${backend})`,
|
|
92
|
+
"GRAPH_CHECKPOINT_NOT_SUPPORTED",
|
|
93
|
+
{
|
|
94
|
+
operation,
|
|
95
|
+
reason: `${backend} is networked / not file-backed`,
|
|
96
|
+
suggestion: "Use sqliteGraph({ path }) for local snapshots, or checkpoint memory storage only without a network graph provider."
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
this.name = "GraphCheckpointNotSupportedError";
|
|
100
|
+
}
|
|
101
|
+
};
|
|
88
102
|
function wrapOperationError(operation, error) {
|
|
89
103
|
if (error instanceof WolbargError) {
|
|
90
104
|
return error;
|
|
@@ -385,9 +399,9 @@ var AsyncMutex = class {
|
|
|
385
399
|
}
|
|
386
400
|
}
|
|
387
401
|
};
|
|
388
|
-
function joinUrl(baseUrl,
|
|
402
|
+
function joinUrl(baseUrl, path8) {
|
|
389
403
|
const base = baseUrl.replace(/\/+$/, "");
|
|
390
|
-
const suffix =
|
|
404
|
+
const suffix = path8.startsWith("/") ? path8 : `/${path8}`;
|
|
391
405
|
return `${base}${suffix}`;
|
|
392
406
|
}
|
|
393
407
|
|
|
@@ -1082,7 +1096,7 @@ function extOf(filename) {
|
|
|
1082
1096
|
if (!filename) {
|
|
1083
1097
|
return "";
|
|
1084
1098
|
}
|
|
1085
|
-
return
|
|
1099
|
+
return path7.extname(filename).toLowerCase();
|
|
1086
1100
|
}
|
|
1087
1101
|
var TextParser = class {
|
|
1088
1102
|
name = "text";
|
|
@@ -1227,7 +1241,7 @@ async function loadIngestSource(source) {
|
|
|
1227
1241
|
const buffer = await fs.readFile(source.path);
|
|
1228
1242
|
return {
|
|
1229
1243
|
buffer,
|
|
1230
|
-
filename:
|
|
1244
|
+
filename: path7.basename(source.path),
|
|
1231
1245
|
mimeType: source.mimeType
|
|
1232
1246
|
};
|
|
1233
1247
|
}
|
|
@@ -1247,6 +1261,9 @@ function isStorageProvider(value) {
|
|
|
1247
1261
|
function isTelemetryProvider(value) {
|
|
1248
1262
|
return typeof value.emit === "function";
|
|
1249
1263
|
}
|
|
1264
|
+
function isGraphProvider(value) {
|
|
1265
|
+
return typeof value.open === "function" && typeof value.linkMemories === "function" && typeof value.getRelated === "function";
|
|
1266
|
+
}
|
|
1250
1267
|
function resolveDatabaseUrl(config) {
|
|
1251
1268
|
const url = "url" in config && config.url || "connectionString" in config && config.connectionString || "";
|
|
1252
1269
|
return typeof url === "string" ? url : "";
|
|
@@ -1317,11 +1334,11 @@ function jsonPath(field) {
|
|
|
1317
1334
|
return `$.${field}`;
|
|
1318
1335
|
}
|
|
1319
1336
|
function compileComparison(field, op) {
|
|
1320
|
-
const
|
|
1321
|
-
if (!
|
|
1337
|
+
const path8 = jsonPath(field);
|
|
1338
|
+
if (!path8) {
|
|
1322
1339
|
return null;
|
|
1323
1340
|
}
|
|
1324
|
-
const extract = `json_extract(metadata_json, '${
|
|
1341
|
+
const extract = `json_extract(metadata_json, '${path8}')`;
|
|
1325
1342
|
if ("eq" in op) {
|
|
1326
1343
|
const value = op.eq;
|
|
1327
1344
|
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
@@ -1466,6 +1483,40 @@ CREATE TABLE IF NOT EXISTS embedding_cache (
|
|
|
1466
1483
|
last_used_at TEXT NOT NULL
|
|
1467
1484
|
);
|
|
1468
1485
|
`;
|
|
1486
|
+
var CREATE_GRAPH_NODES_TABLE = `
|
|
1487
|
+
CREATE TABLE IF NOT EXISTS graph_nodes (
|
|
1488
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
1489
|
+
type TEXT NOT NULL CHECK (type IN ('memory', 'entity')),
|
|
1490
|
+
ref_id TEXT NOT NULL,
|
|
1491
|
+
name TEXT NULL,
|
|
1492
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1493
|
+
created_at TEXT NOT NULL
|
|
1494
|
+
);
|
|
1495
|
+
`;
|
|
1496
|
+
var CREATE_GRAPH_EDGES_TABLE = `
|
|
1497
|
+
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
1498
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
1499
|
+
from_node_id TEXT NOT NULL,
|
|
1500
|
+
to_node_id TEXT NOT NULL,
|
|
1501
|
+
relation TEXT NOT NULL,
|
|
1502
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1503
|
+
created_at TEXT NOT NULL,
|
|
1504
|
+
FOREIGN KEY (from_node_id) REFERENCES graph_nodes(id) ON DELETE CASCADE,
|
|
1505
|
+
FOREIGN KEY (to_node_id) REFERENCES graph_nodes(id) ON DELETE CASCADE
|
|
1506
|
+
);
|
|
1507
|
+
`;
|
|
1508
|
+
var CREATE_GRAPH_INDEXES = [
|
|
1509
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_nodes_type_ref
|
|
1510
|
+
ON graph_nodes(type, ref_id);`,
|
|
1511
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_from
|
|
1512
|
+
ON graph_edges(from_node_id);`,
|
|
1513
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_to
|
|
1514
|
+
ON graph_edges(to_node_id);`,
|
|
1515
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_from_rel
|
|
1516
|
+
ON graph_edges(from_node_id, relation);`,
|
|
1517
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_to_rel
|
|
1518
|
+
ON graph_edges(to_node_id, relation);`
|
|
1519
|
+
];
|
|
1469
1520
|
var CREATE_INDEXES = [
|
|
1470
1521
|
`CREATE INDEX IF NOT EXISTS idx_memories_org_agent ON memories(organization, agent);`,
|
|
1471
1522
|
`CREATE INDEX IF NOT EXISTS idx_memories_org_archived ON memories(organization, archived);`,
|
|
@@ -2066,7 +2117,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2066
2117
|
const dbPath = this.resolvePath(this.connectionString);
|
|
2067
2118
|
this.resolvedPath = dbPath;
|
|
2068
2119
|
if (dbPath !== ":memory:") {
|
|
2069
|
-
|
|
2120
|
+
fs6.mkdirSync(path7.dirname(dbPath), { recursive: true });
|
|
2070
2121
|
}
|
|
2071
2122
|
const db = new DatabaseSync(dbPath, { allowExtension: true });
|
|
2072
2123
|
this.db = db;
|
|
@@ -2554,11 +2605,11 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2554
2605
|
}
|
|
2555
2606
|
const dbPath = this.resolvedPath ?? this.resolvePath(this.connectionString);
|
|
2556
2607
|
try {
|
|
2557
|
-
let total =
|
|
2608
|
+
let total = fs6.statSync(dbPath).size;
|
|
2558
2609
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2559
2610
|
const side = `${dbPath}${suffix}`;
|
|
2560
|
-
if (
|
|
2561
|
-
total +=
|
|
2611
|
+
if (fs6.existsSync(side)) {
|
|
2612
|
+
total += fs6.statSync(side).size;
|
|
2562
2613
|
}
|
|
2563
2614
|
}
|
|
2564
2615
|
return total;
|
|
@@ -3334,7 +3385,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
3334
3385
|
if (connectionString === ":memory:") {
|
|
3335
3386
|
return ":memory:";
|
|
3336
3387
|
}
|
|
3337
|
-
return
|
|
3388
|
+
return path7.isAbsolute(connectionString) ? connectionString : path7.resolve(process.cwd(), connectionString);
|
|
3338
3389
|
}
|
|
3339
3390
|
requireDb() {
|
|
3340
3391
|
if (!this.db) {
|
|
@@ -3385,8 +3436,8 @@ function jsonbTextExtract(field) {
|
|
|
3385
3436
|
if (parts.length === 1) {
|
|
3386
3437
|
return `metadata_json->>'${parts[0]}'`;
|
|
3387
3438
|
}
|
|
3388
|
-
const
|
|
3389
|
-
return `metadata_json #>> '{${
|
|
3439
|
+
const path8 = parts.join(",");
|
|
3440
|
+
return `metadata_json #>> '{${path8}}'`;
|
|
3390
3441
|
}
|
|
3391
3442
|
function jsonbContainment(field, value, paramIndex) {
|
|
3392
3443
|
if (!FIELD_RE2.test(field)) {
|
|
@@ -5085,7 +5136,11 @@ function createStorageProvider(config, options) {
|
|
|
5085
5136
|
function createDatabaseProvider(config) {
|
|
5086
5137
|
return createStorageProvider(config);
|
|
5087
5138
|
}
|
|
5088
|
-
|
|
5139
|
+
|
|
5140
|
+
// src/version.ts
|
|
5141
|
+
var SDK_VERSION = "0.5.0";
|
|
5142
|
+
|
|
5143
|
+
// src/memory/transfer.ts
|
|
5089
5144
|
var SqliteMemoryTransferProvider = class {
|
|
5090
5145
|
async exportTo(exportPath, sourcePath, organization) {
|
|
5091
5146
|
const resolvedSource = resolvePath(sourcePath);
|
|
@@ -5094,7 +5149,7 @@ var SqliteMemoryTransferProvider = class {
|
|
|
5094
5149
|
"Cannot export an in-memory database. Use a file-backed SQLite database."
|
|
5095
5150
|
);
|
|
5096
5151
|
}
|
|
5097
|
-
if (!
|
|
5152
|
+
if (!fs6.existsSync(resolvedSource)) {
|
|
5098
5153
|
throw new DatabaseError(
|
|
5099
5154
|
`Failed to execute export()
|
|
5100
5155
|
Reason:
|
|
@@ -5104,7 +5159,7 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5104
5159
|
);
|
|
5105
5160
|
}
|
|
5106
5161
|
const resolvedExport = resolvePath(exportPath);
|
|
5107
|
-
|
|
5162
|
+
fs6.mkdirSync(path7.dirname(resolvedExport), { recursive: true });
|
|
5108
5163
|
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : `${resolvedExport}.db`;
|
|
5109
5164
|
const manifestPath = `${dbExportPath}.manifest.json`;
|
|
5110
5165
|
await copySqlite(resolvedSource, dbExportPath);
|
|
@@ -5116,17 +5171,17 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5116
5171
|
sourcePath: resolvedSource,
|
|
5117
5172
|
organization
|
|
5118
5173
|
};
|
|
5119
|
-
|
|
5174
|
+
fs6.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
5120
5175
|
return {
|
|
5121
5176
|
path: dbExportPath,
|
|
5122
5177
|
manifest,
|
|
5123
|
-
sizeBytes:
|
|
5178
|
+
sizeBytes: fs6.statSync(dbExportPath).size
|
|
5124
5179
|
};
|
|
5125
5180
|
}
|
|
5126
5181
|
async importFrom(exportPath, targetPath) {
|
|
5127
5182
|
const resolvedExport = resolvePath(exportPath);
|
|
5128
|
-
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport :
|
|
5129
|
-
if (!
|
|
5183
|
+
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
|
|
5184
|
+
if (!fs6.existsSync(dbExportPath)) {
|
|
5130
5185
|
throw new ValidationError(
|
|
5131
5186
|
`Failed to execute import()
|
|
5132
5187
|
Reason:
|
|
@@ -5137,8 +5192,8 @@ Pass the path returned by export().`
|
|
|
5137
5192
|
}
|
|
5138
5193
|
const manifestPath = `${dbExportPath}.manifest.json`;
|
|
5139
5194
|
let manifest;
|
|
5140
|
-
if (
|
|
5141
|
-
manifest = JSON.parse(
|
|
5195
|
+
if (fs6.existsSync(manifestPath)) {
|
|
5196
|
+
manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf8"));
|
|
5142
5197
|
if (manifest.format !== "wolbarg-export-v1") {
|
|
5143
5198
|
throw new ValidationError(
|
|
5144
5199
|
`Unsupported export format: ${String(manifest.format)}`
|
|
@@ -5159,8 +5214,8 @@ Pass the path returned by export().`
|
|
|
5159
5214
|
}
|
|
5160
5215
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
5161
5216
|
const side = `${resolvedTarget}${suffix}`;
|
|
5162
|
-
if (
|
|
5163
|
-
|
|
5217
|
+
if (fs6.existsSync(side)) {
|
|
5218
|
+
fs6.rmSync(side, { force: true });
|
|
5164
5219
|
}
|
|
5165
5220
|
}
|
|
5166
5221
|
await copySqlite(dbExportPath, resolvedTarget);
|
|
@@ -5168,7 +5223,7 @@ Pass the path returned by export().`
|
|
|
5168
5223
|
}
|
|
5169
5224
|
};
|
|
5170
5225
|
async function copySqlite(sourcePath, destPath) {
|
|
5171
|
-
|
|
5226
|
+
fs6.mkdirSync(path7.dirname(destPath), { recursive: true });
|
|
5172
5227
|
let source = null;
|
|
5173
5228
|
let dest = null;
|
|
5174
5229
|
try {
|
|
@@ -5186,7 +5241,7 @@ async function copySqlite(sourcePath, destPath) {
|
|
|
5186
5241
|
source = null;
|
|
5187
5242
|
dest.close();
|
|
5188
5243
|
dest = null;
|
|
5189
|
-
|
|
5244
|
+
fs6.copyFileSync(sourcePath, destPath);
|
|
5190
5245
|
}
|
|
5191
5246
|
} catch (error) {
|
|
5192
5247
|
throw new DatabaseError(
|
|
@@ -5206,7 +5261,7 @@ async function copySqlite(sourcePath, destPath) {
|
|
|
5206
5261
|
}
|
|
5207
5262
|
function resolvePath(p) {
|
|
5208
5263
|
if (p === ":memory:") return ":memory:";
|
|
5209
|
-
return
|
|
5264
|
+
return path7.isAbsolute(p) ? p : path7.resolve(process.cwd(), p);
|
|
5210
5265
|
}
|
|
5211
5266
|
|
|
5212
5267
|
// src/memory/index.ts
|
|
@@ -5333,6 +5388,723 @@ function adaptiveFetchK(topK, overFetchFactor, hasFilters) {
|
|
|
5333
5388
|
return Math.min(Math.max(Math.ceil(topK * factor), topK), 1e3);
|
|
5334
5389
|
}
|
|
5335
5390
|
|
|
5391
|
+
// src/graph/memory-node.ts
|
|
5392
|
+
function stubMemoryRecord(id) {
|
|
5393
|
+
return {
|
|
5394
|
+
id,
|
|
5395
|
+
organization: "",
|
|
5396
|
+
agent: "",
|
|
5397
|
+
content: { text: "" },
|
|
5398
|
+
metadata: {},
|
|
5399
|
+
archived: false,
|
|
5400
|
+
compressedInto: null,
|
|
5401
|
+
createdAt: /* @__PURE__ */ new Date(0),
|
|
5402
|
+
updatedAt: /* @__PURE__ */ new Date(0)
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
function serializeMetadata2(meta2) {
|
|
5406
|
+
return JSON.stringify(meta2 ?? {});
|
|
5407
|
+
}
|
|
5408
|
+
function deserializeMetadata2(raw) {
|
|
5409
|
+
if (raw == null || raw === "") return {};
|
|
5410
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
5411
|
+
return raw;
|
|
5412
|
+
}
|
|
5413
|
+
if (typeof raw !== "string") return {};
|
|
5414
|
+
try {
|
|
5415
|
+
const parsed = JSON.parse(raw);
|
|
5416
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
5417
|
+
return parsed;
|
|
5418
|
+
}
|
|
5419
|
+
} catch {
|
|
5420
|
+
}
|
|
5421
|
+
return {};
|
|
5422
|
+
}
|
|
5423
|
+
function rowToMemoryRecord(row) {
|
|
5424
|
+
const id = String(row.id ?? row.n_id ?? "");
|
|
5425
|
+
const createdAt = parseDate(row.created_at ?? row.createdAt);
|
|
5426
|
+
const updatedAt = parseDate(row.updated_at ?? row.updatedAt);
|
|
5427
|
+
return {
|
|
5428
|
+
id,
|
|
5429
|
+
organization: String(row.organization ?? ""),
|
|
5430
|
+
agent: String(row.agent ?? ""),
|
|
5431
|
+
content: { text: String(row.content_text ?? row.contentText ?? "") },
|
|
5432
|
+
metadata: deserializeMetadata2(row.metadata_json ?? row.metadata),
|
|
5433
|
+
archived: Boolean(row.archived),
|
|
5434
|
+
compressedInto: row.compressed_into == null || row.compressed_into === "" ? null : String(row.compressed_into),
|
|
5435
|
+
createdAt,
|
|
5436
|
+
updatedAt
|
|
5437
|
+
};
|
|
5438
|
+
}
|
|
5439
|
+
function parseDate(value) {
|
|
5440
|
+
if (value instanceof Date) return value;
|
|
5441
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
5442
|
+
const d = new Date(value);
|
|
5443
|
+
if (!Number.isNaN(d.getTime())) return d;
|
|
5444
|
+
}
|
|
5445
|
+
return /* @__PURE__ */ new Date(0);
|
|
5446
|
+
}
|
|
5447
|
+
function entityIdFrom(name, type) {
|
|
5448
|
+
const key = `${type.trim().toLowerCase()}::${name.trim().toLowerCase()}`;
|
|
5449
|
+
let h = 2166136261;
|
|
5450
|
+
for (let i = 0; i < key.length; i += 1) {
|
|
5451
|
+
h ^= key.charCodeAt(i);
|
|
5452
|
+
h = Math.imul(h, 16777619);
|
|
5453
|
+
}
|
|
5454
|
+
const a = (h >>> 0).toString(16).padStart(8, "0");
|
|
5455
|
+
let h2 = 5381;
|
|
5456
|
+
for (let i = 0; i < key.length; i += 1) {
|
|
5457
|
+
h2 = h2 * 33 ^ key.charCodeAt(i);
|
|
5458
|
+
}
|
|
5459
|
+
const b = (h2 >>> 0).toString(16).padStart(8, "0");
|
|
5460
|
+
return `ent_${a}${b}`;
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
// src/graph/sync/cascade.ts
|
|
5464
|
+
var ENTITY_MENTIONS_RELATION = "MENTIONS";
|
|
5465
|
+
function cascadeDeleteMemoryNode(db, memoryId) {
|
|
5466
|
+
const node = db.prepare(
|
|
5467
|
+
`SELECT id FROM graph_nodes WHERE type = 'memory' AND ref_id = ?`
|
|
5468
|
+
).get(memoryId);
|
|
5469
|
+
if (!node) return;
|
|
5470
|
+
db.prepare(
|
|
5471
|
+
`DELETE FROM graph_edges WHERE from_node_id = ? OR to_node_id = ?`
|
|
5472
|
+
).run(node.id, node.id);
|
|
5473
|
+
db.prepare(`DELETE FROM graph_nodes WHERE id = ?`).run(node.id);
|
|
5474
|
+
}
|
|
5475
|
+
|
|
5476
|
+
// src/graph/providers/sqlite-graph.ts
|
|
5477
|
+
var DEFAULT_GET_RELATED_DEPTH = 1;
|
|
5478
|
+
var MAX_GET_RELATED_DEPTH = 16;
|
|
5479
|
+
var SqliteGraphProvider = class {
|
|
5480
|
+
name = "sqlite";
|
|
5481
|
+
dbPath;
|
|
5482
|
+
concurrency;
|
|
5483
|
+
db = null;
|
|
5484
|
+
opened = false;
|
|
5485
|
+
constructor(options) {
|
|
5486
|
+
if (!options?.path || typeof options.path !== "string" || !options.path.trim()) {
|
|
5487
|
+
throw new ConfigurationError("sqlite graph requires a non-empty path");
|
|
5488
|
+
}
|
|
5489
|
+
this.dbPath = path7.resolve(options.path.trim());
|
|
5490
|
+
this.concurrency = resolveConcurrencyConfig(options.concurrency);
|
|
5491
|
+
}
|
|
5492
|
+
supportsFileSnapshot() {
|
|
5493
|
+
return this.dbPath !== ":memory:";
|
|
5494
|
+
}
|
|
5495
|
+
getDataPath() {
|
|
5496
|
+
return this.dbPath === ":memory:" ? null : this.dbPath;
|
|
5497
|
+
}
|
|
5498
|
+
async open() {
|
|
5499
|
+
if (this.opened) return;
|
|
5500
|
+
try {
|
|
5501
|
+
if (this.dbPath !== ":memory:") {
|
|
5502
|
+
fs6.mkdirSync(path7.dirname(this.dbPath), { recursive: true });
|
|
5503
|
+
}
|
|
5504
|
+
const db = new DatabaseSync(this.dbPath);
|
|
5505
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
5506
|
+
db.exec(`
|
|
5507
|
+
PRAGMA synchronous = NORMAL;
|
|
5508
|
+
PRAGMA foreign_keys = ON;
|
|
5509
|
+
PRAGMA busy_timeout = ${this.concurrency.lockTimeoutMs};
|
|
5510
|
+
PRAGMA temp_store = MEMORY;
|
|
5511
|
+
PRAGMA cache_size = -8192;
|
|
5512
|
+
PRAGMA wal_autocheckpoint = 1000;
|
|
5513
|
+
`);
|
|
5514
|
+
db.exec(CREATE_GRAPH_NODES_TABLE);
|
|
5515
|
+
db.exec(CREATE_GRAPH_EDGES_TABLE);
|
|
5516
|
+
for (const indexSql of CREATE_GRAPH_INDEXES) {
|
|
5517
|
+
db.exec(indexSql);
|
|
5518
|
+
}
|
|
5519
|
+
this.db = db;
|
|
5520
|
+
this.opened = true;
|
|
5521
|
+
} catch (error) {
|
|
5522
|
+
try {
|
|
5523
|
+
this.db?.close();
|
|
5524
|
+
} catch {
|
|
5525
|
+
}
|
|
5526
|
+
this.db = null;
|
|
5527
|
+
this.opened = false;
|
|
5528
|
+
throw new DatabaseError(
|
|
5529
|
+
`Failed to open SQLite graph database: ${describe(error)}`,
|
|
5530
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
5531
|
+
);
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
async close() {
|
|
5535
|
+
if (!this.db) {
|
|
5536
|
+
this.opened = false;
|
|
5537
|
+
return;
|
|
5538
|
+
}
|
|
5539
|
+
try {
|
|
5540
|
+
try {
|
|
5541
|
+
this.db.exec("PRAGMA optimize;");
|
|
5542
|
+
} catch {
|
|
5543
|
+
}
|
|
5544
|
+
this.db.close();
|
|
5545
|
+
} catch (error) {
|
|
5546
|
+
throw new DatabaseError(
|
|
5547
|
+
`Failed to close SQLite graph database: ${describe(error)}`,
|
|
5548
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
5549
|
+
);
|
|
5550
|
+
} finally {
|
|
5551
|
+
this.db = null;
|
|
5552
|
+
this.opened = false;
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
async linkMemories(fromId, toId, relation, metadata) {
|
|
5556
|
+
const db = this.requireDb();
|
|
5557
|
+
await this.withTx(() => {
|
|
5558
|
+
const fromNode = this.ensureMemoryNodeSync(db, fromId);
|
|
5559
|
+
const toNode = this.ensureMemoryNodeSync(db, toId);
|
|
5560
|
+
db.prepare(
|
|
5561
|
+
`DELETE FROM graph_edges
|
|
5562
|
+
WHERE from_node_id = ? AND to_node_id = ? AND relation = ?`
|
|
5563
|
+
).run(fromNode, toNode, relation);
|
|
5564
|
+
db.prepare(
|
|
5565
|
+
`INSERT INTO graph_edges (id, from_node_id, to_node_id, relation, metadata, created_at)
|
|
5566
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
5567
|
+
).run(
|
|
5568
|
+
crypto.randomUUID(),
|
|
5569
|
+
fromNode,
|
|
5570
|
+
toNode,
|
|
5571
|
+
relation,
|
|
5572
|
+
serializeMetadata2(metadata),
|
|
5573
|
+
nowIso2()
|
|
5574
|
+
);
|
|
5575
|
+
});
|
|
5576
|
+
}
|
|
5577
|
+
async unlinkMemories(fromId, toId, relation) {
|
|
5578
|
+
const db = this.requireDb();
|
|
5579
|
+
await this.withTx(() => {
|
|
5580
|
+
const fromNode = this.findMemoryNodeId(db, fromId);
|
|
5581
|
+
const toNode = this.findMemoryNodeId(db, toId);
|
|
5582
|
+
if (!fromNode || !toNode) return;
|
|
5583
|
+
if (relation !== void 0) {
|
|
5584
|
+
db.prepare(
|
|
5585
|
+
`DELETE FROM graph_edges
|
|
5586
|
+
WHERE from_node_id = ? AND to_node_id = ? AND relation = ?`
|
|
5587
|
+
).run(fromNode, toNode, relation);
|
|
5588
|
+
return;
|
|
5589
|
+
}
|
|
5590
|
+
db.prepare(
|
|
5591
|
+
`DELETE FROM graph_edges WHERE from_node_id = ? AND to_node_id = ?`
|
|
5592
|
+
).run(fromNode, toNode);
|
|
5593
|
+
});
|
|
5594
|
+
}
|
|
5595
|
+
/**
|
|
5596
|
+
* Bounded recursive CTE over `graph_edges`.
|
|
5597
|
+
*
|
|
5598
|
+
* Default depth: {@link DEFAULT_GET_RELATED_DEPTH} (1). Cap: 16.
|
|
5599
|
+
* Only memory↔memory edges are walked (entity MENTIONS edges are excluded).
|
|
5600
|
+
* Cycle-safe via path membership (`instr`) so A→B→C→A terminates.
|
|
5601
|
+
*
|
|
5602
|
+
* Returns stub {@link MemoryRecord}s (id + empty fields). The facade
|
|
5603
|
+
* re-hydrates full rows via `toMemoryRecord` from storage when possible.
|
|
5604
|
+
*/
|
|
5605
|
+
async getRelated(memoryId, options) {
|
|
5606
|
+
const db = this.requireDb();
|
|
5607
|
+
const depth = Math.max(
|
|
5608
|
+
1,
|
|
5609
|
+
Math.min(options?.depth ?? DEFAULT_GET_RELATED_DEPTH, MAX_GET_RELATED_DEPTH)
|
|
5610
|
+
);
|
|
5611
|
+
const direction = options?.direction ?? "both";
|
|
5612
|
+
const relation = options?.relation ?? null;
|
|
5613
|
+
const start = this.findMemoryNodeId(db, memoryId);
|
|
5614
|
+
if (!start) return [];
|
|
5615
|
+
const sql = buildGetRelatedSql(direction);
|
|
5616
|
+
const rows = db.prepare(sql).all(start, start, depth, relation, relation);
|
|
5617
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5618
|
+
const out = [];
|
|
5619
|
+
for (const row of rows) {
|
|
5620
|
+
if (row.ref_id === memoryId || seen.has(row.ref_id)) continue;
|
|
5621
|
+
seen.add(row.ref_id);
|
|
5622
|
+
out.push(stubMemoryRecord(row.ref_id));
|
|
5623
|
+
}
|
|
5624
|
+
return out;
|
|
5625
|
+
}
|
|
5626
|
+
async upsertEntity(entity) {
|
|
5627
|
+
const id = entityIdFrom(entity.name, entity.type);
|
|
5628
|
+
const db = this.requireDb();
|
|
5629
|
+
const meta2 = serializeMetadata2({
|
|
5630
|
+
...entity.metadata ?? {},
|
|
5631
|
+
entityType: entity.type
|
|
5632
|
+
});
|
|
5633
|
+
await this.withTx(() => {
|
|
5634
|
+
const existing = db.prepare(
|
|
5635
|
+
`SELECT id FROM graph_nodes WHERE type = 'entity' AND ref_id = ?`
|
|
5636
|
+
).get(id);
|
|
5637
|
+
if (existing) {
|
|
5638
|
+
db.prepare(
|
|
5639
|
+
`UPDATE graph_nodes SET name = ?, metadata = ? WHERE id = ?`
|
|
5640
|
+
).run(entity.name, meta2, existing.id);
|
|
5641
|
+
} else {
|
|
5642
|
+
db.prepare(
|
|
5643
|
+
`INSERT INTO graph_nodes (id, type, ref_id, name, metadata, created_at)
|
|
5644
|
+
VALUES (?, 'entity', ?, ?, ?, ?)`
|
|
5645
|
+
).run(id, id, entity.name, meta2, nowIso2());
|
|
5646
|
+
}
|
|
5647
|
+
});
|
|
5648
|
+
return id;
|
|
5649
|
+
}
|
|
5650
|
+
async linkEntityToMemory(entityId, memoryId, role) {
|
|
5651
|
+
const db = this.requireDb();
|
|
5652
|
+
await this.withTx(() => {
|
|
5653
|
+
const entity = db.prepare(
|
|
5654
|
+
`SELECT id FROM graph_nodes WHERE type = 'entity' AND ref_id = ?`
|
|
5655
|
+
).get(entityId);
|
|
5656
|
+
if (!entity) {
|
|
5657
|
+
throw new DatabaseError(`Entity not found: ${entityId}`, {
|
|
5658
|
+
operation: "linkEntityToMemory"
|
|
5659
|
+
});
|
|
5660
|
+
}
|
|
5661
|
+
const memoryNode = this.ensureMemoryNodeSync(db, memoryId);
|
|
5662
|
+
db.prepare(
|
|
5663
|
+
`DELETE FROM graph_edges
|
|
5664
|
+
WHERE from_node_id = ? AND to_node_id = ? AND relation = ?`
|
|
5665
|
+
).run(entity.id, memoryNode, ENTITY_MENTIONS_RELATION);
|
|
5666
|
+
db.prepare(
|
|
5667
|
+
`INSERT INTO graph_edges (id, from_node_id, to_node_id, relation, metadata, created_at)
|
|
5668
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
5669
|
+
).run(
|
|
5670
|
+
crypto.randomUUID(),
|
|
5671
|
+
entity.id,
|
|
5672
|
+
memoryNode,
|
|
5673
|
+
ENTITY_MENTIONS_RELATION,
|
|
5674
|
+
serializeMetadata2({ role: role ?? "" }),
|
|
5675
|
+
nowIso2()
|
|
5676
|
+
);
|
|
5677
|
+
});
|
|
5678
|
+
}
|
|
5679
|
+
async deleteMemory(memoryId) {
|
|
5680
|
+
const db = this.requireDb();
|
|
5681
|
+
await this.withTx(() => {
|
|
5682
|
+
cascadeDeleteMemoryNode(db, memoryId);
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5685
|
+
/**
|
|
5686
|
+
* Raw Cypher escape hatch — **not supported** on the SQLite graph provider.
|
|
5687
|
+
* Use the typed methods, or open the underlying SQLite file yourself for raw SQL.
|
|
5688
|
+
*/
|
|
5689
|
+
async query(_cypher, _params) {
|
|
5690
|
+
throw new DatabaseError(
|
|
5691
|
+
"raw Cypher queries are not supported by the SQLite graph provider \u2014 use the typed methods, or query the underlying SQLite tables directly via your own connection if you need raw access",
|
|
5692
|
+
{
|
|
5693
|
+
operation: "query",
|
|
5694
|
+
suggestion: "Use linkMemories / getRelated / upsertEntity / linkEntityToMemory, or open the graph .db file with node:sqlite for ad-hoc SQL"
|
|
5695
|
+
}
|
|
5696
|
+
);
|
|
5697
|
+
}
|
|
5698
|
+
async health() {
|
|
5699
|
+
try {
|
|
5700
|
+
if (!this.opened || !this.db) {
|
|
5701
|
+
return { ok: false, backend: "sqlite", details: { reason: "not open" } };
|
|
5702
|
+
}
|
|
5703
|
+
const nodeCount = Number(
|
|
5704
|
+
this.db.prepare(`SELECT COUNT(*) AS c FROM graph_nodes`).get().c
|
|
5705
|
+
);
|
|
5706
|
+
const edgeCount = Number(
|
|
5707
|
+
this.db.prepare(`SELECT COUNT(*) AS c FROM graph_edges`).get().c
|
|
5708
|
+
);
|
|
5709
|
+
return {
|
|
5710
|
+
ok: true,
|
|
5711
|
+
backend: "sqlite",
|
|
5712
|
+
details: {
|
|
5713
|
+
path: this.dbPath,
|
|
5714
|
+
embedded: true,
|
|
5715
|
+
nodeCount,
|
|
5716
|
+
edgeCount,
|
|
5717
|
+
exists: this.dbPath === ":memory:" || fs6.existsSync(this.dbPath)
|
|
5718
|
+
}
|
|
5719
|
+
};
|
|
5720
|
+
} catch (error) {
|
|
5721
|
+
return {
|
|
5722
|
+
ok: false,
|
|
5723
|
+
backend: "sqlite",
|
|
5724
|
+
details: {
|
|
5725
|
+
path: this.dbPath,
|
|
5726
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5727
|
+
}
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
async withTx(fn) {
|
|
5732
|
+
const db = this.requireDb();
|
|
5733
|
+
return withImmediateTransaction(db, this.concurrency, fn);
|
|
5734
|
+
}
|
|
5735
|
+
requireDb() {
|
|
5736
|
+
if (!this.db || !this.opened) {
|
|
5737
|
+
throw new DatabaseError("SQLite graph is not open", {
|
|
5738
|
+
operation: "graph",
|
|
5739
|
+
suggestion: "Call ready() / open() before graph operations"
|
|
5740
|
+
});
|
|
5741
|
+
}
|
|
5742
|
+
return this.db;
|
|
5743
|
+
}
|
|
5744
|
+
findMemoryNodeId(db, memoryId) {
|
|
5745
|
+
const row = db.prepare(
|
|
5746
|
+
`SELECT id FROM graph_nodes WHERE type = 'memory' AND ref_id = ?`
|
|
5747
|
+
).get(memoryId);
|
|
5748
|
+
return row?.id ?? null;
|
|
5749
|
+
}
|
|
5750
|
+
ensureMemoryNodeSync(db, memoryId) {
|
|
5751
|
+
const existing = this.findMemoryNodeId(db, memoryId);
|
|
5752
|
+
if (existing) return existing;
|
|
5753
|
+
db.prepare(
|
|
5754
|
+
`INSERT INTO graph_nodes (id, type, ref_id, name, metadata, created_at)
|
|
5755
|
+
VALUES (?, 'memory', ?, NULL, '{}', ?)`
|
|
5756
|
+
).run(memoryId, memoryId, nowIso2());
|
|
5757
|
+
return memoryId;
|
|
5758
|
+
}
|
|
5759
|
+
};
|
|
5760
|
+
function buildGetRelatedSql(direction) {
|
|
5761
|
+
const mentions = ENTITY_MENTIONS_RELATION;
|
|
5762
|
+
if (direction === "out") {
|
|
5763
|
+
return `
|
|
5764
|
+
WITH RECURSIVE walk(node_id, depth, path) AS (
|
|
5765
|
+
SELECT ? AS node_id, 0, '/' || ? || '/'
|
|
5766
|
+
UNION ALL
|
|
5767
|
+
SELECT e.to_node_id, w.depth + 1, w.path || e.to_node_id || '/'
|
|
5768
|
+
FROM walk w
|
|
5769
|
+
JOIN graph_edges e ON e.from_node_id = w.node_id
|
|
5770
|
+
JOIN graph_nodes dest ON dest.id = e.to_node_id AND dest.type = 'memory'
|
|
5771
|
+
WHERE w.depth < ?
|
|
5772
|
+
AND instr(w.path, '/' || e.to_node_id || '/') = 0
|
|
5773
|
+
AND (? IS NULL OR e.relation = ?)
|
|
5774
|
+
AND e.relation != '${mentions}'
|
|
5775
|
+
)
|
|
5776
|
+
SELECT DISTINCT n.ref_id AS ref_id
|
|
5777
|
+
FROM walk w
|
|
5778
|
+
JOIN graph_nodes n ON n.id = w.node_id
|
|
5779
|
+
WHERE w.depth > 0 AND n.type = 'memory'
|
|
5780
|
+
`;
|
|
5781
|
+
}
|
|
5782
|
+
if (direction === "in") {
|
|
5783
|
+
return `
|
|
5784
|
+
WITH RECURSIVE walk(node_id, depth, path) AS (
|
|
5785
|
+
SELECT ? AS node_id, 0, '/' || ? || '/'
|
|
5786
|
+
UNION ALL
|
|
5787
|
+
SELECT e.from_node_id, w.depth + 1, w.path || e.from_node_id || '/'
|
|
5788
|
+
FROM walk w
|
|
5789
|
+
JOIN graph_edges e ON e.to_node_id = w.node_id
|
|
5790
|
+
JOIN graph_nodes src ON src.id = e.from_node_id AND src.type = 'memory'
|
|
5791
|
+
WHERE w.depth < ?
|
|
5792
|
+
AND instr(w.path, '/' || e.from_node_id || '/') = 0
|
|
5793
|
+
AND (? IS NULL OR e.relation = ?)
|
|
5794
|
+
AND e.relation != '${mentions}'
|
|
5795
|
+
)
|
|
5796
|
+
SELECT DISTINCT n.ref_id AS ref_id
|
|
5797
|
+
FROM walk w
|
|
5798
|
+
JOIN graph_nodes n ON n.id = w.node_id
|
|
5799
|
+
WHERE w.depth > 0 AND n.type = 'memory'
|
|
5800
|
+
`;
|
|
5801
|
+
}
|
|
5802
|
+
return `
|
|
5803
|
+
WITH RECURSIVE walk(node_id, depth, path) AS (
|
|
5804
|
+
SELECT ? AS node_id, 0, '/' || ? || '/'
|
|
5805
|
+
UNION ALL
|
|
5806
|
+
SELECT neighbor.id, w.depth + 1, w.path || neighbor.id || '/'
|
|
5807
|
+
FROM walk w
|
|
5808
|
+
JOIN graph_edges e
|
|
5809
|
+
ON e.from_node_id = w.node_id OR e.to_node_id = w.node_id
|
|
5810
|
+
JOIN graph_nodes neighbor
|
|
5811
|
+
ON neighbor.id = CASE
|
|
5812
|
+
WHEN e.from_node_id = w.node_id THEN e.to_node_id
|
|
5813
|
+
ELSE e.from_node_id
|
|
5814
|
+
END
|
|
5815
|
+
AND neighbor.type = 'memory'
|
|
5816
|
+
WHERE w.depth < ?
|
|
5817
|
+
AND instr(w.path, '/' || neighbor.id || '/') = 0
|
|
5818
|
+
AND (? IS NULL OR e.relation = ?)
|
|
5819
|
+
AND e.relation != '${mentions}'
|
|
5820
|
+
)
|
|
5821
|
+
SELECT DISTINCT n.ref_id AS ref_id
|
|
5822
|
+
FROM walk w
|
|
5823
|
+
JOIN graph_nodes n ON n.id = w.node_id
|
|
5824
|
+
WHERE w.depth > 0 AND n.type = 'memory'
|
|
5825
|
+
`;
|
|
5826
|
+
}
|
|
5827
|
+
function nowIso2() {
|
|
5828
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5829
|
+
}
|
|
5830
|
+
function describe(error) {
|
|
5831
|
+
return error instanceof Error ? error.message : String(error);
|
|
5832
|
+
}
|
|
5833
|
+
|
|
5834
|
+
// src/graph/providers/neo4j.ts
|
|
5835
|
+
var Neo4jGraphProvider = class {
|
|
5836
|
+
name = "neo4j";
|
|
5837
|
+
url;
|
|
5838
|
+
username;
|
|
5839
|
+
password;
|
|
5840
|
+
database;
|
|
5841
|
+
driver = null;
|
|
5842
|
+
opened = false;
|
|
5843
|
+
constructor(options) {
|
|
5844
|
+
if (!options?.url?.trim()) {
|
|
5845
|
+
throw new ConfigurationError("neo4j graph requires a non-empty url");
|
|
5846
|
+
}
|
|
5847
|
+
if (!options?.username?.trim()) {
|
|
5848
|
+
throw new ConfigurationError("neo4j graph requires a non-empty username");
|
|
5849
|
+
}
|
|
5850
|
+
if (typeof options.password !== "string") {
|
|
5851
|
+
throw new ConfigurationError("neo4j graph requires a password string");
|
|
5852
|
+
}
|
|
5853
|
+
this.url = options.url.trim();
|
|
5854
|
+
this.username = options.username.trim();
|
|
5855
|
+
this.password = options.password;
|
|
5856
|
+
this.database = options.database?.trim() || void 0;
|
|
5857
|
+
}
|
|
5858
|
+
supportsFileSnapshot() {
|
|
5859
|
+
return false;
|
|
5860
|
+
}
|
|
5861
|
+
getDataPath() {
|
|
5862
|
+
return null;
|
|
5863
|
+
}
|
|
5864
|
+
async open() {
|
|
5865
|
+
if (this.opened) return;
|
|
5866
|
+
let mod;
|
|
5867
|
+
try {
|
|
5868
|
+
mod = await import('neo4j-driver');
|
|
5869
|
+
} catch {
|
|
5870
|
+
throw new ConfigurationError(
|
|
5871
|
+
'Neo4j graph requires the optional "neo4j-driver" package. Install it with: npm install neo4j-driver'
|
|
5872
|
+
);
|
|
5873
|
+
}
|
|
5874
|
+
this.driver = mod.driver(
|
|
5875
|
+
this.url,
|
|
5876
|
+
mod.auth.basic(this.username, this.password)
|
|
5877
|
+
);
|
|
5878
|
+
await this.driver.verifyConnectivity();
|
|
5879
|
+
this.opened = true;
|
|
5880
|
+
await this.withSession(async (session) => {
|
|
5881
|
+
try {
|
|
5882
|
+
await session.run(
|
|
5883
|
+
`CREATE CONSTRAINT memory_id IF NOT EXISTS FOR (m:Memory) REQUIRE m.id IS UNIQUE`
|
|
5884
|
+
);
|
|
5885
|
+
} catch {
|
|
5886
|
+
}
|
|
5887
|
+
try {
|
|
5888
|
+
await session.run(
|
|
5889
|
+
`CREATE CONSTRAINT entity_id IF NOT EXISTS FOR (e:Entity) REQUIRE e.id IS UNIQUE`
|
|
5890
|
+
);
|
|
5891
|
+
} catch {
|
|
5892
|
+
}
|
|
5893
|
+
});
|
|
5894
|
+
}
|
|
5895
|
+
async close() {
|
|
5896
|
+
if (this.driver) {
|
|
5897
|
+
await this.driver.close();
|
|
5898
|
+
}
|
|
5899
|
+
this.driver = null;
|
|
5900
|
+
this.opened = false;
|
|
5901
|
+
}
|
|
5902
|
+
requireDriver() {
|
|
5903
|
+
if (!this.driver || !this.opened) {
|
|
5904
|
+
throw new DatabaseError("Neo4j graph is not open", {
|
|
5905
|
+
operation: "graph",
|
|
5906
|
+
suggestion: "Call ready() / open() before graph operations"
|
|
5907
|
+
});
|
|
5908
|
+
}
|
|
5909
|
+
return this.driver;
|
|
5910
|
+
}
|
|
5911
|
+
async withSession(fn) {
|
|
5912
|
+
const driver = this.requireDriver();
|
|
5913
|
+
const session = this.database ? driver.session({ database: this.database }) : driver.session();
|
|
5914
|
+
try {
|
|
5915
|
+
return await fn(session);
|
|
5916
|
+
} finally {
|
|
5917
|
+
await session.close();
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
async run(cypher, params = {}) {
|
|
5921
|
+
return this.withSession(async (session) => {
|
|
5922
|
+
const result = await session.run(cypher, params);
|
|
5923
|
+
return result.records.map((rec) => {
|
|
5924
|
+
try {
|
|
5925
|
+
return rec.toObject();
|
|
5926
|
+
} catch {
|
|
5927
|
+
const obj = {};
|
|
5928
|
+
for (const key of rec.keys) {
|
|
5929
|
+
obj[key] = unwrapNeo4jValue(rec.get(key));
|
|
5930
|
+
}
|
|
5931
|
+
return obj;
|
|
5932
|
+
}
|
|
5933
|
+
});
|
|
5934
|
+
});
|
|
5935
|
+
}
|
|
5936
|
+
async ensureMemoryNode(id) {
|
|
5937
|
+
await this.run(
|
|
5938
|
+
`MERGE (m:Memory { id: $id })
|
|
5939
|
+
ON CREATE SET
|
|
5940
|
+
m.organization = '',
|
|
5941
|
+
m.agent = '',
|
|
5942
|
+
m.content_text = '',
|
|
5943
|
+
m.metadata_json = '{}',
|
|
5944
|
+
m.archived = false,
|
|
5945
|
+
m.compressed_into = '',
|
|
5946
|
+
m.created_at = '',
|
|
5947
|
+
m.updated_at = ''`,
|
|
5948
|
+
{ id }
|
|
5949
|
+
);
|
|
5950
|
+
}
|
|
5951
|
+
async linkMemories(fromId, toId, relation, metadata) {
|
|
5952
|
+
await this.ensureMemoryNode(fromId);
|
|
5953
|
+
await this.ensureMemoryNode(toId);
|
|
5954
|
+
await this.run(
|
|
5955
|
+
`MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
|
|
5956
|
+
MERGE (a)-[r:RELATED { relation: $relation }]->(b)
|
|
5957
|
+
SET r.metadata_json = $metadata`,
|
|
5958
|
+
{
|
|
5959
|
+
fromId,
|
|
5960
|
+
toId,
|
|
5961
|
+
relation,
|
|
5962
|
+
metadata: serializeMetadata2(metadata)
|
|
5963
|
+
}
|
|
5964
|
+
);
|
|
5965
|
+
}
|
|
5966
|
+
async unlinkMemories(fromId, toId, relation) {
|
|
5967
|
+
if (relation !== void 0) {
|
|
5968
|
+
await this.run(
|
|
5969
|
+
`MATCH (a:Memory { id: $fromId })-[r:RELATED { relation: $relation }]->(b:Memory { id: $toId })
|
|
5970
|
+
DELETE r`,
|
|
5971
|
+
{ fromId, toId, relation }
|
|
5972
|
+
);
|
|
5973
|
+
return;
|
|
5974
|
+
}
|
|
5975
|
+
await this.run(
|
|
5976
|
+
`MATCH (a:Memory { id: $fromId })-[r:RELATED]->(b:Memory { id: $toId })
|
|
5977
|
+
DELETE r`,
|
|
5978
|
+
{ fromId, toId }
|
|
5979
|
+
);
|
|
5980
|
+
}
|
|
5981
|
+
/**
|
|
5982
|
+
* Native Neo4j variable-length path traversal.
|
|
5983
|
+
* Relationship filter uses RELATED.relation property for parity with Kuzu.
|
|
5984
|
+
*/
|
|
5985
|
+
async getRelated(memoryId, options) {
|
|
5986
|
+
const depth = Math.max(1, Math.min(options?.depth ?? 1, 16));
|
|
5987
|
+
const direction = options?.direction ?? "both";
|
|
5988
|
+
const relation = options?.relation;
|
|
5989
|
+
const pathPattern = direction === "out" ? `(start)-[:RELATED*1..${depth}]->(n:Memory)` : direction === "in" ? `(start)<-[:RELATED*1..${depth}]-(n:Memory)` : `(start)-[:RELATED*1..${depth}]-(n:Memory)`;
|
|
5990
|
+
const cypher = relation ? `MATCH (start:Memory { id: $id }), p = ${pathPattern}
|
|
5991
|
+
WHERE ALL(r IN relationships(p) WHERE r.relation = $relation)
|
|
5992
|
+
AND n.id <> $id
|
|
5993
|
+
RETURN DISTINCT
|
|
5994
|
+
n.id AS id, n.organization AS organization, n.agent AS agent,
|
|
5995
|
+
n.content_text AS content_text, n.metadata_json AS metadata_json,
|
|
5996
|
+
n.archived AS archived, n.compressed_into AS compressed_into,
|
|
5997
|
+
n.created_at AS created_at, n.updated_at AS updated_at` : `MATCH (start:Memory { id: $id }), p = ${pathPattern}
|
|
5998
|
+
WHERE n.id <> $id
|
|
5999
|
+
RETURN DISTINCT
|
|
6000
|
+
n.id AS id, n.organization AS organization, n.agent AS agent,
|
|
6001
|
+
n.content_text AS content_text, n.metadata_json AS metadata_json,
|
|
6002
|
+
n.archived AS archived, n.compressed_into AS compressed_into,
|
|
6003
|
+
n.created_at AS created_at, n.updated_at AS updated_at`;
|
|
6004
|
+
const params = { id: memoryId };
|
|
6005
|
+
if (relation !== void 0) params.relation = relation;
|
|
6006
|
+
const rows = await this.run(cypher, params);
|
|
6007
|
+
return rows.map(
|
|
6008
|
+
(row) => rowToMemoryRecord(unwrapRow(row))
|
|
6009
|
+
);
|
|
6010
|
+
}
|
|
6011
|
+
async upsertEntity(entity) {
|
|
6012
|
+
const id = entityIdFrom(entity.name, entity.type);
|
|
6013
|
+
await this.run(
|
|
6014
|
+
`MERGE (e:Entity { id: $id })
|
|
6015
|
+
SET e.name = $name, e.type = $type, e.metadata_json = $metadata`,
|
|
6016
|
+
{
|
|
6017
|
+
id,
|
|
6018
|
+
name: entity.name,
|
|
6019
|
+
type: entity.type,
|
|
6020
|
+
metadata: serializeMetadata2(entity.metadata)
|
|
6021
|
+
}
|
|
6022
|
+
);
|
|
6023
|
+
return id;
|
|
6024
|
+
}
|
|
6025
|
+
async linkEntityToMemory(entityId, memoryId, role) {
|
|
6026
|
+
await this.ensureMemoryNode(memoryId);
|
|
6027
|
+
const found = await this.run(
|
|
6028
|
+
`MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
|
|
6029
|
+
{ id: entityId }
|
|
6030
|
+
);
|
|
6031
|
+
if (found.length === 0) {
|
|
6032
|
+
throw new DatabaseError(`Entity not found: ${entityId}`, {
|
|
6033
|
+
operation: "linkEntityToMemory"
|
|
6034
|
+
});
|
|
6035
|
+
}
|
|
6036
|
+
await this.run(
|
|
6037
|
+
`MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
|
|
6038
|
+
MERGE (e)-[r:MENTIONS]->(m)
|
|
6039
|
+
SET r.role = $role`,
|
|
6040
|
+
{ entityId, memoryId, role: role ?? "" }
|
|
6041
|
+
);
|
|
6042
|
+
}
|
|
6043
|
+
async deleteMemory(memoryId) {
|
|
6044
|
+
await this.run(
|
|
6045
|
+
`MATCH (m:Memory { id: $id })
|
|
6046
|
+
DETACH DELETE m`,
|
|
6047
|
+
{ id: memoryId }
|
|
6048
|
+
);
|
|
6049
|
+
}
|
|
6050
|
+
async query(cypher, params) {
|
|
6051
|
+
return this.run(cypher, params ?? {});
|
|
6052
|
+
}
|
|
6053
|
+
async health() {
|
|
6054
|
+
try {
|
|
6055
|
+
if (!this.opened || !this.driver) {
|
|
6056
|
+
return { ok: false, backend: "neo4j", details: { reason: "not open" } };
|
|
6057
|
+
}
|
|
6058
|
+
await this.driver.verifyConnectivity();
|
|
6059
|
+
let serverInfo = void 0;
|
|
6060
|
+
if (typeof this.driver.getServerInfo === "function") {
|
|
6061
|
+
serverInfo = await this.driver.getServerInfo();
|
|
6062
|
+
}
|
|
6063
|
+
return {
|
|
6064
|
+
ok: true,
|
|
6065
|
+
backend: "neo4j",
|
|
6066
|
+
details: {
|
|
6067
|
+
url: this.url,
|
|
6068
|
+
database: this.database ?? "default",
|
|
6069
|
+
networked: true,
|
|
6070
|
+
serverInfo
|
|
6071
|
+
}
|
|
6072
|
+
};
|
|
6073
|
+
} catch (error) {
|
|
6074
|
+
return {
|
|
6075
|
+
ok: false,
|
|
6076
|
+
backend: "neo4j",
|
|
6077
|
+
details: {
|
|
6078
|
+
url: this.url,
|
|
6079
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6080
|
+
}
|
|
6081
|
+
};
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
};
|
|
6085
|
+
function unwrapNeo4jValue(value) {
|
|
6086
|
+
if (value == null) return value;
|
|
6087
|
+
if (typeof value === "object" && value !== null && "toNumber" in value) {
|
|
6088
|
+
try {
|
|
6089
|
+
return value.toNumber();
|
|
6090
|
+
} catch {
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
6093
|
+
if (typeof value === "object" && value !== null && "properties" in value) {
|
|
6094
|
+
return unwrapRow(
|
|
6095
|
+
value.properties
|
|
6096
|
+
);
|
|
6097
|
+
}
|
|
6098
|
+
return value;
|
|
6099
|
+
}
|
|
6100
|
+
function unwrapRow(row) {
|
|
6101
|
+
const out = {};
|
|
6102
|
+
for (const [k, v] of Object.entries(row)) {
|
|
6103
|
+
out[k] = unwrapNeo4jValue(v);
|
|
6104
|
+
}
|
|
6105
|
+
return out;
|
|
6106
|
+
}
|
|
6107
|
+
|
|
5336
6108
|
// src/core/validate.ts
|
|
5337
6109
|
function assertNonEmpty(value, fieldName) {
|
|
5338
6110
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
@@ -5413,9 +6185,21 @@ function validateTelemetryConfig(config) {
|
|
|
5413
6185
|
if (!config.database || typeof config.database !== "object") {
|
|
5414
6186
|
throw new ConfigurationError("telemetry.database is required when telemetry is enabled");
|
|
5415
6187
|
}
|
|
6188
|
+
const providerName = String(
|
|
6189
|
+
config.database.provider ?? ""
|
|
6190
|
+
);
|
|
6191
|
+
if (providerName === "kuzu" || providerName === "neo4j") {
|
|
6192
|
+
throw new ConfigurationError(
|
|
6193
|
+
`telemetry supports sqlite or postgres only (got "${providerName}")`,
|
|
6194
|
+
{
|
|
6195
|
+
reason: `provider=${providerName}`,
|
|
6196
|
+
suggestion: 'Use telemetry: { database: { provider: "sqlite", url: "./telemetry.db" } }. Graph backends (kuzu/neo4j) are not valid telemetry stores.'
|
|
6197
|
+
}
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
5416
6200
|
if (config.database.provider !== "sqlite") {
|
|
5417
6201
|
throw new ConfigurationError(
|
|
5418
|
-
`Unsupported telemetry provider "${config.database.provider}". Only "sqlite" is implemented
|
|
6202
|
+
`Unsupported telemetry provider "${config.database.provider}". Only "sqlite" is implemented; PostgreSQL typed but not implemented. Telemetry supports sqlite or postgres only \u2014 not kuzu/neo4j.`,
|
|
5419
6203
|
{
|
|
5420
6204
|
reason: `provider=${config.database.provider}`,
|
|
5421
6205
|
suggestion: 'Use telemetry: { database: { provider: "sqlite", url: "./telemetry.db" } }'
|
|
@@ -5500,14 +6284,74 @@ function validateWolbargOptions(options) {
|
|
|
5500
6284
|
if (telemetry && !isTelemetryProvider(telemetry)) {
|
|
5501
6285
|
telemetry = validateTelemetryConfig(telemetry);
|
|
5502
6286
|
}
|
|
6287
|
+
let graph = options.graph;
|
|
6288
|
+
if (graph !== void 0) {
|
|
6289
|
+
graph = resolveGraphInput(graph);
|
|
6290
|
+
}
|
|
5503
6291
|
return {
|
|
5504
6292
|
...options,
|
|
5505
6293
|
organization: options.organization.trim(),
|
|
5506
6294
|
storage,
|
|
5507
6295
|
database: void 0,
|
|
5508
|
-
...telemetry ? { telemetry } : {}
|
|
6296
|
+
...telemetry ? { telemetry } : {},
|
|
6297
|
+
...graph !== void 0 ? { graph } : {}
|
|
5509
6298
|
};
|
|
5510
6299
|
}
|
|
6300
|
+
function resolveGraphInput(input) {
|
|
6301
|
+
if (input == null || typeof input !== "object") {
|
|
6302
|
+
throw new ConfigurationError(
|
|
6303
|
+
"graph must be a GraphProvider instance or a config object",
|
|
6304
|
+
{
|
|
6305
|
+
suggestion: "Use graph: sqliteGraph({ path }) or graph: neo4jGraph({ url, username, password })"
|
|
6306
|
+
}
|
|
6307
|
+
);
|
|
6308
|
+
}
|
|
6309
|
+
if (Array.isArray(input)) {
|
|
6310
|
+
throw new ConfigurationError(
|
|
6311
|
+
"graph must be a GraphProvider instance or a config object",
|
|
6312
|
+
{
|
|
6313
|
+
suggestion: "Use graph: sqliteGraph({ path }) or graph: neo4jGraph({ url, username, password })"
|
|
6314
|
+
}
|
|
6315
|
+
);
|
|
6316
|
+
}
|
|
6317
|
+
if (isGraphProvider(input)) {
|
|
6318
|
+
return input;
|
|
6319
|
+
}
|
|
6320
|
+
const config = input;
|
|
6321
|
+
if (config.provider === "sqlite") {
|
|
6322
|
+
if (typeof config.path !== "string" || !config.path.trim()) {
|
|
6323
|
+
throw new ConfigurationError(
|
|
6324
|
+
'graph sqlite config requires a non-empty "path"'
|
|
6325
|
+
);
|
|
6326
|
+
}
|
|
6327
|
+
return new SqliteGraphProvider({ path: config.path });
|
|
6328
|
+
}
|
|
6329
|
+
if (config.provider === "neo4j") {
|
|
6330
|
+
if (typeof config.url !== "string" || !config.url.trim()) {
|
|
6331
|
+
throw new ConfigurationError('graph neo4j config requires a non-empty "url"');
|
|
6332
|
+
}
|
|
6333
|
+
if (typeof config.username !== "string" || !config.username.trim()) {
|
|
6334
|
+
throw new ConfigurationError(
|
|
6335
|
+
'graph neo4j config requires a non-empty "username"'
|
|
6336
|
+
);
|
|
6337
|
+
}
|
|
6338
|
+
if (typeof config.password !== "string") {
|
|
6339
|
+
throw new ConfigurationError('graph neo4j config requires a "password" string');
|
|
6340
|
+
}
|
|
6341
|
+
return new Neo4jGraphProvider({
|
|
6342
|
+
url: config.url,
|
|
6343
|
+
username: config.username,
|
|
6344
|
+
password: config.password,
|
|
6345
|
+
database: config.database
|
|
6346
|
+
});
|
|
6347
|
+
}
|
|
6348
|
+
throw new ConfigurationError(
|
|
6349
|
+
`Unsupported graph provider "${String(config.provider)}". Supported: "sqlite", "neo4j", or a GraphProvider instance.`,
|
|
6350
|
+
{
|
|
6351
|
+
suggestion: 'Use graph: sqliteGraph({ path: "./local-graph.db" }) or graph: neo4jGraph({ url, username, password })'
|
|
6352
|
+
}
|
|
6353
|
+
);
|
|
6354
|
+
}
|
|
5511
6355
|
|
|
5512
6356
|
// src/telemetry/logger.ts
|
|
5513
6357
|
var LEVEL_ORDER = {
|
|
@@ -5787,7 +6631,7 @@ var SqliteEventDatabase = class {
|
|
|
5787
6631
|
try {
|
|
5788
6632
|
const dbPath = this.resolvePath(this.url);
|
|
5789
6633
|
if (dbPath !== ":memory:" && !this.readonly) {
|
|
5790
|
-
|
|
6634
|
+
fs6.mkdirSync(path7.dirname(dbPath), { recursive: true });
|
|
5791
6635
|
}
|
|
5792
6636
|
const db = new DatabaseSync(dbPath, {
|
|
5793
6637
|
allowExtension: false,
|
|
@@ -5832,7 +6676,7 @@ var SqliteEventDatabase = class {
|
|
|
5832
6676
|
this.db = null;
|
|
5833
6677
|
this.columns.clear();
|
|
5834
6678
|
throw new InitializationError(
|
|
5835
|
-
`Failed to open telemetry EventDatabase: ${
|
|
6679
|
+
`Failed to open telemetry EventDatabase: ${describe2(error)}`,
|
|
5836
6680
|
{ cause: error instanceof Error ? error : void 0 }
|
|
5837
6681
|
);
|
|
5838
6682
|
}
|
|
@@ -5887,7 +6731,7 @@ var SqliteEventDatabase = class {
|
|
|
5887
6731
|
return event;
|
|
5888
6732
|
} catch (error) {
|
|
5889
6733
|
throw new DatabaseError(
|
|
5890
|
-
`Failed to write telemetry event: ${
|
|
6734
|
+
`Failed to write telemetry event: ${describe2(error)}`,
|
|
5891
6735
|
{ cause: error instanceof Error ? error : void 0 }
|
|
5892
6736
|
);
|
|
5893
6737
|
}
|
|
@@ -6043,7 +6887,7 @@ var SqliteEventDatabase = class {
|
|
|
6043
6887
|
}
|
|
6044
6888
|
resolvePath(url) {
|
|
6045
6889
|
if (url === ":memory:") return ":memory:";
|
|
6046
|
-
return
|
|
6890
|
+
return path7.isAbsolute(url) ? url : path7.resolve(process.cwd(), url);
|
|
6047
6891
|
}
|
|
6048
6892
|
};
|
|
6049
6893
|
function normalizeEvent(input) {
|
|
@@ -6128,7 +6972,7 @@ function parseJson(value) {
|
|
|
6128
6972
|
return null;
|
|
6129
6973
|
}
|
|
6130
6974
|
}
|
|
6131
|
-
function
|
|
6975
|
+
function describe2(error) {
|
|
6132
6976
|
return error instanceof Error ? error.message : String(error);
|
|
6133
6977
|
}
|
|
6134
6978
|
|
|
@@ -6206,16 +7050,15 @@ var SqliteTelemetryProvider = class {
|
|
|
6206
7050
|
}
|
|
6207
7051
|
}
|
|
6208
7052
|
};
|
|
6209
|
-
var SDK_VERSION2 = "0.3.0";
|
|
6210
7053
|
var SqliteCheckpointProvider = class {
|
|
6211
7054
|
name = "sqlite";
|
|
6212
7055
|
directory;
|
|
6213
7056
|
ready = false;
|
|
6214
7057
|
constructor(options) {
|
|
6215
|
-
this.directory = options?.directory ??
|
|
7058
|
+
this.directory = options?.directory ?? path7.resolve(process.cwd(), ".wolbarg", "checkpoints");
|
|
6216
7059
|
}
|
|
6217
7060
|
async open() {
|
|
6218
|
-
|
|
7061
|
+
fs6.mkdirSync(this.directory, { recursive: true });
|
|
6219
7062
|
this.ready = true;
|
|
6220
7063
|
}
|
|
6221
7064
|
async close() {
|
|
@@ -6225,7 +7068,7 @@ var SqliteCheckpointProvider = class {
|
|
|
6225
7068
|
this.requireReady();
|
|
6226
7069
|
assertCheckpointName(name);
|
|
6227
7070
|
const metaPath = this.metaPath(name);
|
|
6228
|
-
if (
|
|
7071
|
+
if (fs6.existsSync(metaPath)) {
|
|
6229
7072
|
throw new ValidationError(
|
|
6230
7073
|
`Checkpoint "${name}" already exists. Choose a different name \u2014 checkpoints are never overwritten.`
|
|
6231
7074
|
);
|
|
@@ -6236,7 +7079,7 @@ var SqliteCheckpointProvider = class {
|
|
|
6236
7079
|
"Cannot checkpoint an in-memory database. Use a file-backed SQLite database."
|
|
6237
7080
|
);
|
|
6238
7081
|
}
|
|
6239
|
-
if (!
|
|
7082
|
+
if (!fs6.existsSync(resolvedSource)) {
|
|
6240
7083
|
throw new DatabaseError(
|
|
6241
7084
|
`Failed to execute checkpoint()
|
|
6242
7085
|
Reason:
|
|
@@ -6247,18 +7090,18 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6247
7090
|
}
|
|
6248
7091
|
const snapshotPath = this.snapshotPath(name);
|
|
6249
7092
|
await safeSqliteBackup(resolvedSource, snapshotPath);
|
|
6250
|
-
const stats =
|
|
7093
|
+
const stats = fs6.statSync(snapshotPath);
|
|
6251
7094
|
const meta2 = {
|
|
6252
7095
|
name,
|
|
6253
7096
|
description: options?.description ?? null,
|
|
6254
7097
|
createdAt: nowIso(),
|
|
6255
|
-
sdkVersion:
|
|
7098
|
+
sdkVersion: SDK_VERSION,
|
|
6256
7099
|
provider: this.name,
|
|
6257
7100
|
snapshotPath,
|
|
6258
7101
|
sourcePath: resolvedSource,
|
|
6259
7102
|
sizeBytes: stats.size
|
|
6260
7103
|
};
|
|
6261
|
-
|
|
7104
|
+
fs6.writeFileSync(metaPath, JSON.stringify(meta2, null, 2), "utf8");
|
|
6262
7105
|
return meta2;
|
|
6263
7106
|
}
|
|
6264
7107
|
async rollback(name, targetPath) {
|
|
@@ -6273,11 +7116,11 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6273
7116
|
"Cannot rollback into an in-memory database."
|
|
6274
7117
|
);
|
|
6275
7118
|
}
|
|
6276
|
-
|
|
7119
|
+
fs6.mkdirSync(path7.dirname(resolvedTarget), { recursive: true });
|
|
6277
7120
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
6278
7121
|
const side = `${resolvedTarget}${suffix}`;
|
|
6279
|
-
if (
|
|
6280
|
-
|
|
7122
|
+
if (fs6.existsSync(side)) {
|
|
7123
|
+
fs6.rmSync(side, { force: true });
|
|
6281
7124
|
}
|
|
6282
7125
|
}
|
|
6283
7126
|
await safeSqliteBackup(meta2.snapshotPath, resolvedTarget);
|
|
@@ -6288,26 +7131,26 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6288
7131
|
const metaPath = this.metaPath(name);
|
|
6289
7132
|
const snapshotPath = this.snapshotPath(name);
|
|
6290
7133
|
let removed = false;
|
|
6291
|
-
if (
|
|
6292
|
-
|
|
7134
|
+
if (fs6.existsSync(metaPath)) {
|
|
7135
|
+
fs6.rmSync(metaPath, { force: true });
|
|
6293
7136
|
removed = true;
|
|
6294
7137
|
}
|
|
6295
|
-
if (
|
|
6296
|
-
|
|
7138
|
+
if (fs6.existsSync(snapshotPath)) {
|
|
7139
|
+
fs6.rmSync(snapshotPath, { force: true });
|
|
6297
7140
|
removed = true;
|
|
6298
7141
|
}
|
|
6299
7142
|
return removed;
|
|
6300
7143
|
}
|
|
6301
7144
|
async listCheckpoints() {
|
|
6302
7145
|
this.requireReady();
|
|
6303
|
-
if (!
|
|
7146
|
+
if (!fs6.existsSync(this.directory)) {
|
|
6304
7147
|
return [];
|
|
6305
7148
|
}
|
|
6306
|
-
const files =
|
|
7149
|
+
const files = fs6.readdirSync(this.directory).filter((f) => f.endsWith(".json"));
|
|
6307
7150
|
const out = [];
|
|
6308
7151
|
for (const file of files) {
|
|
6309
7152
|
try {
|
|
6310
|
-
const raw =
|
|
7153
|
+
const raw = fs6.readFileSync(path7.join(this.directory, file), "utf8");
|
|
6311
7154
|
out.push(JSON.parse(raw));
|
|
6312
7155
|
} catch {
|
|
6313
7156
|
}
|
|
@@ -6317,20 +7160,81 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6317
7160
|
async getCheckpoint(name) {
|
|
6318
7161
|
this.requireReady();
|
|
6319
7162
|
const metaPath = this.metaPath(name);
|
|
6320
|
-
if (!
|
|
7163
|
+
if (!fs6.existsSync(metaPath)) {
|
|
6321
7164
|
return null;
|
|
6322
7165
|
}
|
|
6323
7166
|
try {
|
|
6324
|
-
return JSON.parse(
|
|
7167
|
+
return JSON.parse(fs6.readFileSync(metaPath, "utf8"));
|
|
6325
7168
|
} catch {
|
|
6326
7169
|
return null;
|
|
6327
7170
|
}
|
|
6328
7171
|
}
|
|
7172
|
+
/**
|
|
7173
|
+
* Consistent SQLite file backup (WAL checkpoint + backup API / copy).
|
|
7174
|
+
* Used for memory snapshots and for secondary files such as the graph DB.
|
|
7175
|
+
*/
|
|
7176
|
+
async backupSqliteFile(sourcePath, destPath) {
|
|
7177
|
+
this.requireReady();
|
|
7178
|
+
const resolved = resolveDbPath(sourcePath);
|
|
7179
|
+
if (resolved === ":memory:") {
|
|
7180
|
+
throw new ConfigurationError(
|
|
7181
|
+
"Cannot backup an in-memory database. Use a file-backed SQLite database."
|
|
7182
|
+
);
|
|
7183
|
+
}
|
|
7184
|
+
if (!fs6.existsSync(resolved)) {
|
|
7185
|
+
throw new DatabaseError(
|
|
7186
|
+
`SQLite file not found at ${resolved}`,
|
|
7187
|
+
{
|
|
7188
|
+
suggestion: "Ensure the source database exists before backing up."
|
|
7189
|
+
}
|
|
7190
|
+
);
|
|
7191
|
+
}
|
|
7192
|
+
await safeSqliteBackup(resolved, destPath);
|
|
7193
|
+
}
|
|
7194
|
+
/**
|
|
7195
|
+
* Snapshot a secondary SQLite file (typically the graph DB) next to a named
|
|
7196
|
+
* memory checkpoint as `{name}.graph.db`.
|
|
7197
|
+
*/
|
|
7198
|
+
async checkpointGraph(name, graphSourcePath) {
|
|
7199
|
+
this.requireReady();
|
|
7200
|
+
assertCheckpointName(name);
|
|
7201
|
+
const dest = this.graphSnapshotPath(name);
|
|
7202
|
+
await this.backupSqliteFile(graphSourcePath, dest);
|
|
7203
|
+
return dest;
|
|
7204
|
+
}
|
|
7205
|
+
/**
|
|
7206
|
+
* Restore a previously snapshotted graph SQLite file into `targetPath`.
|
|
7207
|
+
*/
|
|
7208
|
+
async rollbackGraph(name, targetPath) {
|
|
7209
|
+
this.requireReady();
|
|
7210
|
+
const snapshot = this.graphSnapshotPath(name);
|
|
7211
|
+
if (!fs6.existsSync(snapshot)) {
|
|
7212
|
+
return;
|
|
7213
|
+
}
|
|
7214
|
+
const resolvedTarget = resolveDbPath(targetPath);
|
|
7215
|
+
if (resolvedTarget === ":memory:") {
|
|
7216
|
+
throw new ConfigurationError(
|
|
7217
|
+
"Cannot rollback graph into an in-memory database."
|
|
7218
|
+
);
|
|
7219
|
+
}
|
|
7220
|
+
fs6.mkdirSync(path7.dirname(resolvedTarget), { recursive: true });
|
|
7221
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
7222
|
+
const side = `${resolvedTarget}${suffix}`;
|
|
7223
|
+
if (fs6.existsSync(side)) {
|
|
7224
|
+
fs6.rmSync(side, { force: true });
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
await safeSqliteBackup(snapshot, resolvedTarget);
|
|
7228
|
+
}
|
|
7229
|
+
/** Absolute path of the graph snapshot for a named checkpoint, if present. */
|
|
7230
|
+
graphSnapshotPath(name) {
|
|
7231
|
+
return path7.join(this.directory, `${sanitize(name)}.graph.db`);
|
|
7232
|
+
}
|
|
6329
7233
|
metaPath(name) {
|
|
6330
|
-
return
|
|
7234
|
+
return path7.join(this.directory, `${sanitize(name)}.json`);
|
|
6331
7235
|
}
|
|
6332
7236
|
snapshotPath(name) {
|
|
6333
|
-
return
|
|
7237
|
+
return path7.join(this.directory, `${sanitize(name)}.db`);
|
|
6334
7238
|
}
|
|
6335
7239
|
requireReady() {
|
|
6336
7240
|
if (!this.ready) {
|
|
@@ -6353,12 +7257,12 @@ function assertCheckpointName(name) {
|
|
|
6353
7257
|
}
|
|
6354
7258
|
function resolveDbPath(connectionString) {
|
|
6355
7259
|
if (connectionString === ":memory:") return ":memory:";
|
|
6356
|
-
return
|
|
7260
|
+
return path7.isAbsolute(connectionString) ? connectionString : path7.resolve(process.cwd(), connectionString);
|
|
6357
7261
|
}
|
|
6358
7262
|
async function safeSqliteBackup(sourcePath, destPath) {
|
|
6359
|
-
|
|
6360
|
-
if (
|
|
6361
|
-
|
|
7263
|
+
fs6.mkdirSync(path7.dirname(destPath), { recursive: true });
|
|
7264
|
+
if (fs6.existsSync(destPath)) {
|
|
7265
|
+
fs6.rmSync(destPath, { force: true });
|
|
6362
7266
|
}
|
|
6363
7267
|
let source = null;
|
|
6364
7268
|
let dest = null;
|
|
@@ -6377,7 +7281,7 @@ async function safeSqliteBackup(sourcePath, destPath) {
|
|
|
6377
7281
|
source = null;
|
|
6378
7282
|
dest.close();
|
|
6379
7283
|
dest = null;
|
|
6380
|
-
|
|
7284
|
+
fs6.copyFileSync(sourcePath, destPath);
|
|
6381
7285
|
}
|
|
6382
7286
|
} catch (error) {
|
|
6383
7287
|
throw new DatabaseError(
|
|
@@ -6473,6 +7377,7 @@ var Wolbarg = class {
|
|
|
6473
7377
|
keywordSearch = null;
|
|
6474
7378
|
ocr = null;
|
|
6475
7379
|
vision = null;
|
|
7380
|
+
graph = null;
|
|
6476
7381
|
chunking = null;
|
|
6477
7382
|
retrievalConfig = {};
|
|
6478
7383
|
embeddingDimensions = null;
|
|
@@ -6519,6 +7424,9 @@ var Wolbarg = class {
|
|
|
6519
7424
|
this.keywordSearch = validated.keywordSearch ?? null;
|
|
6520
7425
|
this.ocr = validated.ocr ?? null;
|
|
6521
7426
|
this.vision = validated.vision ?? null;
|
|
7427
|
+
if (validated.graph) {
|
|
7428
|
+
this.graph = validated.graph;
|
|
7429
|
+
}
|
|
6522
7430
|
this.chunking = validated.chunking ?? null;
|
|
6523
7431
|
this.retrievalConfig = validated.retrieval ?? {};
|
|
6524
7432
|
this.subscribeBackend = new SqliteSubscribeEmitter();
|
|
@@ -6597,6 +7505,7 @@ var Wolbarg = class {
|
|
|
6597
7505
|
await this.storage.open();
|
|
6598
7506
|
await this.telemetry.open();
|
|
6599
7507
|
await this.checkpointProvider?.open();
|
|
7508
|
+
await this.graph?.open();
|
|
6600
7509
|
if (this.rawEmbedding && this.embeddingCacheConfig.enabled) {
|
|
6601
7510
|
if (this.storage instanceof SqliteStorageProvider) {
|
|
6602
7511
|
const sqlite2 = this.storage;
|
|
@@ -6648,6 +7557,7 @@ var Wolbarg = class {
|
|
|
6648
7557
|
this.initialized = true;
|
|
6649
7558
|
this.telemetry.emitStartup(this.storage.name);
|
|
6650
7559
|
} catch (error) {
|
|
7560
|
+
await this.graph?.close().catch(() => void 0);
|
|
6651
7561
|
await this.storage.close().catch(() => void 0);
|
|
6652
7562
|
await this.telemetry.close().catch(() => void 0);
|
|
6653
7563
|
this.initialized = false;
|
|
@@ -6674,7 +7584,7 @@ var Wolbarg = class {
|
|
|
6674
7584
|
metadata: result.metadata,
|
|
6675
7585
|
agentId: result.agent,
|
|
6676
7586
|
tags: telemetryTags(result.metadata),
|
|
6677
|
-
extra: { upsertAction: result.action }
|
|
7587
|
+
extra: { upsertAction: result.action, action: result.action }
|
|
6678
7588
|
});
|
|
6679
7589
|
return result;
|
|
6680
7590
|
} catch (error) {
|
|
@@ -7378,6 +8288,13 @@ var Wolbarg = class {
|
|
|
7378
8288
|
tags: commonTags(serialized.map((result) => result.metadata))
|
|
7379
8289
|
};
|
|
7380
8290
|
if (!explain) {
|
|
8291
|
+
if (options.includeGraph === true && this.graph) {
|
|
8292
|
+
const tGraph = performance.now();
|
|
8293
|
+
for (const hit of serialized) {
|
|
8294
|
+
hit.related = await this.hydrateRelated(hit.id);
|
|
8295
|
+
}
|
|
8296
|
+
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
8297
|
+
}
|
|
7381
8298
|
trace.success(telemetryFields);
|
|
7382
8299
|
return serialized;
|
|
7383
8300
|
}
|
|
@@ -7730,21 +8647,82 @@ var Wolbarg = class {
|
|
|
7730
8647
|
throw wrapOperationError("ingest", error);
|
|
7731
8648
|
}
|
|
7732
8649
|
}
|
|
8650
|
+
/**
|
|
8651
|
+
* Link two memories in the optional graph layer.
|
|
8652
|
+
* Throws {@link ProviderNotConfiguredError} when no graph provider is set.
|
|
8653
|
+
*/
|
|
8654
|
+
async linkMemories(fromId, toId, relation, metadata) {
|
|
8655
|
+
const trace = this.telemetry.start("linkMemories");
|
|
8656
|
+
try {
|
|
8657
|
+
await this.requireReady();
|
|
8658
|
+
const graph = this.requireGraph("linkMemories");
|
|
8659
|
+
assertNonEmptyString(fromId, "fromId");
|
|
8660
|
+
assertNonEmptyString(toId, "toId");
|
|
8661
|
+
assertNonEmptyString(relation, "relation");
|
|
8662
|
+
const tGraph = performance.now();
|
|
8663
|
+
await graph.linkMemories(
|
|
8664
|
+
fromId.trim(),
|
|
8665
|
+
toId.trim(),
|
|
8666
|
+
relation.trim(),
|
|
8667
|
+
metadata
|
|
8668
|
+
);
|
|
8669
|
+
trace.mark("databaseWriteMs", performance.now() - tGraph);
|
|
8670
|
+
trace.success({
|
|
8671
|
+
provider: graph.name,
|
|
8672
|
+
memoryIds: [fromId.trim(), toId.trim()],
|
|
8673
|
+
extra: { relation: relation.trim() }
|
|
8674
|
+
});
|
|
8675
|
+
} catch (error) {
|
|
8676
|
+
trace.failure(error);
|
|
8677
|
+
throw wrapOperationError("linkMemories", error);
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
/**
|
|
8681
|
+
* Traverse related memories via the optional graph layer.
|
|
8682
|
+
* Throws {@link ProviderNotConfiguredError} when no graph provider is set.
|
|
8683
|
+
* Results are re-hydrated from SQL storage when available.
|
|
8684
|
+
*/
|
|
8685
|
+
async getRelated(memoryId, options) {
|
|
8686
|
+
const trace = this.telemetry.start("getRelated");
|
|
8687
|
+
try {
|
|
8688
|
+
await this.requireReady();
|
|
8689
|
+
const graph = this.requireGraph("getRelated");
|
|
8690
|
+
assertNonEmptyString(memoryId, "memoryId");
|
|
8691
|
+
const tGraph = performance.now();
|
|
8692
|
+
const related = await this.hydrateRelated(memoryId.trim(), options);
|
|
8693
|
+
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
8694
|
+
trace.success({
|
|
8695
|
+
provider: graph.name,
|
|
8696
|
+
memoryIds: [memoryId.trim(), ...related.map((r) => r.id)],
|
|
8697
|
+
returnedCount: related.length
|
|
8698
|
+
});
|
|
8699
|
+
return related;
|
|
8700
|
+
} catch (error) {
|
|
8701
|
+
trace.failure(error);
|
|
8702
|
+
throw wrapOperationError("getRelated", error);
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
7733
8705
|
async forget(options) {
|
|
7734
8706
|
const trace = this.telemetry.start("forget");
|
|
7735
8707
|
try {
|
|
7736
8708
|
const { storage, organization } = await this.requireReady();
|
|
8709
|
+
const deletedIds = [];
|
|
7737
8710
|
const deleted = await this.withWriteLock(async () => {
|
|
7738
8711
|
if ("id" in options && options.id !== void 0) {
|
|
7739
8712
|
assertNonEmptyString(options.id, "id");
|
|
7740
|
-
const
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
);
|
|
8713
|
+
const id = options.id.trim();
|
|
8714
|
+
const ok = await storage.deleteMemoryById(id, organization);
|
|
8715
|
+
if (ok) deletedIds.push(id);
|
|
7744
8716
|
return ok ? 1 : 0;
|
|
7745
8717
|
}
|
|
7746
8718
|
if ("filter" in options && options.filter?.agent) {
|
|
7747
8719
|
assertNonEmptyString(options.filter.agent, "filter.agent");
|
|
8720
|
+
const rows = await storage.listMemories({
|
|
8721
|
+
organization,
|
|
8722
|
+
agent: options.filter.agent.trim(),
|
|
8723
|
+
includeArchived: true
|
|
8724
|
+
});
|
|
8725
|
+
for (const row of rows) deletedIds.push(row.id);
|
|
7748
8726
|
return storage.deleteMemoriesByFilter({
|
|
7749
8727
|
organization,
|
|
7750
8728
|
agent: options.filter.agent.trim(),
|
|
@@ -7755,12 +8733,20 @@ var Wolbarg = class {
|
|
|
7755
8733
|
"forget requires either { id } or { filter: { agent } }"
|
|
7756
8734
|
);
|
|
7757
8735
|
});
|
|
8736
|
+
if (deleted > 0 && this.graph && deletedIds.length > 0) {
|
|
8737
|
+
const tGraph = performance.now();
|
|
8738
|
+
for (const id of deletedIds) {
|
|
8739
|
+
await this.graph.deleteMemory(id);
|
|
8740
|
+
}
|
|
8741
|
+
trace.mark("databaseWriteMs", performance.now() - tGraph);
|
|
8742
|
+
}
|
|
7758
8743
|
trace.success({
|
|
7759
8744
|
provider: storage.name,
|
|
7760
8745
|
returnedCount: deleted,
|
|
7761
|
-
memoryIds:
|
|
8746
|
+
memoryIds: deletedIds.length > 0 ? deletedIds : null,
|
|
7762
8747
|
filters: "filter" in options ? options.filter : null,
|
|
7763
|
-
agentId: "filter" in options ? options.filter?.agent?.trim() ?? null : null
|
|
8748
|
+
agentId: "filter" in options ? options.filter?.agent?.trim() ?? null : null,
|
|
8749
|
+
extra: this.graph ? { graphCascade: deletedIds.length } : void 0
|
|
7764
8750
|
});
|
|
7765
8751
|
if (deleted > 0) {
|
|
7766
8752
|
this.emitChange({
|
|
@@ -7844,6 +8830,15 @@ var Wolbarg = class {
|
|
|
7844
8830
|
);
|
|
7845
8831
|
}
|
|
7846
8832
|
const deleted = await this.withWriteLock(async () => {
|
|
8833
|
+
if (this.graph) {
|
|
8834
|
+
const rows = await storage.listMemories({
|
|
8835
|
+
organization,
|
|
8836
|
+
includeArchived: true
|
|
8837
|
+
});
|
|
8838
|
+
for (const row of rows) {
|
|
8839
|
+
await this.graph.deleteMemory(row.id);
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
7847
8842
|
return storage.clearOrganization(organization);
|
|
7848
8843
|
});
|
|
7849
8844
|
trace.success({ provider: storage.name, returnedCount: deleted });
|
|
@@ -7860,8 +8855,10 @@ var Wolbarg = class {
|
|
|
7860
8855
|
await this.requireReady();
|
|
7861
8856
|
const provider = this.requireCheckpointProvider();
|
|
7862
8857
|
const source = this.requireMemoryDbPath();
|
|
8858
|
+
this.assertGraphCheckpointSupported("checkpoint");
|
|
7863
8859
|
const tCheckpoint = performance.now();
|
|
7864
8860
|
const meta2 = await provider.checkpoint(name, source, options);
|
|
8861
|
+
await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
|
|
7865
8862
|
trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
|
|
7866
8863
|
trace.success({
|
|
7867
8864
|
provider: provider.name,
|
|
@@ -7890,8 +8887,13 @@ var Wolbarg = class {
|
|
|
7890
8887
|
await this.storage.close();
|
|
7891
8888
|
storageClosed = true;
|
|
7892
8889
|
}
|
|
8890
|
+
const graphClosed = await this.closeGraphForSnapshot();
|
|
7893
8891
|
const tRollback = performance.now();
|
|
7894
8892
|
const meta2 = await provider.rollback(name, target);
|
|
8893
|
+
await this.restoreGraphAlongside(meta2.snapshotPath, "rollback");
|
|
8894
|
+
if (graphClosed) {
|
|
8895
|
+
await this.graph?.open();
|
|
8896
|
+
}
|
|
7895
8897
|
trace.mark("databaseWriteMs", performance.now() - tRollback);
|
|
7896
8898
|
await this.storage?.open();
|
|
7897
8899
|
storageClosed = false;
|
|
@@ -7977,6 +8979,7 @@ var Wolbarg = class {
|
|
|
7977
8979
|
try {
|
|
7978
8980
|
await this.requireReady();
|
|
7979
8981
|
const source = this.requireMemoryDbPath();
|
|
8982
|
+
this.assertGraphCheckpointSupported("export");
|
|
7980
8983
|
if (this.storage?.name === "sqlite") {
|
|
7981
8984
|
}
|
|
7982
8985
|
const result = await this.transfer.exportTo(
|
|
@@ -7984,6 +8987,7 @@ var Wolbarg = class {
|
|
|
7984
8987
|
source,
|
|
7985
8988
|
this.organization ?? void 0
|
|
7986
8989
|
);
|
|
8990
|
+
await this.snapshotGraphAlongside(result.path, "export");
|
|
7987
8991
|
trace.success({
|
|
7988
8992
|
provider: "sqlite",
|
|
7989
8993
|
extra: { path: result.path, sizeBytes: result.sizeBytes }
|
|
@@ -8009,10 +9013,16 @@ var Wolbarg = class {
|
|
|
8009
9013
|
await this.storage.close();
|
|
8010
9014
|
storageClosed = true;
|
|
8011
9015
|
}
|
|
9016
|
+
const graphClosed = await this.closeGraphForSnapshot();
|
|
9017
|
+
this.assertGraphCheckpointSupported("import");
|
|
8012
9018
|
const result = await this.transfer.importFrom(
|
|
8013
9019
|
exportPath,
|
|
8014
9020
|
target
|
|
8015
9021
|
);
|
|
9022
|
+
await this.restoreGraphAlongside(result.path, "import");
|
|
9023
|
+
if (graphClosed) {
|
|
9024
|
+
await this.graph?.open();
|
|
9025
|
+
}
|
|
8016
9026
|
await this.storage?.open();
|
|
8017
9027
|
storageClosed = false;
|
|
8018
9028
|
if (this.embeddingDimensions !== null) {
|
|
@@ -8066,6 +9076,8 @@ var Wolbarg = class {
|
|
|
8066
9076
|
this.pgListenBackend = null;
|
|
8067
9077
|
await this.telemetry.close().catch(() => void 0);
|
|
8068
9078
|
await this.checkpointProvider?.close().catch(() => void 0);
|
|
9079
|
+
await this.graph?.close().catch(() => void 0);
|
|
9080
|
+
this.graph = null;
|
|
8069
9081
|
if (this.storage) {
|
|
8070
9082
|
await this.storage.close();
|
|
8071
9083
|
}
|
|
@@ -8111,6 +9123,106 @@ var Wolbarg = class {
|
|
|
8111
9123
|
}
|
|
8112
9124
|
return this.checkpointProvider;
|
|
8113
9125
|
}
|
|
9126
|
+
requireGraph(method) {
|
|
9127
|
+
if (!this.graph) {
|
|
9128
|
+
throw new ProviderNotConfiguredError(
|
|
9129
|
+
"graph",
|
|
9130
|
+
method,
|
|
9131
|
+
"pass graph: sqliteGraph({ path }) or graph: neo4jGraph({ url, username, password })"
|
|
9132
|
+
);
|
|
9133
|
+
}
|
|
9134
|
+
return this.graph;
|
|
9135
|
+
}
|
|
9136
|
+
assertGraphCheckpointSupported(operation) {
|
|
9137
|
+
if (!this.graph) return;
|
|
9138
|
+
if (!this.graph.supportsFileSnapshot()) {
|
|
9139
|
+
throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
|
|
9140
|
+
}
|
|
9141
|
+
}
|
|
9142
|
+
graphSnapshotDir(alongsidePath) {
|
|
9143
|
+
return `${alongsidePath}.graph`;
|
|
9144
|
+
}
|
|
9145
|
+
async closeGraphForSnapshot() {
|
|
9146
|
+
if (!this.graph || !this.graph.supportsFileSnapshot()) return false;
|
|
9147
|
+
await this.graph.close();
|
|
9148
|
+
return true;
|
|
9149
|
+
}
|
|
9150
|
+
async snapshotGraphAlongside(alongsidePath, operation) {
|
|
9151
|
+
if (!this.graph) return;
|
|
9152
|
+
if (!this.graph.supportsFileSnapshot()) {
|
|
9153
|
+
throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
|
|
9154
|
+
}
|
|
9155
|
+
const dataPath = this.graph.getDataPath();
|
|
9156
|
+
if (!dataPath) return;
|
|
9157
|
+
const dest = this.graphSnapshotDir(alongsidePath);
|
|
9158
|
+
await this.graph.close();
|
|
9159
|
+
try {
|
|
9160
|
+
if (fs6.existsSync(dest)) {
|
|
9161
|
+
fs6.rmSync(dest, { recursive: true, force: true });
|
|
9162
|
+
}
|
|
9163
|
+
if (fs6.existsSync(dataPath)) {
|
|
9164
|
+
const stat = fs6.statSync(dataPath);
|
|
9165
|
+
if (stat.isDirectory()) {
|
|
9166
|
+
fs6.cpSync(dataPath, dest, { recursive: true });
|
|
9167
|
+
} else {
|
|
9168
|
+
fs6.mkdirSync(dest, { recursive: true });
|
|
9169
|
+
fs6.cpSync(dataPath, path7.join(dest, path7.basename(dataPath)));
|
|
9170
|
+
const parent = path7.dirname(dataPath);
|
|
9171
|
+
const base = path7.basename(dataPath);
|
|
9172
|
+
for (const entry of fs6.readdirSync(parent)) {
|
|
9173
|
+
if (entry === base) continue;
|
|
9174
|
+
if (entry.startsWith(base)) {
|
|
9175
|
+
fs6.cpSync(
|
|
9176
|
+
path7.join(parent, entry),
|
|
9177
|
+
path7.join(dest, entry),
|
|
9178
|
+
{ recursive: true }
|
|
9179
|
+
);
|
|
9180
|
+
}
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
9183
|
+
}
|
|
9184
|
+
} finally {
|
|
9185
|
+
await this.graph.open();
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
async restoreGraphAlongside(alongsidePath, operation) {
|
|
9189
|
+
if (!this.graph) return;
|
|
9190
|
+
if (!this.graph.supportsFileSnapshot()) {
|
|
9191
|
+
throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
|
|
9192
|
+
}
|
|
9193
|
+
const dataPath = this.graph.getDataPath();
|
|
9194
|
+
if (!dataPath) return;
|
|
9195
|
+
const src = this.graphSnapshotDir(alongsidePath);
|
|
9196
|
+
if (!fs6.existsSync(src)) {
|
|
9197
|
+
return;
|
|
9198
|
+
}
|
|
9199
|
+
if (fs6.existsSync(dataPath)) {
|
|
9200
|
+
fs6.rmSync(dataPath, { recursive: true, force: true });
|
|
9201
|
+
}
|
|
9202
|
+
const entries = fs6.readdirSync(src);
|
|
9203
|
+
if (entries.length === 1 && entries[0] === path7.basename(dataPath)) {
|
|
9204
|
+
fs6.mkdirSync(path7.dirname(dataPath), { recursive: true });
|
|
9205
|
+
fs6.cpSync(path7.join(src, entries[0]), dataPath, { recursive: true });
|
|
9206
|
+
} else {
|
|
9207
|
+
fs6.mkdirSync(path7.dirname(dataPath), { recursive: true });
|
|
9208
|
+
fs6.cpSync(src, dataPath, { recursive: true });
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
async hydrateRelated(memoryId, options) {
|
|
9212
|
+
const graph = this.requireGraph("getRelated");
|
|
9213
|
+
const related = await graph.getRelated(memoryId, options);
|
|
9214
|
+
const { storage, organization } = await this.requireReady();
|
|
9215
|
+
const out = [];
|
|
9216
|
+
for (const stub of related) {
|
|
9217
|
+
const row = await storage.getMemoryById(stub.id, organization);
|
|
9218
|
+
if (row) {
|
|
9219
|
+
out.push(toMemoryRecord(row));
|
|
9220
|
+
} else {
|
|
9221
|
+
out.push(stub);
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
return out;
|
|
9225
|
+
}
|
|
8114
9226
|
requireMemoryDbPath() {
|
|
8115
9227
|
if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
|
|
8116
9228
|
throw new ConfigurationError(
|
|
@@ -8120,7 +9232,7 @@ var Wolbarg = class {
|
|
|
8120
9232
|
}
|
|
8121
9233
|
);
|
|
8122
9234
|
}
|
|
8123
|
-
return
|
|
9235
|
+
return path7.isAbsolute(this.memoryDbPath) ? this.memoryDbPath : path7.resolve(process.cwd(), this.memoryDbPath);
|
|
8124
9236
|
}
|
|
8125
9237
|
};
|
|
8126
9238
|
function wolbarg(options) {
|
|
@@ -8216,10 +9328,16 @@ function sqliteTelemetry(url) {
|
|
|
8216
9328
|
function sqliteCheckpoint(directory) {
|
|
8217
9329
|
return new SqliteCheckpointProvider({ directory });
|
|
8218
9330
|
}
|
|
9331
|
+
function sqliteGraph(options) {
|
|
9332
|
+
return new SqliteGraphProvider(options);
|
|
9333
|
+
}
|
|
9334
|
+
function neo4jGraph(options) {
|
|
9335
|
+
return new Neo4jGraphProvider(options);
|
|
9336
|
+
}
|
|
8219
9337
|
function createTelemetryProvider(config) {
|
|
8220
9338
|
if (config.database.provider !== "sqlite") {
|
|
8221
9339
|
throw new ConfigurationError(
|
|
8222
|
-
`Unsupported telemetry provider "${config.database.provider}". Only "sqlite" is implemented
|
|
9340
|
+
`Unsupported telemetry provider "${config.database.provider}". Only "sqlite" is implemented. Telemetry supports sqlite or postgres only \u2014 not kuzu/neo4j.`
|
|
8223
9341
|
);
|
|
8224
9342
|
}
|
|
8225
9343
|
const url = config.database.url ?? config.database.connectionString ?? "";
|
|
@@ -8676,6 +9794,6 @@ function percentile(sorted, p) {
|
|
|
8676
9794
|
return sorted[idx];
|
|
8677
9795
|
}
|
|
8678
9796
|
|
|
8679
|
-
export { CompressionError, ConfigurationError, DatabaseError, EmbeddingError, FixedChunkingStrategy, HeadingChunkingStrategy, InitializationError, LlmCompressionProvider, MarkdownChunkingStrategy, MemoryNotFoundError, NoopTelemetryProvider, ParagraphChunkingStrategy, PostgresStorageProvider, ProviderNotConfiguredError, SentenceChunkingStrategy, SqliteCheckpointProvider, SqliteDatabaseProvider, SqliteEventDatabase, SqliteStorageProvider, SqliteTelemetryProvider, StorageLockedError, TelemetryEmitter, ValidationError, Wolbarg, WolbargError, WolbargLogger, bgeReranker, bm25, cohereReranker, createChunkingStrategy, createCompressionProvider, createDatabaseProvider, createEmbeddingProvider, createLlmProvider, createStorageProvider, createTelemetryProvider, wolbarg2 as createWolbarg, crossEncoder, geminiEmbedding, geminiVision, jinaReranker, lmStudioEmbedding, meta, ollamaEmbedding, ollamaLlm, openRouterEmbedding, openRouterLlm, openaiCompatibleEmbedding, openaiCompatibleLlm, openaiEmbedding, openaiLlm, openaiReranker, openaiVision, postgres, postgresConfig, runBenchmark, sqlite, sqliteCheckpoint, sqliteConfig, sqliteTelemetry, summarizeBenchmark, tesseract, togetherEmbedding, vllmEmbedding, wolbarg, wrapOperationError };
|
|
9797
|
+
export { CompressionError, ConfigurationError, DatabaseError, EmbeddingError, FixedChunkingStrategy, GraphCheckpointNotSupportedError, HeadingChunkingStrategy, InitializationError, LlmCompressionProvider, MarkdownChunkingStrategy, MemoryNotFoundError, Neo4jGraphProvider, NoopTelemetryProvider, ParagraphChunkingStrategy, PostgresStorageProvider, ProviderNotConfiguredError, SDK_VERSION, SentenceChunkingStrategy, SqliteCheckpointProvider, SqliteDatabaseProvider, SqliteEventDatabase, SqliteGraphProvider, SqliteStorageProvider, SqliteTelemetryProvider, StorageLockedError, TelemetryEmitter, ValidationError, Wolbarg, WolbargError, WolbargLogger, bgeReranker, bm25, cohereReranker, createChunkingStrategy, createCompressionProvider, createDatabaseProvider, createEmbeddingProvider, createLlmProvider, createStorageProvider, createTelemetryProvider, wolbarg2 as createWolbarg, crossEncoder, geminiEmbedding, geminiVision, jinaReranker, lmStudioEmbedding, meta, neo4jGraph, ollamaEmbedding, ollamaLlm, openRouterEmbedding, openRouterLlm, openaiCompatibleEmbedding, openaiCompatibleLlm, openaiEmbedding, openaiLlm, openaiReranker, openaiVision, postgres, postgresConfig, runBenchmark, sqlite, sqliteCheckpoint, sqliteConfig, sqliteGraph, sqliteTelemetry, summarizeBenchmark, tesseract, togetherEmbedding, vllmEmbedding, wolbarg, wrapOperationError };
|
|
8680
9798
|
//# sourceMappingURL=index.js.map
|
|
8681
9799
|
//# sourceMappingURL=index.js.map
|