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.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var fs6 = require("node:fs");
|
|
4
|
+
var path7 = require("node:path");
|
|
4
5
|
var crypto$1 = require('crypto');
|
|
5
6
|
var fs = require('fs/promises');
|
|
6
|
-
var fs5 = require("node:fs");
|
|
7
7
|
var sqlite$1 = require("node:sqlite");
|
|
8
8
|
var sqliteVec = require('sqlite-vec');
|
|
9
9
|
var async_hooks = require('async_hooks');
|
|
@@ -29,9 +29,9 @@ function _interopNamespace(e) {
|
|
|
29
29
|
return Object.freeze(n);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
var
|
|
32
|
+
var fs6__default = /*#__PURE__*/_interopDefault(fs6);
|
|
33
|
+
var path7__default = /*#__PURE__*/_interopDefault(path7);
|
|
33
34
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
34
|
-
var fs5__default = /*#__PURE__*/_interopDefault(fs5);
|
|
35
35
|
var sqliteVec__namespace = /*#__PURE__*/_interopNamespace(sqliteVec);
|
|
36
36
|
|
|
37
37
|
// src/core/wolbarg.ts
|
|
@@ -112,6 +112,20 @@ var ProviderNotConfiguredError = class extends ConfigurationError {
|
|
|
112
112
|
this.provider = provider;
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
|
+
var GraphCheckpointNotSupportedError = class extends WolbargError {
|
|
116
|
+
constructor(backend, operation) {
|
|
117
|
+
super(
|
|
118
|
+
`graph checkpoint not supported for network-backed graph providers (${backend})`,
|
|
119
|
+
"GRAPH_CHECKPOINT_NOT_SUPPORTED",
|
|
120
|
+
{
|
|
121
|
+
operation,
|
|
122
|
+
reason: `${backend} is networked / not file-backed`,
|
|
123
|
+
suggestion: "Use sqliteGraph({ path }) for local snapshots, or checkpoint memory storage only without a network graph provider."
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
this.name = "GraphCheckpointNotSupportedError";
|
|
127
|
+
}
|
|
128
|
+
};
|
|
115
129
|
function wrapOperationError(operation, error) {
|
|
116
130
|
if (error instanceof WolbargError) {
|
|
117
131
|
return error;
|
|
@@ -412,9 +426,9 @@ var AsyncMutex = class {
|
|
|
412
426
|
}
|
|
413
427
|
}
|
|
414
428
|
};
|
|
415
|
-
function joinUrl(baseUrl,
|
|
429
|
+
function joinUrl(baseUrl, path8) {
|
|
416
430
|
const base = baseUrl.replace(/\/+$/, "");
|
|
417
|
-
const suffix =
|
|
431
|
+
const suffix = path8.startsWith("/") ? path8 : `/${path8}`;
|
|
418
432
|
return `${base}${suffix}`;
|
|
419
433
|
}
|
|
420
434
|
|
|
@@ -1109,7 +1123,7 @@ function extOf(filename) {
|
|
|
1109
1123
|
if (!filename) {
|
|
1110
1124
|
return "";
|
|
1111
1125
|
}
|
|
1112
|
-
return
|
|
1126
|
+
return path7__default.default.extname(filename).toLowerCase();
|
|
1113
1127
|
}
|
|
1114
1128
|
var TextParser = class {
|
|
1115
1129
|
name = "text";
|
|
@@ -1254,7 +1268,7 @@ async function loadIngestSource(source) {
|
|
|
1254
1268
|
const buffer = await fs__default.default.readFile(source.path);
|
|
1255
1269
|
return {
|
|
1256
1270
|
buffer,
|
|
1257
|
-
filename:
|
|
1271
|
+
filename: path7__default.default.basename(source.path),
|
|
1258
1272
|
mimeType: source.mimeType
|
|
1259
1273
|
};
|
|
1260
1274
|
}
|
|
@@ -1274,6 +1288,9 @@ function isStorageProvider(value) {
|
|
|
1274
1288
|
function isTelemetryProvider(value) {
|
|
1275
1289
|
return typeof value.emit === "function";
|
|
1276
1290
|
}
|
|
1291
|
+
function isGraphProvider(value) {
|
|
1292
|
+
return typeof value.open === "function" && typeof value.linkMemories === "function" && typeof value.getRelated === "function";
|
|
1293
|
+
}
|
|
1277
1294
|
function resolveDatabaseUrl(config) {
|
|
1278
1295
|
const url = "url" in config && config.url || "connectionString" in config && config.connectionString || "";
|
|
1279
1296
|
return typeof url === "string" ? url : "";
|
|
@@ -1344,11 +1361,11 @@ function jsonPath(field) {
|
|
|
1344
1361
|
return `$.${field}`;
|
|
1345
1362
|
}
|
|
1346
1363
|
function compileComparison(field, op) {
|
|
1347
|
-
const
|
|
1348
|
-
if (!
|
|
1364
|
+
const path8 = jsonPath(field);
|
|
1365
|
+
if (!path8) {
|
|
1349
1366
|
return null;
|
|
1350
1367
|
}
|
|
1351
|
-
const extract = `json_extract(metadata_json, '${
|
|
1368
|
+
const extract = `json_extract(metadata_json, '${path8}')`;
|
|
1352
1369
|
if ("eq" in op) {
|
|
1353
1370
|
const value = op.eq;
|
|
1354
1371
|
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
@@ -1493,6 +1510,40 @@ CREATE TABLE IF NOT EXISTS embedding_cache (
|
|
|
1493
1510
|
last_used_at TEXT NOT NULL
|
|
1494
1511
|
);
|
|
1495
1512
|
`;
|
|
1513
|
+
var CREATE_GRAPH_NODES_TABLE = `
|
|
1514
|
+
CREATE TABLE IF NOT EXISTS graph_nodes (
|
|
1515
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
1516
|
+
type TEXT NOT NULL CHECK (type IN ('memory', 'entity')),
|
|
1517
|
+
ref_id TEXT NOT NULL,
|
|
1518
|
+
name TEXT NULL,
|
|
1519
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1520
|
+
created_at TEXT NOT NULL
|
|
1521
|
+
);
|
|
1522
|
+
`;
|
|
1523
|
+
var CREATE_GRAPH_EDGES_TABLE = `
|
|
1524
|
+
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
1525
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
1526
|
+
from_node_id TEXT NOT NULL,
|
|
1527
|
+
to_node_id TEXT NOT NULL,
|
|
1528
|
+
relation TEXT NOT NULL,
|
|
1529
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1530
|
+
created_at TEXT NOT NULL,
|
|
1531
|
+
FOREIGN KEY (from_node_id) REFERENCES graph_nodes(id) ON DELETE CASCADE,
|
|
1532
|
+
FOREIGN KEY (to_node_id) REFERENCES graph_nodes(id) ON DELETE CASCADE
|
|
1533
|
+
);
|
|
1534
|
+
`;
|
|
1535
|
+
var CREATE_GRAPH_INDEXES = [
|
|
1536
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_nodes_type_ref
|
|
1537
|
+
ON graph_nodes(type, ref_id);`,
|
|
1538
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_from
|
|
1539
|
+
ON graph_edges(from_node_id);`,
|
|
1540
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_to
|
|
1541
|
+
ON graph_edges(to_node_id);`,
|
|
1542
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_from_rel
|
|
1543
|
+
ON graph_edges(from_node_id, relation);`,
|
|
1544
|
+
`CREATE INDEX IF NOT EXISTS idx_graph_edges_to_rel
|
|
1545
|
+
ON graph_edges(to_node_id, relation);`
|
|
1546
|
+
];
|
|
1496
1547
|
var CREATE_INDEXES = [
|
|
1497
1548
|
`CREATE INDEX IF NOT EXISTS idx_memories_org_agent ON memories(organization, agent);`,
|
|
1498
1549
|
`CREATE INDEX IF NOT EXISTS idx_memories_org_archived ON memories(organization, archived);`,
|
|
@@ -2093,7 +2144,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2093
2144
|
const dbPath = this.resolvePath(this.connectionString);
|
|
2094
2145
|
this.resolvedPath = dbPath;
|
|
2095
2146
|
if (dbPath !== ":memory:") {
|
|
2096
|
-
|
|
2147
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(dbPath), { recursive: true });
|
|
2097
2148
|
}
|
|
2098
2149
|
const db = new sqlite$1.DatabaseSync(dbPath, { allowExtension: true });
|
|
2099
2150
|
this.db = db;
|
|
@@ -2581,11 +2632,11 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
2581
2632
|
}
|
|
2582
2633
|
const dbPath = this.resolvedPath ?? this.resolvePath(this.connectionString);
|
|
2583
2634
|
try {
|
|
2584
|
-
let total =
|
|
2635
|
+
let total = fs6__default.default.statSync(dbPath).size;
|
|
2585
2636
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2586
2637
|
const side = `${dbPath}${suffix}`;
|
|
2587
|
-
if (
|
|
2588
|
-
total +=
|
|
2638
|
+
if (fs6__default.default.existsSync(side)) {
|
|
2639
|
+
total += fs6__default.default.statSync(side).size;
|
|
2589
2640
|
}
|
|
2590
2641
|
}
|
|
2591
2642
|
return total;
|
|
@@ -3361,7 +3412,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
|
|
|
3361
3412
|
if (connectionString === ":memory:") {
|
|
3362
3413
|
return ":memory:";
|
|
3363
3414
|
}
|
|
3364
|
-
return
|
|
3415
|
+
return path7__default.default.isAbsolute(connectionString) ? connectionString : path7__default.default.resolve(process.cwd(), connectionString);
|
|
3365
3416
|
}
|
|
3366
3417
|
requireDb() {
|
|
3367
3418
|
if (!this.db) {
|
|
@@ -3412,8 +3463,8 @@ function jsonbTextExtract(field) {
|
|
|
3412
3463
|
if (parts.length === 1) {
|
|
3413
3464
|
return `metadata_json->>'${parts[0]}'`;
|
|
3414
3465
|
}
|
|
3415
|
-
const
|
|
3416
|
-
return `metadata_json #>> '{${
|
|
3466
|
+
const path8 = parts.join(",");
|
|
3467
|
+
return `metadata_json #>> '{${path8}}'`;
|
|
3417
3468
|
}
|
|
3418
3469
|
function jsonbContainment(field, value, paramIndex) {
|
|
3419
3470
|
if (!FIELD_RE2.test(field)) {
|
|
@@ -5112,7 +5163,11 @@ function createStorageProvider(config, options) {
|
|
|
5112
5163
|
function createDatabaseProvider(config) {
|
|
5113
5164
|
return createStorageProvider(config);
|
|
5114
5165
|
}
|
|
5115
|
-
|
|
5166
|
+
|
|
5167
|
+
// src/version.ts
|
|
5168
|
+
var SDK_VERSION = "0.5.0";
|
|
5169
|
+
|
|
5170
|
+
// src/memory/transfer.ts
|
|
5116
5171
|
var SqliteMemoryTransferProvider = class {
|
|
5117
5172
|
async exportTo(exportPath, sourcePath, organization) {
|
|
5118
5173
|
const resolvedSource = resolvePath(sourcePath);
|
|
@@ -5121,7 +5176,7 @@ var SqliteMemoryTransferProvider = class {
|
|
|
5121
5176
|
"Cannot export an in-memory database. Use a file-backed SQLite database."
|
|
5122
5177
|
);
|
|
5123
5178
|
}
|
|
5124
|
-
if (!
|
|
5179
|
+
if (!fs6__default.default.existsSync(resolvedSource)) {
|
|
5125
5180
|
throw new DatabaseError(
|
|
5126
5181
|
`Failed to execute export()
|
|
5127
5182
|
Reason:
|
|
@@ -5131,7 +5186,7 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5131
5186
|
);
|
|
5132
5187
|
}
|
|
5133
5188
|
const resolvedExport = resolvePath(exportPath);
|
|
5134
|
-
|
|
5189
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(resolvedExport), { recursive: true });
|
|
5135
5190
|
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : `${resolvedExport}.db`;
|
|
5136
5191
|
const manifestPath = `${dbExportPath}.manifest.json`;
|
|
5137
5192
|
await copySqlite(resolvedSource, dbExportPath);
|
|
@@ -5143,17 +5198,17 @@ Initialize Wolbarg and verify the database path.`
|
|
|
5143
5198
|
sourcePath: resolvedSource,
|
|
5144
5199
|
organization
|
|
5145
5200
|
};
|
|
5146
|
-
|
|
5201
|
+
fs6__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
5147
5202
|
return {
|
|
5148
5203
|
path: dbExportPath,
|
|
5149
5204
|
manifest,
|
|
5150
|
-
sizeBytes:
|
|
5205
|
+
sizeBytes: fs6__default.default.statSync(dbExportPath).size
|
|
5151
5206
|
};
|
|
5152
5207
|
}
|
|
5153
5208
|
async importFrom(exportPath, targetPath) {
|
|
5154
5209
|
const resolvedExport = resolvePath(exportPath);
|
|
5155
|
-
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport :
|
|
5156
|
-
if (!
|
|
5210
|
+
const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6__default.default.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
|
|
5211
|
+
if (!fs6__default.default.existsSync(dbExportPath)) {
|
|
5157
5212
|
throw new ValidationError(
|
|
5158
5213
|
`Failed to execute import()
|
|
5159
5214
|
Reason:
|
|
@@ -5164,8 +5219,8 @@ Pass the path returned by export().`
|
|
|
5164
5219
|
}
|
|
5165
5220
|
const manifestPath = `${dbExportPath}.manifest.json`;
|
|
5166
5221
|
let manifest;
|
|
5167
|
-
if (
|
|
5168
|
-
manifest = JSON.parse(
|
|
5222
|
+
if (fs6__default.default.existsSync(manifestPath)) {
|
|
5223
|
+
manifest = JSON.parse(fs6__default.default.readFileSync(manifestPath, "utf8"));
|
|
5169
5224
|
if (manifest.format !== "wolbarg-export-v1") {
|
|
5170
5225
|
throw new ValidationError(
|
|
5171
5226
|
`Unsupported export format: ${String(manifest.format)}`
|
|
@@ -5186,8 +5241,8 @@ Pass the path returned by export().`
|
|
|
5186
5241
|
}
|
|
5187
5242
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
5188
5243
|
const side = `${resolvedTarget}${suffix}`;
|
|
5189
|
-
if (
|
|
5190
|
-
|
|
5244
|
+
if (fs6__default.default.existsSync(side)) {
|
|
5245
|
+
fs6__default.default.rmSync(side, { force: true });
|
|
5191
5246
|
}
|
|
5192
5247
|
}
|
|
5193
5248
|
await copySqlite(dbExportPath, resolvedTarget);
|
|
@@ -5195,7 +5250,7 @@ Pass the path returned by export().`
|
|
|
5195
5250
|
}
|
|
5196
5251
|
};
|
|
5197
5252
|
async function copySqlite(sourcePath, destPath) {
|
|
5198
|
-
|
|
5253
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(destPath), { recursive: true });
|
|
5199
5254
|
let source = null;
|
|
5200
5255
|
let dest = null;
|
|
5201
5256
|
try {
|
|
@@ -5213,7 +5268,7 @@ async function copySqlite(sourcePath, destPath) {
|
|
|
5213
5268
|
source = null;
|
|
5214
5269
|
dest.close();
|
|
5215
5270
|
dest = null;
|
|
5216
|
-
|
|
5271
|
+
fs6__default.default.copyFileSync(sourcePath, destPath);
|
|
5217
5272
|
}
|
|
5218
5273
|
} catch (error) {
|
|
5219
5274
|
throw new DatabaseError(
|
|
@@ -5233,7 +5288,7 @@ async function copySqlite(sourcePath, destPath) {
|
|
|
5233
5288
|
}
|
|
5234
5289
|
function resolvePath(p) {
|
|
5235
5290
|
if (p === ":memory:") return ":memory:";
|
|
5236
|
-
return
|
|
5291
|
+
return path7__default.default.isAbsolute(p) ? p : path7__default.default.resolve(process.cwd(), p);
|
|
5237
5292
|
}
|
|
5238
5293
|
|
|
5239
5294
|
// src/memory/index.ts
|
|
@@ -5360,6 +5415,723 @@ function adaptiveFetchK(topK, overFetchFactor, hasFilters) {
|
|
|
5360
5415
|
return Math.min(Math.max(Math.ceil(topK * factor), topK), 1e3);
|
|
5361
5416
|
}
|
|
5362
5417
|
|
|
5418
|
+
// src/graph/memory-node.ts
|
|
5419
|
+
function stubMemoryRecord(id) {
|
|
5420
|
+
return {
|
|
5421
|
+
id,
|
|
5422
|
+
organization: "",
|
|
5423
|
+
agent: "",
|
|
5424
|
+
content: { text: "" },
|
|
5425
|
+
metadata: {},
|
|
5426
|
+
archived: false,
|
|
5427
|
+
compressedInto: null,
|
|
5428
|
+
createdAt: /* @__PURE__ */ new Date(0),
|
|
5429
|
+
updatedAt: /* @__PURE__ */ new Date(0)
|
|
5430
|
+
};
|
|
5431
|
+
}
|
|
5432
|
+
function serializeMetadata2(meta2) {
|
|
5433
|
+
return JSON.stringify(meta2 ?? {});
|
|
5434
|
+
}
|
|
5435
|
+
function deserializeMetadata2(raw) {
|
|
5436
|
+
if (raw == null || raw === "") return {};
|
|
5437
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
5438
|
+
return raw;
|
|
5439
|
+
}
|
|
5440
|
+
if (typeof raw !== "string") return {};
|
|
5441
|
+
try {
|
|
5442
|
+
const parsed = JSON.parse(raw);
|
|
5443
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
5444
|
+
return parsed;
|
|
5445
|
+
}
|
|
5446
|
+
} catch {
|
|
5447
|
+
}
|
|
5448
|
+
return {};
|
|
5449
|
+
}
|
|
5450
|
+
function rowToMemoryRecord(row) {
|
|
5451
|
+
const id = String(row.id ?? row.n_id ?? "");
|
|
5452
|
+
const createdAt = parseDate(row.created_at ?? row.createdAt);
|
|
5453
|
+
const updatedAt = parseDate(row.updated_at ?? row.updatedAt);
|
|
5454
|
+
return {
|
|
5455
|
+
id,
|
|
5456
|
+
organization: String(row.organization ?? ""),
|
|
5457
|
+
agent: String(row.agent ?? ""),
|
|
5458
|
+
content: { text: String(row.content_text ?? row.contentText ?? "") },
|
|
5459
|
+
metadata: deserializeMetadata2(row.metadata_json ?? row.metadata),
|
|
5460
|
+
archived: Boolean(row.archived),
|
|
5461
|
+
compressedInto: row.compressed_into == null || row.compressed_into === "" ? null : String(row.compressed_into),
|
|
5462
|
+
createdAt,
|
|
5463
|
+
updatedAt
|
|
5464
|
+
};
|
|
5465
|
+
}
|
|
5466
|
+
function parseDate(value) {
|
|
5467
|
+
if (value instanceof Date) return value;
|
|
5468
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
5469
|
+
const d = new Date(value);
|
|
5470
|
+
if (!Number.isNaN(d.getTime())) return d;
|
|
5471
|
+
}
|
|
5472
|
+
return /* @__PURE__ */ new Date(0);
|
|
5473
|
+
}
|
|
5474
|
+
function entityIdFrom(name, type) {
|
|
5475
|
+
const key = `${type.trim().toLowerCase()}::${name.trim().toLowerCase()}`;
|
|
5476
|
+
let h = 2166136261;
|
|
5477
|
+
for (let i = 0; i < key.length; i += 1) {
|
|
5478
|
+
h ^= key.charCodeAt(i);
|
|
5479
|
+
h = Math.imul(h, 16777619);
|
|
5480
|
+
}
|
|
5481
|
+
const a = (h >>> 0).toString(16).padStart(8, "0");
|
|
5482
|
+
let h2 = 5381;
|
|
5483
|
+
for (let i = 0; i < key.length; i += 1) {
|
|
5484
|
+
h2 = h2 * 33 ^ key.charCodeAt(i);
|
|
5485
|
+
}
|
|
5486
|
+
const b = (h2 >>> 0).toString(16).padStart(8, "0");
|
|
5487
|
+
return `ent_${a}${b}`;
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5490
|
+
// src/graph/sync/cascade.ts
|
|
5491
|
+
var ENTITY_MENTIONS_RELATION = "MENTIONS";
|
|
5492
|
+
function cascadeDeleteMemoryNode(db, memoryId) {
|
|
5493
|
+
const node = db.prepare(
|
|
5494
|
+
`SELECT id FROM graph_nodes WHERE type = 'memory' AND ref_id = ?`
|
|
5495
|
+
).get(memoryId);
|
|
5496
|
+
if (!node) return;
|
|
5497
|
+
db.prepare(
|
|
5498
|
+
`DELETE FROM graph_edges WHERE from_node_id = ? OR to_node_id = ?`
|
|
5499
|
+
).run(node.id, node.id);
|
|
5500
|
+
db.prepare(`DELETE FROM graph_nodes WHERE id = ?`).run(node.id);
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
// src/graph/providers/sqlite-graph.ts
|
|
5504
|
+
var DEFAULT_GET_RELATED_DEPTH = 1;
|
|
5505
|
+
var MAX_GET_RELATED_DEPTH = 16;
|
|
5506
|
+
var SqliteGraphProvider = class {
|
|
5507
|
+
name = "sqlite";
|
|
5508
|
+
dbPath;
|
|
5509
|
+
concurrency;
|
|
5510
|
+
db = null;
|
|
5511
|
+
opened = false;
|
|
5512
|
+
constructor(options) {
|
|
5513
|
+
if (!options?.path || typeof options.path !== "string" || !options.path.trim()) {
|
|
5514
|
+
throw new ConfigurationError("sqlite graph requires a non-empty path");
|
|
5515
|
+
}
|
|
5516
|
+
this.dbPath = path7__default.default.resolve(options.path.trim());
|
|
5517
|
+
this.concurrency = resolveConcurrencyConfig(options.concurrency);
|
|
5518
|
+
}
|
|
5519
|
+
supportsFileSnapshot() {
|
|
5520
|
+
return this.dbPath !== ":memory:";
|
|
5521
|
+
}
|
|
5522
|
+
getDataPath() {
|
|
5523
|
+
return this.dbPath === ":memory:" ? null : this.dbPath;
|
|
5524
|
+
}
|
|
5525
|
+
async open() {
|
|
5526
|
+
if (this.opened) return;
|
|
5527
|
+
try {
|
|
5528
|
+
if (this.dbPath !== ":memory:") {
|
|
5529
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(this.dbPath), { recursive: true });
|
|
5530
|
+
}
|
|
5531
|
+
const db = new sqlite$1.DatabaseSync(this.dbPath);
|
|
5532
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
5533
|
+
db.exec(`
|
|
5534
|
+
PRAGMA synchronous = NORMAL;
|
|
5535
|
+
PRAGMA foreign_keys = ON;
|
|
5536
|
+
PRAGMA busy_timeout = ${this.concurrency.lockTimeoutMs};
|
|
5537
|
+
PRAGMA temp_store = MEMORY;
|
|
5538
|
+
PRAGMA cache_size = -8192;
|
|
5539
|
+
PRAGMA wal_autocheckpoint = 1000;
|
|
5540
|
+
`);
|
|
5541
|
+
db.exec(CREATE_GRAPH_NODES_TABLE);
|
|
5542
|
+
db.exec(CREATE_GRAPH_EDGES_TABLE);
|
|
5543
|
+
for (const indexSql of CREATE_GRAPH_INDEXES) {
|
|
5544
|
+
db.exec(indexSql);
|
|
5545
|
+
}
|
|
5546
|
+
this.db = db;
|
|
5547
|
+
this.opened = true;
|
|
5548
|
+
} catch (error) {
|
|
5549
|
+
try {
|
|
5550
|
+
this.db?.close();
|
|
5551
|
+
} catch {
|
|
5552
|
+
}
|
|
5553
|
+
this.db = null;
|
|
5554
|
+
this.opened = false;
|
|
5555
|
+
throw new DatabaseError(
|
|
5556
|
+
`Failed to open SQLite graph database: ${describe(error)}`,
|
|
5557
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
5558
|
+
);
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
async close() {
|
|
5562
|
+
if (!this.db) {
|
|
5563
|
+
this.opened = false;
|
|
5564
|
+
return;
|
|
5565
|
+
}
|
|
5566
|
+
try {
|
|
5567
|
+
try {
|
|
5568
|
+
this.db.exec("PRAGMA optimize;");
|
|
5569
|
+
} catch {
|
|
5570
|
+
}
|
|
5571
|
+
this.db.close();
|
|
5572
|
+
} catch (error) {
|
|
5573
|
+
throw new DatabaseError(
|
|
5574
|
+
`Failed to close SQLite graph database: ${describe(error)}`,
|
|
5575
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
5576
|
+
);
|
|
5577
|
+
} finally {
|
|
5578
|
+
this.db = null;
|
|
5579
|
+
this.opened = false;
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
async linkMemories(fromId, toId, relation, metadata) {
|
|
5583
|
+
const db = this.requireDb();
|
|
5584
|
+
await this.withTx(() => {
|
|
5585
|
+
const fromNode = this.ensureMemoryNodeSync(db, fromId);
|
|
5586
|
+
const toNode = this.ensureMemoryNodeSync(db, toId);
|
|
5587
|
+
db.prepare(
|
|
5588
|
+
`DELETE FROM graph_edges
|
|
5589
|
+
WHERE from_node_id = ? AND to_node_id = ? AND relation = ?`
|
|
5590
|
+
).run(fromNode, toNode, relation);
|
|
5591
|
+
db.prepare(
|
|
5592
|
+
`INSERT INTO graph_edges (id, from_node_id, to_node_id, relation, metadata, created_at)
|
|
5593
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
5594
|
+
).run(
|
|
5595
|
+
crypto.randomUUID(),
|
|
5596
|
+
fromNode,
|
|
5597
|
+
toNode,
|
|
5598
|
+
relation,
|
|
5599
|
+
serializeMetadata2(metadata),
|
|
5600
|
+
nowIso2()
|
|
5601
|
+
);
|
|
5602
|
+
});
|
|
5603
|
+
}
|
|
5604
|
+
async unlinkMemories(fromId, toId, relation) {
|
|
5605
|
+
const db = this.requireDb();
|
|
5606
|
+
await this.withTx(() => {
|
|
5607
|
+
const fromNode = this.findMemoryNodeId(db, fromId);
|
|
5608
|
+
const toNode = this.findMemoryNodeId(db, toId);
|
|
5609
|
+
if (!fromNode || !toNode) return;
|
|
5610
|
+
if (relation !== void 0) {
|
|
5611
|
+
db.prepare(
|
|
5612
|
+
`DELETE FROM graph_edges
|
|
5613
|
+
WHERE from_node_id = ? AND to_node_id = ? AND relation = ?`
|
|
5614
|
+
).run(fromNode, toNode, relation);
|
|
5615
|
+
return;
|
|
5616
|
+
}
|
|
5617
|
+
db.prepare(
|
|
5618
|
+
`DELETE FROM graph_edges WHERE from_node_id = ? AND to_node_id = ?`
|
|
5619
|
+
).run(fromNode, toNode);
|
|
5620
|
+
});
|
|
5621
|
+
}
|
|
5622
|
+
/**
|
|
5623
|
+
* Bounded recursive CTE over `graph_edges`.
|
|
5624
|
+
*
|
|
5625
|
+
* Default depth: {@link DEFAULT_GET_RELATED_DEPTH} (1). Cap: 16.
|
|
5626
|
+
* Only memory↔memory edges are walked (entity MENTIONS edges are excluded).
|
|
5627
|
+
* Cycle-safe via path membership (`instr`) so A→B→C→A terminates.
|
|
5628
|
+
*
|
|
5629
|
+
* Returns stub {@link MemoryRecord}s (id + empty fields). The facade
|
|
5630
|
+
* re-hydrates full rows via `toMemoryRecord` from storage when possible.
|
|
5631
|
+
*/
|
|
5632
|
+
async getRelated(memoryId, options) {
|
|
5633
|
+
const db = this.requireDb();
|
|
5634
|
+
const depth = Math.max(
|
|
5635
|
+
1,
|
|
5636
|
+
Math.min(options?.depth ?? DEFAULT_GET_RELATED_DEPTH, MAX_GET_RELATED_DEPTH)
|
|
5637
|
+
);
|
|
5638
|
+
const direction = options?.direction ?? "both";
|
|
5639
|
+
const relation = options?.relation ?? null;
|
|
5640
|
+
const start = this.findMemoryNodeId(db, memoryId);
|
|
5641
|
+
if (!start) return [];
|
|
5642
|
+
const sql = buildGetRelatedSql(direction);
|
|
5643
|
+
const rows = db.prepare(sql).all(start, start, depth, relation, relation);
|
|
5644
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5645
|
+
const out = [];
|
|
5646
|
+
for (const row of rows) {
|
|
5647
|
+
if (row.ref_id === memoryId || seen.has(row.ref_id)) continue;
|
|
5648
|
+
seen.add(row.ref_id);
|
|
5649
|
+
out.push(stubMemoryRecord(row.ref_id));
|
|
5650
|
+
}
|
|
5651
|
+
return out;
|
|
5652
|
+
}
|
|
5653
|
+
async upsertEntity(entity) {
|
|
5654
|
+
const id = entityIdFrom(entity.name, entity.type);
|
|
5655
|
+
const db = this.requireDb();
|
|
5656
|
+
const meta2 = serializeMetadata2({
|
|
5657
|
+
...entity.metadata ?? {},
|
|
5658
|
+
entityType: entity.type
|
|
5659
|
+
});
|
|
5660
|
+
await this.withTx(() => {
|
|
5661
|
+
const existing = db.prepare(
|
|
5662
|
+
`SELECT id FROM graph_nodes WHERE type = 'entity' AND ref_id = ?`
|
|
5663
|
+
).get(id);
|
|
5664
|
+
if (existing) {
|
|
5665
|
+
db.prepare(
|
|
5666
|
+
`UPDATE graph_nodes SET name = ?, metadata = ? WHERE id = ?`
|
|
5667
|
+
).run(entity.name, meta2, existing.id);
|
|
5668
|
+
} else {
|
|
5669
|
+
db.prepare(
|
|
5670
|
+
`INSERT INTO graph_nodes (id, type, ref_id, name, metadata, created_at)
|
|
5671
|
+
VALUES (?, 'entity', ?, ?, ?, ?)`
|
|
5672
|
+
).run(id, id, entity.name, meta2, nowIso2());
|
|
5673
|
+
}
|
|
5674
|
+
});
|
|
5675
|
+
return id;
|
|
5676
|
+
}
|
|
5677
|
+
async linkEntityToMemory(entityId, memoryId, role) {
|
|
5678
|
+
const db = this.requireDb();
|
|
5679
|
+
await this.withTx(() => {
|
|
5680
|
+
const entity = db.prepare(
|
|
5681
|
+
`SELECT id FROM graph_nodes WHERE type = 'entity' AND ref_id = ?`
|
|
5682
|
+
).get(entityId);
|
|
5683
|
+
if (!entity) {
|
|
5684
|
+
throw new DatabaseError(`Entity not found: ${entityId}`, {
|
|
5685
|
+
operation: "linkEntityToMemory"
|
|
5686
|
+
});
|
|
5687
|
+
}
|
|
5688
|
+
const memoryNode = this.ensureMemoryNodeSync(db, memoryId);
|
|
5689
|
+
db.prepare(
|
|
5690
|
+
`DELETE FROM graph_edges
|
|
5691
|
+
WHERE from_node_id = ? AND to_node_id = ? AND relation = ?`
|
|
5692
|
+
).run(entity.id, memoryNode, ENTITY_MENTIONS_RELATION);
|
|
5693
|
+
db.prepare(
|
|
5694
|
+
`INSERT INTO graph_edges (id, from_node_id, to_node_id, relation, metadata, created_at)
|
|
5695
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
5696
|
+
).run(
|
|
5697
|
+
crypto.randomUUID(),
|
|
5698
|
+
entity.id,
|
|
5699
|
+
memoryNode,
|
|
5700
|
+
ENTITY_MENTIONS_RELATION,
|
|
5701
|
+
serializeMetadata2({ role: role ?? "" }),
|
|
5702
|
+
nowIso2()
|
|
5703
|
+
);
|
|
5704
|
+
});
|
|
5705
|
+
}
|
|
5706
|
+
async deleteMemory(memoryId) {
|
|
5707
|
+
const db = this.requireDb();
|
|
5708
|
+
await this.withTx(() => {
|
|
5709
|
+
cascadeDeleteMemoryNode(db, memoryId);
|
|
5710
|
+
});
|
|
5711
|
+
}
|
|
5712
|
+
/**
|
|
5713
|
+
* Raw Cypher escape hatch — **not supported** on the SQLite graph provider.
|
|
5714
|
+
* Use the typed methods, or open the underlying SQLite file yourself for raw SQL.
|
|
5715
|
+
*/
|
|
5716
|
+
async query(_cypher, _params) {
|
|
5717
|
+
throw new DatabaseError(
|
|
5718
|
+
"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",
|
|
5719
|
+
{
|
|
5720
|
+
operation: "query",
|
|
5721
|
+
suggestion: "Use linkMemories / getRelated / upsertEntity / linkEntityToMemory, or open the graph .db file with node:sqlite for ad-hoc SQL"
|
|
5722
|
+
}
|
|
5723
|
+
);
|
|
5724
|
+
}
|
|
5725
|
+
async health() {
|
|
5726
|
+
try {
|
|
5727
|
+
if (!this.opened || !this.db) {
|
|
5728
|
+
return { ok: false, backend: "sqlite", details: { reason: "not open" } };
|
|
5729
|
+
}
|
|
5730
|
+
const nodeCount = Number(
|
|
5731
|
+
this.db.prepare(`SELECT COUNT(*) AS c FROM graph_nodes`).get().c
|
|
5732
|
+
);
|
|
5733
|
+
const edgeCount = Number(
|
|
5734
|
+
this.db.prepare(`SELECT COUNT(*) AS c FROM graph_edges`).get().c
|
|
5735
|
+
);
|
|
5736
|
+
return {
|
|
5737
|
+
ok: true,
|
|
5738
|
+
backend: "sqlite",
|
|
5739
|
+
details: {
|
|
5740
|
+
path: this.dbPath,
|
|
5741
|
+
embedded: true,
|
|
5742
|
+
nodeCount,
|
|
5743
|
+
edgeCount,
|
|
5744
|
+
exists: this.dbPath === ":memory:" || fs6__default.default.existsSync(this.dbPath)
|
|
5745
|
+
}
|
|
5746
|
+
};
|
|
5747
|
+
} catch (error) {
|
|
5748
|
+
return {
|
|
5749
|
+
ok: false,
|
|
5750
|
+
backend: "sqlite",
|
|
5751
|
+
details: {
|
|
5752
|
+
path: this.dbPath,
|
|
5753
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5754
|
+
}
|
|
5755
|
+
};
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
async withTx(fn) {
|
|
5759
|
+
const db = this.requireDb();
|
|
5760
|
+
return withImmediateTransaction(db, this.concurrency, fn);
|
|
5761
|
+
}
|
|
5762
|
+
requireDb() {
|
|
5763
|
+
if (!this.db || !this.opened) {
|
|
5764
|
+
throw new DatabaseError("SQLite graph is not open", {
|
|
5765
|
+
operation: "graph",
|
|
5766
|
+
suggestion: "Call ready() / open() before graph operations"
|
|
5767
|
+
});
|
|
5768
|
+
}
|
|
5769
|
+
return this.db;
|
|
5770
|
+
}
|
|
5771
|
+
findMemoryNodeId(db, memoryId) {
|
|
5772
|
+
const row = db.prepare(
|
|
5773
|
+
`SELECT id FROM graph_nodes WHERE type = 'memory' AND ref_id = ?`
|
|
5774
|
+
).get(memoryId);
|
|
5775
|
+
return row?.id ?? null;
|
|
5776
|
+
}
|
|
5777
|
+
ensureMemoryNodeSync(db, memoryId) {
|
|
5778
|
+
const existing = this.findMemoryNodeId(db, memoryId);
|
|
5779
|
+
if (existing) return existing;
|
|
5780
|
+
db.prepare(
|
|
5781
|
+
`INSERT INTO graph_nodes (id, type, ref_id, name, metadata, created_at)
|
|
5782
|
+
VALUES (?, 'memory', ?, NULL, '{}', ?)`
|
|
5783
|
+
).run(memoryId, memoryId, nowIso2());
|
|
5784
|
+
return memoryId;
|
|
5785
|
+
}
|
|
5786
|
+
};
|
|
5787
|
+
function buildGetRelatedSql(direction) {
|
|
5788
|
+
const mentions = ENTITY_MENTIONS_RELATION;
|
|
5789
|
+
if (direction === "out") {
|
|
5790
|
+
return `
|
|
5791
|
+
WITH RECURSIVE walk(node_id, depth, path) AS (
|
|
5792
|
+
SELECT ? AS node_id, 0, '/' || ? || '/'
|
|
5793
|
+
UNION ALL
|
|
5794
|
+
SELECT e.to_node_id, w.depth + 1, w.path || e.to_node_id || '/'
|
|
5795
|
+
FROM walk w
|
|
5796
|
+
JOIN graph_edges e ON e.from_node_id = w.node_id
|
|
5797
|
+
JOIN graph_nodes dest ON dest.id = e.to_node_id AND dest.type = 'memory'
|
|
5798
|
+
WHERE w.depth < ?
|
|
5799
|
+
AND instr(w.path, '/' || e.to_node_id || '/') = 0
|
|
5800
|
+
AND (? IS NULL OR e.relation = ?)
|
|
5801
|
+
AND e.relation != '${mentions}'
|
|
5802
|
+
)
|
|
5803
|
+
SELECT DISTINCT n.ref_id AS ref_id
|
|
5804
|
+
FROM walk w
|
|
5805
|
+
JOIN graph_nodes n ON n.id = w.node_id
|
|
5806
|
+
WHERE w.depth > 0 AND n.type = 'memory'
|
|
5807
|
+
`;
|
|
5808
|
+
}
|
|
5809
|
+
if (direction === "in") {
|
|
5810
|
+
return `
|
|
5811
|
+
WITH RECURSIVE walk(node_id, depth, path) AS (
|
|
5812
|
+
SELECT ? AS node_id, 0, '/' || ? || '/'
|
|
5813
|
+
UNION ALL
|
|
5814
|
+
SELECT e.from_node_id, w.depth + 1, w.path || e.from_node_id || '/'
|
|
5815
|
+
FROM walk w
|
|
5816
|
+
JOIN graph_edges e ON e.to_node_id = w.node_id
|
|
5817
|
+
JOIN graph_nodes src ON src.id = e.from_node_id AND src.type = 'memory'
|
|
5818
|
+
WHERE w.depth < ?
|
|
5819
|
+
AND instr(w.path, '/' || e.from_node_id || '/') = 0
|
|
5820
|
+
AND (? IS NULL OR e.relation = ?)
|
|
5821
|
+
AND e.relation != '${mentions}'
|
|
5822
|
+
)
|
|
5823
|
+
SELECT DISTINCT n.ref_id AS ref_id
|
|
5824
|
+
FROM walk w
|
|
5825
|
+
JOIN graph_nodes n ON n.id = w.node_id
|
|
5826
|
+
WHERE w.depth > 0 AND n.type = 'memory'
|
|
5827
|
+
`;
|
|
5828
|
+
}
|
|
5829
|
+
return `
|
|
5830
|
+
WITH RECURSIVE walk(node_id, depth, path) AS (
|
|
5831
|
+
SELECT ? AS node_id, 0, '/' || ? || '/'
|
|
5832
|
+
UNION ALL
|
|
5833
|
+
SELECT neighbor.id, w.depth + 1, w.path || neighbor.id || '/'
|
|
5834
|
+
FROM walk w
|
|
5835
|
+
JOIN graph_edges e
|
|
5836
|
+
ON e.from_node_id = w.node_id OR e.to_node_id = w.node_id
|
|
5837
|
+
JOIN graph_nodes neighbor
|
|
5838
|
+
ON neighbor.id = CASE
|
|
5839
|
+
WHEN e.from_node_id = w.node_id THEN e.to_node_id
|
|
5840
|
+
ELSE e.from_node_id
|
|
5841
|
+
END
|
|
5842
|
+
AND neighbor.type = 'memory'
|
|
5843
|
+
WHERE w.depth < ?
|
|
5844
|
+
AND instr(w.path, '/' || neighbor.id || '/') = 0
|
|
5845
|
+
AND (? IS NULL OR e.relation = ?)
|
|
5846
|
+
AND e.relation != '${mentions}'
|
|
5847
|
+
)
|
|
5848
|
+
SELECT DISTINCT n.ref_id AS ref_id
|
|
5849
|
+
FROM walk w
|
|
5850
|
+
JOIN graph_nodes n ON n.id = w.node_id
|
|
5851
|
+
WHERE w.depth > 0 AND n.type = 'memory'
|
|
5852
|
+
`;
|
|
5853
|
+
}
|
|
5854
|
+
function nowIso2() {
|
|
5855
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5856
|
+
}
|
|
5857
|
+
function describe(error) {
|
|
5858
|
+
return error instanceof Error ? error.message : String(error);
|
|
5859
|
+
}
|
|
5860
|
+
|
|
5861
|
+
// src/graph/providers/neo4j.ts
|
|
5862
|
+
var Neo4jGraphProvider = class {
|
|
5863
|
+
name = "neo4j";
|
|
5864
|
+
url;
|
|
5865
|
+
username;
|
|
5866
|
+
password;
|
|
5867
|
+
database;
|
|
5868
|
+
driver = null;
|
|
5869
|
+
opened = false;
|
|
5870
|
+
constructor(options) {
|
|
5871
|
+
if (!options?.url?.trim()) {
|
|
5872
|
+
throw new ConfigurationError("neo4j graph requires a non-empty url");
|
|
5873
|
+
}
|
|
5874
|
+
if (!options?.username?.trim()) {
|
|
5875
|
+
throw new ConfigurationError("neo4j graph requires a non-empty username");
|
|
5876
|
+
}
|
|
5877
|
+
if (typeof options.password !== "string") {
|
|
5878
|
+
throw new ConfigurationError("neo4j graph requires a password string");
|
|
5879
|
+
}
|
|
5880
|
+
this.url = options.url.trim();
|
|
5881
|
+
this.username = options.username.trim();
|
|
5882
|
+
this.password = options.password;
|
|
5883
|
+
this.database = options.database?.trim() || void 0;
|
|
5884
|
+
}
|
|
5885
|
+
supportsFileSnapshot() {
|
|
5886
|
+
return false;
|
|
5887
|
+
}
|
|
5888
|
+
getDataPath() {
|
|
5889
|
+
return null;
|
|
5890
|
+
}
|
|
5891
|
+
async open() {
|
|
5892
|
+
if (this.opened) return;
|
|
5893
|
+
let mod;
|
|
5894
|
+
try {
|
|
5895
|
+
mod = await import('neo4j-driver');
|
|
5896
|
+
} catch {
|
|
5897
|
+
throw new ConfigurationError(
|
|
5898
|
+
'Neo4j graph requires the optional "neo4j-driver" package. Install it with: npm install neo4j-driver'
|
|
5899
|
+
);
|
|
5900
|
+
}
|
|
5901
|
+
this.driver = mod.driver(
|
|
5902
|
+
this.url,
|
|
5903
|
+
mod.auth.basic(this.username, this.password)
|
|
5904
|
+
);
|
|
5905
|
+
await this.driver.verifyConnectivity();
|
|
5906
|
+
this.opened = true;
|
|
5907
|
+
await this.withSession(async (session) => {
|
|
5908
|
+
try {
|
|
5909
|
+
await session.run(
|
|
5910
|
+
`CREATE CONSTRAINT memory_id IF NOT EXISTS FOR (m:Memory) REQUIRE m.id IS UNIQUE`
|
|
5911
|
+
);
|
|
5912
|
+
} catch {
|
|
5913
|
+
}
|
|
5914
|
+
try {
|
|
5915
|
+
await session.run(
|
|
5916
|
+
`CREATE CONSTRAINT entity_id IF NOT EXISTS FOR (e:Entity) REQUIRE e.id IS UNIQUE`
|
|
5917
|
+
);
|
|
5918
|
+
} catch {
|
|
5919
|
+
}
|
|
5920
|
+
});
|
|
5921
|
+
}
|
|
5922
|
+
async close() {
|
|
5923
|
+
if (this.driver) {
|
|
5924
|
+
await this.driver.close();
|
|
5925
|
+
}
|
|
5926
|
+
this.driver = null;
|
|
5927
|
+
this.opened = false;
|
|
5928
|
+
}
|
|
5929
|
+
requireDriver() {
|
|
5930
|
+
if (!this.driver || !this.opened) {
|
|
5931
|
+
throw new DatabaseError("Neo4j graph is not open", {
|
|
5932
|
+
operation: "graph",
|
|
5933
|
+
suggestion: "Call ready() / open() before graph operations"
|
|
5934
|
+
});
|
|
5935
|
+
}
|
|
5936
|
+
return this.driver;
|
|
5937
|
+
}
|
|
5938
|
+
async withSession(fn) {
|
|
5939
|
+
const driver = this.requireDriver();
|
|
5940
|
+
const session = this.database ? driver.session({ database: this.database }) : driver.session();
|
|
5941
|
+
try {
|
|
5942
|
+
return await fn(session);
|
|
5943
|
+
} finally {
|
|
5944
|
+
await session.close();
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
async run(cypher, params = {}) {
|
|
5948
|
+
return this.withSession(async (session) => {
|
|
5949
|
+
const result = await session.run(cypher, params);
|
|
5950
|
+
return result.records.map((rec) => {
|
|
5951
|
+
try {
|
|
5952
|
+
return rec.toObject();
|
|
5953
|
+
} catch {
|
|
5954
|
+
const obj = {};
|
|
5955
|
+
for (const key of rec.keys) {
|
|
5956
|
+
obj[key] = unwrapNeo4jValue(rec.get(key));
|
|
5957
|
+
}
|
|
5958
|
+
return obj;
|
|
5959
|
+
}
|
|
5960
|
+
});
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
async ensureMemoryNode(id) {
|
|
5964
|
+
await this.run(
|
|
5965
|
+
`MERGE (m:Memory { id: $id })
|
|
5966
|
+
ON CREATE SET
|
|
5967
|
+
m.organization = '',
|
|
5968
|
+
m.agent = '',
|
|
5969
|
+
m.content_text = '',
|
|
5970
|
+
m.metadata_json = '{}',
|
|
5971
|
+
m.archived = false,
|
|
5972
|
+
m.compressed_into = '',
|
|
5973
|
+
m.created_at = '',
|
|
5974
|
+
m.updated_at = ''`,
|
|
5975
|
+
{ id }
|
|
5976
|
+
);
|
|
5977
|
+
}
|
|
5978
|
+
async linkMemories(fromId, toId, relation, metadata) {
|
|
5979
|
+
await this.ensureMemoryNode(fromId);
|
|
5980
|
+
await this.ensureMemoryNode(toId);
|
|
5981
|
+
await this.run(
|
|
5982
|
+
`MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
|
|
5983
|
+
MERGE (a)-[r:RELATED { relation: $relation }]->(b)
|
|
5984
|
+
SET r.metadata_json = $metadata`,
|
|
5985
|
+
{
|
|
5986
|
+
fromId,
|
|
5987
|
+
toId,
|
|
5988
|
+
relation,
|
|
5989
|
+
metadata: serializeMetadata2(metadata)
|
|
5990
|
+
}
|
|
5991
|
+
);
|
|
5992
|
+
}
|
|
5993
|
+
async unlinkMemories(fromId, toId, relation) {
|
|
5994
|
+
if (relation !== void 0) {
|
|
5995
|
+
await this.run(
|
|
5996
|
+
`MATCH (a:Memory { id: $fromId })-[r:RELATED { relation: $relation }]->(b:Memory { id: $toId })
|
|
5997
|
+
DELETE r`,
|
|
5998
|
+
{ fromId, toId, relation }
|
|
5999
|
+
);
|
|
6000
|
+
return;
|
|
6001
|
+
}
|
|
6002
|
+
await this.run(
|
|
6003
|
+
`MATCH (a:Memory { id: $fromId })-[r:RELATED]->(b:Memory { id: $toId })
|
|
6004
|
+
DELETE r`,
|
|
6005
|
+
{ fromId, toId }
|
|
6006
|
+
);
|
|
6007
|
+
}
|
|
6008
|
+
/**
|
|
6009
|
+
* Native Neo4j variable-length path traversal.
|
|
6010
|
+
* Relationship filter uses RELATED.relation property for parity with Kuzu.
|
|
6011
|
+
*/
|
|
6012
|
+
async getRelated(memoryId, options) {
|
|
6013
|
+
const depth = Math.max(1, Math.min(options?.depth ?? 1, 16));
|
|
6014
|
+
const direction = options?.direction ?? "both";
|
|
6015
|
+
const relation = options?.relation;
|
|
6016
|
+
const pathPattern = direction === "out" ? `(start)-[:RELATED*1..${depth}]->(n:Memory)` : direction === "in" ? `(start)<-[:RELATED*1..${depth}]-(n:Memory)` : `(start)-[:RELATED*1..${depth}]-(n:Memory)`;
|
|
6017
|
+
const cypher = relation ? `MATCH (start:Memory { id: $id }), p = ${pathPattern}
|
|
6018
|
+
WHERE ALL(r IN relationships(p) WHERE r.relation = $relation)
|
|
6019
|
+
AND n.id <> $id
|
|
6020
|
+
RETURN DISTINCT
|
|
6021
|
+
n.id AS id, n.organization AS organization, n.agent AS agent,
|
|
6022
|
+
n.content_text AS content_text, n.metadata_json AS metadata_json,
|
|
6023
|
+
n.archived AS archived, n.compressed_into AS compressed_into,
|
|
6024
|
+
n.created_at AS created_at, n.updated_at AS updated_at` : `MATCH (start:Memory { id: $id }), p = ${pathPattern}
|
|
6025
|
+
WHERE n.id <> $id
|
|
6026
|
+
RETURN DISTINCT
|
|
6027
|
+
n.id AS id, n.organization AS organization, n.agent AS agent,
|
|
6028
|
+
n.content_text AS content_text, n.metadata_json AS metadata_json,
|
|
6029
|
+
n.archived AS archived, n.compressed_into AS compressed_into,
|
|
6030
|
+
n.created_at AS created_at, n.updated_at AS updated_at`;
|
|
6031
|
+
const params = { id: memoryId };
|
|
6032
|
+
if (relation !== void 0) params.relation = relation;
|
|
6033
|
+
const rows = await this.run(cypher, params);
|
|
6034
|
+
return rows.map(
|
|
6035
|
+
(row) => rowToMemoryRecord(unwrapRow(row))
|
|
6036
|
+
);
|
|
6037
|
+
}
|
|
6038
|
+
async upsertEntity(entity) {
|
|
6039
|
+
const id = entityIdFrom(entity.name, entity.type);
|
|
6040
|
+
await this.run(
|
|
6041
|
+
`MERGE (e:Entity { id: $id })
|
|
6042
|
+
SET e.name = $name, e.type = $type, e.metadata_json = $metadata`,
|
|
6043
|
+
{
|
|
6044
|
+
id,
|
|
6045
|
+
name: entity.name,
|
|
6046
|
+
type: entity.type,
|
|
6047
|
+
metadata: serializeMetadata2(entity.metadata)
|
|
6048
|
+
}
|
|
6049
|
+
);
|
|
6050
|
+
return id;
|
|
6051
|
+
}
|
|
6052
|
+
async linkEntityToMemory(entityId, memoryId, role) {
|
|
6053
|
+
await this.ensureMemoryNode(memoryId);
|
|
6054
|
+
const found = await this.run(
|
|
6055
|
+
`MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
|
|
6056
|
+
{ id: entityId }
|
|
6057
|
+
);
|
|
6058
|
+
if (found.length === 0) {
|
|
6059
|
+
throw new DatabaseError(`Entity not found: ${entityId}`, {
|
|
6060
|
+
operation: "linkEntityToMemory"
|
|
6061
|
+
});
|
|
6062
|
+
}
|
|
6063
|
+
await this.run(
|
|
6064
|
+
`MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
|
|
6065
|
+
MERGE (e)-[r:MENTIONS]->(m)
|
|
6066
|
+
SET r.role = $role`,
|
|
6067
|
+
{ entityId, memoryId, role: role ?? "" }
|
|
6068
|
+
);
|
|
6069
|
+
}
|
|
6070
|
+
async deleteMemory(memoryId) {
|
|
6071
|
+
await this.run(
|
|
6072
|
+
`MATCH (m:Memory { id: $id })
|
|
6073
|
+
DETACH DELETE m`,
|
|
6074
|
+
{ id: memoryId }
|
|
6075
|
+
);
|
|
6076
|
+
}
|
|
6077
|
+
async query(cypher, params) {
|
|
6078
|
+
return this.run(cypher, params ?? {});
|
|
6079
|
+
}
|
|
6080
|
+
async health() {
|
|
6081
|
+
try {
|
|
6082
|
+
if (!this.opened || !this.driver) {
|
|
6083
|
+
return { ok: false, backend: "neo4j", details: { reason: "not open" } };
|
|
6084
|
+
}
|
|
6085
|
+
await this.driver.verifyConnectivity();
|
|
6086
|
+
let serverInfo = void 0;
|
|
6087
|
+
if (typeof this.driver.getServerInfo === "function") {
|
|
6088
|
+
serverInfo = await this.driver.getServerInfo();
|
|
6089
|
+
}
|
|
6090
|
+
return {
|
|
6091
|
+
ok: true,
|
|
6092
|
+
backend: "neo4j",
|
|
6093
|
+
details: {
|
|
6094
|
+
url: this.url,
|
|
6095
|
+
database: this.database ?? "default",
|
|
6096
|
+
networked: true,
|
|
6097
|
+
serverInfo
|
|
6098
|
+
}
|
|
6099
|
+
};
|
|
6100
|
+
} catch (error) {
|
|
6101
|
+
return {
|
|
6102
|
+
ok: false,
|
|
6103
|
+
backend: "neo4j",
|
|
6104
|
+
details: {
|
|
6105
|
+
url: this.url,
|
|
6106
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6107
|
+
}
|
|
6108
|
+
};
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
};
|
|
6112
|
+
function unwrapNeo4jValue(value) {
|
|
6113
|
+
if (value == null) return value;
|
|
6114
|
+
if (typeof value === "object" && value !== null && "toNumber" in value) {
|
|
6115
|
+
try {
|
|
6116
|
+
return value.toNumber();
|
|
6117
|
+
} catch {
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
if (typeof value === "object" && value !== null && "properties" in value) {
|
|
6121
|
+
return unwrapRow(
|
|
6122
|
+
value.properties
|
|
6123
|
+
);
|
|
6124
|
+
}
|
|
6125
|
+
return value;
|
|
6126
|
+
}
|
|
6127
|
+
function unwrapRow(row) {
|
|
6128
|
+
const out = {};
|
|
6129
|
+
for (const [k, v] of Object.entries(row)) {
|
|
6130
|
+
out[k] = unwrapNeo4jValue(v);
|
|
6131
|
+
}
|
|
6132
|
+
return out;
|
|
6133
|
+
}
|
|
6134
|
+
|
|
5363
6135
|
// src/core/validate.ts
|
|
5364
6136
|
function assertNonEmpty(value, fieldName) {
|
|
5365
6137
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
@@ -5440,9 +6212,21 @@ function validateTelemetryConfig(config) {
|
|
|
5440
6212
|
if (!config.database || typeof config.database !== "object") {
|
|
5441
6213
|
throw new ConfigurationError("telemetry.database is required when telemetry is enabled");
|
|
5442
6214
|
}
|
|
6215
|
+
const providerName = String(
|
|
6216
|
+
config.database.provider ?? ""
|
|
6217
|
+
);
|
|
6218
|
+
if (providerName === "kuzu" || providerName === "neo4j") {
|
|
6219
|
+
throw new ConfigurationError(
|
|
6220
|
+
`telemetry supports sqlite or postgres only (got "${providerName}")`,
|
|
6221
|
+
{
|
|
6222
|
+
reason: `provider=${providerName}`,
|
|
6223
|
+
suggestion: 'Use telemetry: { database: { provider: "sqlite", url: "./telemetry.db" } }. Graph backends (kuzu/neo4j) are not valid telemetry stores.'
|
|
6224
|
+
}
|
|
6225
|
+
);
|
|
6226
|
+
}
|
|
5443
6227
|
if (config.database.provider !== "sqlite") {
|
|
5444
6228
|
throw new ConfigurationError(
|
|
5445
|
-
`Unsupported telemetry provider "${config.database.provider}". Only "sqlite" is implemented
|
|
6229
|
+
`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.`,
|
|
5446
6230
|
{
|
|
5447
6231
|
reason: `provider=${config.database.provider}`,
|
|
5448
6232
|
suggestion: 'Use telemetry: { database: { provider: "sqlite", url: "./telemetry.db" } }'
|
|
@@ -5527,14 +6311,74 @@ function validateWolbargOptions(options) {
|
|
|
5527
6311
|
if (telemetry && !isTelemetryProvider(telemetry)) {
|
|
5528
6312
|
telemetry = validateTelemetryConfig(telemetry);
|
|
5529
6313
|
}
|
|
6314
|
+
let graph = options.graph;
|
|
6315
|
+
if (graph !== void 0) {
|
|
6316
|
+
graph = resolveGraphInput(graph);
|
|
6317
|
+
}
|
|
5530
6318
|
return {
|
|
5531
6319
|
...options,
|
|
5532
6320
|
organization: options.organization.trim(),
|
|
5533
6321
|
storage,
|
|
5534
6322
|
database: void 0,
|
|
5535
|
-
...telemetry ? { telemetry } : {}
|
|
6323
|
+
...telemetry ? { telemetry } : {},
|
|
6324
|
+
...graph !== void 0 ? { graph } : {}
|
|
5536
6325
|
};
|
|
5537
6326
|
}
|
|
6327
|
+
function resolveGraphInput(input) {
|
|
6328
|
+
if (input == null || typeof input !== "object") {
|
|
6329
|
+
throw new ConfigurationError(
|
|
6330
|
+
"graph must be a GraphProvider instance or a config object",
|
|
6331
|
+
{
|
|
6332
|
+
suggestion: "Use graph: sqliteGraph({ path }) or graph: neo4jGraph({ url, username, password })"
|
|
6333
|
+
}
|
|
6334
|
+
);
|
|
6335
|
+
}
|
|
6336
|
+
if (Array.isArray(input)) {
|
|
6337
|
+
throw new ConfigurationError(
|
|
6338
|
+
"graph must be a GraphProvider instance or a config object",
|
|
6339
|
+
{
|
|
6340
|
+
suggestion: "Use graph: sqliteGraph({ path }) or graph: neo4jGraph({ url, username, password })"
|
|
6341
|
+
}
|
|
6342
|
+
);
|
|
6343
|
+
}
|
|
6344
|
+
if (isGraphProvider(input)) {
|
|
6345
|
+
return input;
|
|
6346
|
+
}
|
|
6347
|
+
const config = input;
|
|
6348
|
+
if (config.provider === "sqlite") {
|
|
6349
|
+
if (typeof config.path !== "string" || !config.path.trim()) {
|
|
6350
|
+
throw new ConfigurationError(
|
|
6351
|
+
'graph sqlite config requires a non-empty "path"'
|
|
6352
|
+
);
|
|
6353
|
+
}
|
|
6354
|
+
return new SqliteGraphProvider({ path: config.path });
|
|
6355
|
+
}
|
|
6356
|
+
if (config.provider === "neo4j") {
|
|
6357
|
+
if (typeof config.url !== "string" || !config.url.trim()) {
|
|
6358
|
+
throw new ConfigurationError('graph neo4j config requires a non-empty "url"');
|
|
6359
|
+
}
|
|
6360
|
+
if (typeof config.username !== "string" || !config.username.trim()) {
|
|
6361
|
+
throw new ConfigurationError(
|
|
6362
|
+
'graph neo4j config requires a non-empty "username"'
|
|
6363
|
+
);
|
|
6364
|
+
}
|
|
6365
|
+
if (typeof config.password !== "string") {
|
|
6366
|
+
throw new ConfigurationError('graph neo4j config requires a "password" string');
|
|
6367
|
+
}
|
|
6368
|
+
return new Neo4jGraphProvider({
|
|
6369
|
+
url: config.url,
|
|
6370
|
+
username: config.username,
|
|
6371
|
+
password: config.password,
|
|
6372
|
+
database: config.database
|
|
6373
|
+
});
|
|
6374
|
+
}
|
|
6375
|
+
throw new ConfigurationError(
|
|
6376
|
+
`Unsupported graph provider "${String(config.provider)}". Supported: "sqlite", "neo4j", or a GraphProvider instance.`,
|
|
6377
|
+
{
|
|
6378
|
+
suggestion: 'Use graph: sqliteGraph({ path: "./local-graph.db" }) or graph: neo4jGraph({ url, username, password })'
|
|
6379
|
+
}
|
|
6380
|
+
);
|
|
6381
|
+
}
|
|
5538
6382
|
|
|
5539
6383
|
// src/telemetry/logger.ts
|
|
5540
6384
|
var LEVEL_ORDER = {
|
|
@@ -5814,7 +6658,7 @@ var SqliteEventDatabase = class {
|
|
|
5814
6658
|
try {
|
|
5815
6659
|
const dbPath = this.resolvePath(this.url);
|
|
5816
6660
|
if (dbPath !== ":memory:" && !this.readonly) {
|
|
5817
|
-
|
|
6661
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(dbPath), { recursive: true });
|
|
5818
6662
|
}
|
|
5819
6663
|
const db = new sqlite$1.DatabaseSync(dbPath, {
|
|
5820
6664
|
allowExtension: false,
|
|
@@ -5859,7 +6703,7 @@ var SqliteEventDatabase = class {
|
|
|
5859
6703
|
this.db = null;
|
|
5860
6704
|
this.columns.clear();
|
|
5861
6705
|
throw new InitializationError(
|
|
5862
|
-
`Failed to open telemetry EventDatabase: ${
|
|
6706
|
+
`Failed to open telemetry EventDatabase: ${describe2(error)}`,
|
|
5863
6707
|
{ cause: error instanceof Error ? error : void 0 }
|
|
5864
6708
|
);
|
|
5865
6709
|
}
|
|
@@ -5914,7 +6758,7 @@ var SqliteEventDatabase = class {
|
|
|
5914
6758
|
return event;
|
|
5915
6759
|
} catch (error) {
|
|
5916
6760
|
throw new DatabaseError(
|
|
5917
|
-
`Failed to write telemetry event: ${
|
|
6761
|
+
`Failed to write telemetry event: ${describe2(error)}`,
|
|
5918
6762
|
{ cause: error instanceof Error ? error : void 0 }
|
|
5919
6763
|
);
|
|
5920
6764
|
}
|
|
@@ -6070,7 +6914,7 @@ var SqliteEventDatabase = class {
|
|
|
6070
6914
|
}
|
|
6071
6915
|
resolvePath(url) {
|
|
6072
6916
|
if (url === ":memory:") return ":memory:";
|
|
6073
|
-
return
|
|
6917
|
+
return path7__default.default.isAbsolute(url) ? url : path7__default.default.resolve(process.cwd(), url);
|
|
6074
6918
|
}
|
|
6075
6919
|
};
|
|
6076
6920
|
function normalizeEvent(input) {
|
|
@@ -6155,7 +6999,7 @@ function parseJson(value) {
|
|
|
6155
6999
|
return null;
|
|
6156
7000
|
}
|
|
6157
7001
|
}
|
|
6158
|
-
function
|
|
7002
|
+
function describe2(error) {
|
|
6159
7003
|
return error instanceof Error ? error.message : String(error);
|
|
6160
7004
|
}
|
|
6161
7005
|
|
|
@@ -6233,16 +7077,15 @@ var SqliteTelemetryProvider = class {
|
|
|
6233
7077
|
}
|
|
6234
7078
|
}
|
|
6235
7079
|
};
|
|
6236
|
-
var SDK_VERSION2 = "0.3.0";
|
|
6237
7080
|
var SqliteCheckpointProvider = class {
|
|
6238
7081
|
name = "sqlite";
|
|
6239
7082
|
directory;
|
|
6240
7083
|
ready = false;
|
|
6241
7084
|
constructor(options) {
|
|
6242
|
-
this.directory = options?.directory ??
|
|
7085
|
+
this.directory = options?.directory ?? path7__default.default.resolve(process.cwd(), ".wolbarg", "checkpoints");
|
|
6243
7086
|
}
|
|
6244
7087
|
async open() {
|
|
6245
|
-
|
|
7088
|
+
fs6__default.default.mkdirSync(this.directory, { recursive: true });
|
|
6246
7089
|
this.ready = true;
|
|
6247
7090
|
}
|
|
6248
7091
|
async close() {
|
|
@@ -6252,7 +7095,7 @@ var SqliteCheckpointProvider = class {
|
|
|
6252
7095
|
this.requireReady();
|
|
6253
7096
|
assertCheckpointName(name);
|
|
6254
7097
|
const metaPath = this.metaPath(name);
|
|
6255
|
-
if (
|
|
7098
|
+
if (fs6__default.default.existsSync(metaPath)) {
|
|
6256
7099
|
throw new ValidationError(
|
|
6257
7100
|
`Checkpoint "${name}" already exists. Choose a different name \u2014 checkpoints are never overwritten.`
|
|
6258
7101
|
);
|
|
@@ -6263,7 +7106,7 @@ var SqliteCheckpointProvider = class {
|
|
|
6263
7106
|
"Cannot checkpoint an in-memory database. Use a file-backed SQLite database."
|
|
6264
7107
|
);
|
|
6265
7108
|
}
|
|
6266
|
-
if (!
|
|
7109
|
+
if (!fs6__default.default.existsSync(resolvedSource)) {
|
|
6267
7110
|
throw new DatabaseError(
|
|
6268
7111
|
`Failed to execute checkpoint()
|
|
6269
7112
|
Reason:
|
|
@@ -6274,18 +7117,18 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6274
7117
|
}
|
|
6275
7118
|
const snapshotPath = this.snapshotPath(name);
|
|
6276
7119
|
await safeSqliteBackup(resolvedSource, snapshotPath);
|
|
6277
|
-
const stats =
|
|
7120
|
+
const stats = fs6__default.default.statSync(snapshotPath);
|
|
6278
7121
|
const meta2 = {
|
|
6279
7122
|
name,
|
|
6280
7123
|
description: options?.description ?? null,
|
|
6281
7124
|
createdAt: nowIso(),
|
|
6282
|
-
sdkVersion:
|
|
7125
|
+
sdkVersion: SDK_VERSION,
|
|
6283
7126
|
provider: this.name,
|
|
6284
7127
|
snapshotPath,
|
|
6285
7128
|
sourcePath: resolvedSource,
|
|
6286
7129
|
sizeBytes: stats.size
|
|
6287
7130
|
};
|
|
6288
|
-
|
|
7131
|
+
fs6__default.default.writeFileSync(metaPath, JSON.stringify(meta2, null, 2), "utf8");
|
|
6289
7132
|
return meta2;
|
|
6290
7133
|
}
|
|
6291
7134
|
async rollback(name, targetPath) {
|
|
@@ -6300,11 +7143,11 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6300
7143
|
"Cannot rollback into an in-memory database."
|
|
6301
7144
|
);
|
|
6302
7145
|
}
|
|
6303
|
-
|
|
7146
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(resolvedTarget), { recursive: true });
|
|
6304
7147
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
6305
7148
|
const side = `${resolvedTarget}${suffix}`;
|
|
6306
|
-
if (
|
|
6307
|
-
|
|
7149
|
+
if (fs6__default.default.existsSync(side)) {
|
|
7150
|
+
fs6__default.default.rmSync(side, { force: true });
|
|
6308
7151
|
}
|
|
6309
7152
|
}
|
|
6310
7153
|
await safeSqliteBackup(meta2.snapshotPath, resolvedTarget);
|
|
@@ -6315,26 +7158,26 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6315
7158
|
const metaPath = this.metaPath(name);
|
|
6316
7159
|
const snapshotPath = this.snapshotPath(name);
|
|
6317
7160
|
let removed = false;
|
|
6318
|
-
if (
|
|
6319
|
-
|
|
7161
|
+
if (fs6__default.default.existsSync(metaPath)) {
|
|
7162
|
+
fs6__default.default.rmSync(metaPath, { force: true });
|
|
6320
7163
|
removed = true;
|
|
6321
7164
|
}
|
|
6322
|
-
if (
|
|
6323
|
-
|
|
7165
|
+
if (fs6__default.default.existsSync(snapshotPath)) {
|
|
7166
|
+
fs6__default.default.rmSync(snapshotPath, { force: true });
|
|
6324
7167
|
removed = true;
|
|
6325
7168
|
}
|
|
6326
7169
|
return removed;
|
|
6327
7170
|
}
|
|
6328
7171
|
async listCheckpoints() {
|
|
6329
7172
|
this.requireReady();
|
|
6330
|
-
if (!
|
|
7173
|
+
if (!fs6__default.default.existsSync(this.directory)) {
|
|
6331
7174
|
return [];
|
|
6332
7175
|
}
|
|
6333
|
-
const files =
|
|
7176
|
+
const files = fs6__default.default.readdirSync(this.directory).filter((f) => f.endsWith(".json"));
|
|
6334
7177
|
const out = [];
|
|
6335
7178
|
for (const file of files) {
|
|
6336
7179
|
try {
|
|
6337
|
-
const raw =
|
|
7180
|
+
const raw = fs6__default.default.readFileSync(path7__default.default.join(this.directory, file), "utf8");
|
|
6338
7181
|
out.push(JSON.parse(raw));
|
|
6339
7182
|
} catch {
|
|
6340
7183
|
}
|
|
@@ -6344,20 +7187,81 @@ Ensure Wolbarg has been initialized and the database path is correct.`
|
|
|
6344
7187
|
async getCheckpoint(name) {
|
|
6345
7188
|
this.requireReady();
|
|
6346
7189
|
const metaPath = this.metaPath(name);
|
|
6347
|
-
if (!
|
|
7190
|
+
if (!fs6__default.default.existsSync(metaPath)) {
|
|
6348
7191
|
return null;
|
|
6349
7192
|
}
|
|
6350
7193
|
try {
|
|
6351
|
-
return JSON.parse(
|
|
7194
|
+
return JSON.parse(fs6__default.default.readFileSync(metaPath, "utf8"));
|
|
6352
7195
|
} catch {
|
|
6353
7196
|
return null;
|
|
6354
7197
|
}
|
|
6355
7198
|
}
|
|
7199
|
+
/**
|
|
7200
|
+
* Consistent SQLite file backup (WAL checkpoint + backup API / copy).
|
|
7201
|
+
* Used for memory snapshots and for secondary files such as the graph DB.
|
|
7202
|
+
*/
|
|
7203
|
+
async backupSqliteFile(sourcePath, destPath) {
|
|
7204
|
+
this.requireReady();
|
|
7205
|
+
const resolved = resolveDbPath(sourcePath);
|
|
7206
|
+
if (resolved === ":memory:") {
|
|
7207
|
+
throw new ConfigurationError(
|
|
7208
|
+
"Cannot backup an in-memory database. Use a file-backed SQLite database."
|
|
7209
|
+
);
|
|
7210
|
+
}
|
|
7211
|
+
if (!fs6__default.default.existsSync(resolved)) {
|
|
7212
|
+
throw new DatabaseError(
|
|
7213
|
+
`SQLite file not found at ${resolved}`,
|
|
7214
|
+
{
|
|
7215
|
+
suggestion: "Ensure the source database exists before backing up."
|
|
7216
|
+
}
|
|
7217
|
+
);
|
|
7218
|
+
}
|
|
7219
|
+
await safeSqliteBackup(resolved, destPath);
|
|
7220
|
+
}
|
|
7221
|
+
/**
|
|
7222
|
+
* Snapshot a secondary SQLite file (typically the graph DB) next to a named
|
|
7223
|
+
* memory checkpoint as `{name}.graph.db`.
|
|
7224
|
+
*/
|
|
7225
|
+
async checkpointGraph(name, graphSourcePath) {
|
|
7226
|
+
this.requireReady();
|
|
7227
|
+
assertCheckpointName(name);
|
|
7228
|
+
const dest = this.graphSnapshotPath(name);
|
|
7229
|
+
await this.backupSqliteFile(graphSourcePath, dest);
|
|
7230
|
+
return dest;
|
|
7231
|
+
}
|
|
7232
|
+
/**
|
|
7233
|
+
* Restore a previously snapshotted graph SQLite file into `targetPath`.
|
|
7234
|
+
*/
|
|
7235
|
+
async rollbackGraph(name, targetPath) {
|
|
7236
|
+
this.requireReady();
|
|
7237
|
+
const snapshot = this.graphSnapshotPath(name);
|
|
7238
|
+
if (!fs6__default.default.existsSync(snapshot)) {
|
|
7239
|
+
return;
|
|
7240
|
+
}
|
|
7241
|
+
const resolvedTarget = resolveDbPath(targetPath);
|
|
7242
|
+
if (resolvedTarget === ":memory:") {
|
|
7243
|
+
throw new ConfigurationError(
|
|
7244
|
+
"Cannot rollback graph into an in-memory database."
|
|
7245
|
+
);
|
|
7246
|
+
}
|
|
7247
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(resolvedTarget), { recursive: true });
|
|
7248
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
7249
|
+
const side = `${resolvedTarget}${suffix}`;
|
|
7250
|
+
if (fs6__default.default.existsSync(side)) {
|
|
7251
|
+
fs6__default.default.rmSync(side, { force: true });
|
|
7252
|
+
}
|
|
7253
|
+
}
|
|
7254
|
+
await safeSqliteBackup(snapshot, resolvedTarget);
|
|
7255
|
+
}
|
|
7256
|
+
/** Absolute path of the graph snapshot for a named checkpoint, if present. */
|
|
7257
|
+
graphSnapshotPath(name) {
|
|
7258
|
+
return path7__default.default.join(this.directory, `${sanitize(name)}.graph.db`);
|
|
7259
|
+
}
|
|
6356
7260
|
metaPath(name) {
|
|
6357
|
-
return
|
|
7261
|
+
return path7__default.default.join(this.directory, `${sanitize(name)}.json`);
|
|
6358
7262
|
}
|
|
6359
7263
|
snapshotPath(name) {
|
|
6360
|
-
return
|
|
7264
|
+
return path7__default.default.join(this.directory, `${sanitize(name)}.db`);
|
|
6361
7265
|
}
|
|
6362
7266
|
requireReady() {
|
|
6363
7267
|
if (!this.ready) {
|
|
@@ -6380,12 +7284,12 @@ function assertCheckpointName(name) {
|
|
|
6380
7284
|
}
|
|
6381
7285
|
function resolveDbPath(connectionString) {
|
|
6382
7286
|
if (connectionString === ":memory:") return ":memory:";
|
|
6383
|
-
return
|
|
7287
|
+
return path7__default.default.isAbsolute(connectionString) ? connectionString : path7__default.default.resolve(process.cwd(), connectionString);
|
|
6384
7288
|
}
|
|
6385
7289
|
async function safeSqliteBackup(sourcePath, destPath) {
|
|
6386
|
-
|
|
6387
|
-
if (
|
|
6388
|
-
|
|
7290
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(destPath), { recursive: true });
|
|
7291
|
+
if (fs6__default.default.existsSync(destPath)) {
|
|
7292
|
+
fs6__default.default.rmSync(destPath, { force: true });
|
|
6389
7293
|
}
|
|
6390
7294
|
let source = null;
|
|
6391
7295
|
let dest = null;
|
|
@@ -6404,7 +7308,7 @@ async function safeSqliteBackup(sourcePath, destPath) {
|
|
|
6404
7308
|
source = null;
|
|
6405
7309
|
dest.close();
|
|
6406
7310
|
dest = null;
|
|
6407
|
-
|
|
7311
|
+
fs6__default.default.copyFileSync(sourcePath, destPath);
|
|
6408
7312
|
}
|
|
6409
7313
|
} catch (error) {
|
|
6410
7314
|
throw new DatabaseError(
|
|
@@ -6500,6 +7404,7 @@ var Wolbarg = class {
|
|
|
6500
7404
|
keywordSearch = null;
|
|
6501
7405
|
ocr = null;
|
|
6502
7406
|
vision = null;
|
|
7407
|
+
graph = null;
|
|
6503
7408
|
chunking = null;
|
|
6504
7409
|
retrievalConfig = {};
|
|
6505
7410
|
embeddingDimensions = null;
|
|
@@ -6546,6 +7451,9 @@ var Wolbarg = class {
|
|
|
6546
7451
|
this.keywordSearch = validated.keywordSearch ?? null;
|
|
6547
7452
|
this.ocr = validated.ocr ?? null;
|
|
6548
7453
|
this.vision = validated.vision ?? null;
|
|
7454
|
+
if (validated.graph) {
|
|
7455
|
+
this.graph = validated.graph;
|
|
7456
|
+
}
|
|
6549
7457
|
this.chunking = validated.chunking ?? null;
|
|
6550
7458
|
this.retrievalConfig = validated.retrieval ?? {};
|
|
6551
7459
|
this.subscribeBackend = new SqliteSubscribeEmitter();
|
|
@@ -6624,6 +7532,7 @@ var Wolbarg = class {
|
|
|
6624
7532
|
await this.storage.open();
|
|
6625
7533
|
await this.telemetry.open();
|
|
6626
7534
|
await this.checkpointProvider?.open();
|
|
7535
|
+
await this.graph?.open();
|
|
6627
7536
|
if (this.rawEmbedding && this.embeddingCacheConfig.enabled) {
|
|
6628
7537
|
if (this.storage instanceof SqliteStorageProvider) {
|
|
6629
7538
|
const sqlite2 = this.storage;
|
|
@@ -6675,6 +7584,7 @@ var Wolbarg = class {
|
|
|
6675
7584
|
this.initialized = true;
|
|
6676
7585
|
this.telemetry.emitStartup(this.storage.name);
|
|
6677
7586
|
} catch (error) {
|
|
7587
|
+
await this.graph?.close().catch(() => void 0);
|
|
6678
7588
|
await this.storage.close().catch(() => void 0);
|
|
6679
7589
|
await this.telemetry.close().catch(() => void 0);
|
|
6680
7590
|
this.initialized = false;
|
|
@@ -6701,7 +7611,7 @@ var Wolbarg = class {
|
|
|
6701
7611
|
metadata: result.metadata,
|
|
6702
7612
|
agentId: result.agent,
|
|
6703
7613
|
tags: telemetryTags(result.metadata),
|
|
6704
|
-
extra: { upsertAction: result.action }
|
|
7614
|
+
extra: { upsertAction: result.action, action: result.action }
|
|
6705
7615
|
});
|
|
6706
7616
|
return result;
|
|
6707
7617
|
} catch (error) {
|
|
@@ -7405,6 +8315,13 @@ var Wolbarg = class {
|
|
|
7405
8315
|
tags: commonTags(serialized.map((result) => result.metadata))
|
|
7406
8316
|
};
|
|
7407
8317
|
if (!explain) {
|
|
8318
|
+
if (options.includeGraph === true && this.graph) {
|
|
8319
|
+
const tGraph = performance.now();
|
|
8320
|
+
for (const hit of serialized) {
|
|
8321
|
+
hit.related = await this.hydrateRelated(hit.id);
|
|
8322
|
+
}
|
|
8323
|
+
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
8324
|
+
}
|
|
7408
8325
|
trace.success(telemetryFields);
|
|
7409
8326
|
return serialized;
|
|
7410
8327
|
}
|
|
@@ -7757,21 +8674,82 @@ var Wolbarg = class {
|
|
|
7757
8674
|
throw wrapOperationError("ingest", error);
|
|
7758
8675
|
}
|
|
7759
8676
|
}
|
|
8677
|
+
/**
|
|
8678
|
+
* Link two memories in the optional graph layer.
|
|
8679
|
+
* Throws {@link ProviderNotConfiguredError} when no graph provider is set.
|
|
8680
|
+
*/
|
|
8681
|
+
async linkMemories(fromId, toId, relation, metadata) {
|
|
8682
|
+
const trace = this.telemetry.start("linkMemories");
|
|
8683
|
+
try {
|
|
8684
|
+
await this.requireReady();
|
|
8685
|
+
const graph = this.requireGraph("linkMemories");
|
|
8686
|
+
assertNonEmptyString(fromId, "fromId");
|
|
8687
|
+
assertNonEmptyString(toId, "toId");
|
|
8688
|
+
assertNonEmptyString(relation, "relation");
|
|
8689
|
+
const tGraph = performance.now();
|
|
8690
|
+
await graph.linkMemories(
|
|
8691
|
+
fromId.trim(),
|
|
8692
|
+
toId.trim(),
|
|
8693
|
+
relation.trim(),
|
|
8694
|
+
metadata
|
|
8695
|
+
);
|
|
8696
|
+
trace.mark("databaseWriteMs", performance.now() - tGraph);
|
|
8697
|
+
trace.success({
|
|
8698
|
+
provider: graph.name,
|
|
8699
|
+
memoryIds: [fromId.trim(), toId.trim()],
|
|
8700
|
+
extra: { relation: relation.trim() }
|
|
8701
|
+
});
|
|
8702
|
+
} catch (error) {
|
|
8703
|
+
trace.failure(error);
|
|
8704
|
+
throw wrapOperationError("linkMemories", error);
|
|
8705
|
+
}
|
|
8706
|
+
}
|
|
8707
|
+
/**
|
|
8708
|
+
* Traverse related memories via the optional graph layer.
|
|
8709
|
+
* Throws {@link ProviderNotConfiguredError} when no graph provider is set.
|
|
8710
|
+
* Results are re-hydrated from SQL storage when available.
|
|
8711
|
+
*/
|
|
8712
|
+
async getRelated(memoryId, options) {
|
|
8713
|
+
const trace = this.telemetry.start("getRelated");
|
|
8714
|
+
try {
|
|
8715
|
+
await this.requireReady();
|
|
8716
|
+
const graph = this.requireGraph("getRelated");
|
|
8717
|
+
assertNonEmptyString(memoryId, "memoryId");
|
|
8718
|
+
const tGraph = performance.now();
|
|
8719
|
+
const related = await this.hydrateRelated(memoryId.trim(), options);
|
|
8720
|
+
trace.mark("databaseReadMs", performance.now() - tGraph);
|
|
8721
|
+
trace.success({
|
|
8722
|
+
provider: graph.name,
|
|
8723
|
+
memoryIds: [memoryId.trim(), ...related.map((r) => r.id)],
|
|
8724
|
+
returnedCount: related.length
|
|
8725
|
+
});
|
|
8726
|
+
return related;
|
|
8727
|
+
} catch (error) {
|
|
8728
|
+
trace.failure(error);
|
|
8729
|
+
throw wrapOperationError("getRelated", error);
|
|
8730
|
+
}
|
|
8731
|
+
}
|
|
7760
8732
|
async forget(options) {
|
|
7761
8733
|
const trace = this.telemetry.start("forget");
|
|
7762
8734
|
try {
|
|
7763
8735
|
const { storage, organization } = await this.requireReady();
|
|
8736
|
+
const deletedIds = [];
|
|
7764
8737
|
const deleted = await this.withWriteLock(async () => {
|
|
7765
8738
|
if ("id" in options && options.id !== void 0) {
|
|
7766
8739
|
assertNonEmptyString(options.id, "id");
|
|
7767
|
-
const
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
);
|
|
8740
|
+
const id = options.id.trim();
|
|
8741
|
+
const ok = await storage.deleteMemoryById(id, organization);
|
|
8742
|
+
if (ok) deletedIds.push(id);
|
|
7771
8743
|
return ok ? 1 : 0;
|
|
7772
8744
|
}
|
|
7773
8745
|
if ("filter" in options && options.filter?.agent) {
|
|
7774
8746
|
assertNonEmptyString(options.filter.agent, "filter.agent");
|
|
8747
|
+
const rows = await storage.listMemories({
|
|
8748
|
+
organization,
|
|
8749
|
+
agent: options.filter.agent.trim(),
|
|
8750
|
+
includeArchived: true
|
|
8751
|
+
});
|
|
8752
|
+
for (const row of rows) deletedIds.push(row.id);
|
|
7775
8753
|
return storage.deleteMemoriesByFilter({
|
|
7776
8754
|
organization,
|
|
7777
8755
|
agent: options.filter.agent.trim(),
|
|
@@ -7782,12 +8760,20 @@ var Wolbarg = class {
|
|
|
7782
8760
|
"forget requires either { id } or { filter: { agent } }"
|
|
7783
8761
|
);
|
|
7784
8762
|
});
|
|
8763
|
+
if (deleted > 0 && this.graph && deletedIds.length > 0) {
|
|
8764
|
+
const tGraph = performance.now();
|
|
8765
|
+
for (const id of deletedIds) {
|
|
8766
|
+
await this.graph.deleteMemory(id);
|
|
8767
|
+
}
|
|
8768
|
+
trace.mark("databaseWriteMs", performance.now() - tGraph);
|
|
8769
|
+
}
|
|
7785
8770
|
trace.success({
|
|
7786
8771
|
provider: storage.name,
|
|
7787
8772
|
returnedCount: deleted,
|
|
7788
|
-
memoryIds:
|
|
8773
|
+
memoryIds: deletedIds.length > 0 ? deletedIds : null,
|
|
7789
8774
|
filters: "filter" in options ? options.filter : null,
|
|
7790
|
-
agentId: "filter" in options ? options.filter?.agent?.trim() ?? null : null
|
|
8775
|
+
agentId: "filter" in options ? options.filter?.agent?.trim() ?? null : null,
|
|
8776
|
+
extra: this.graph ? { graphCascade: deletedIds.length } : void 0
|
|
7791
8777
|
});
|
|
7792
8778
|
if (deleted > 0) {
|
|
7793
8779
|
this.emitChange({
|
|
@@ -7871,6 +8857,15 @@ var Wolbarg = class {
|
|
|
7871
8857
|
);
|
|
7872
8858
|
}
|
|
7873
8859
|
const deleted = await this.withWriteLock(async () => {
|
|
8860
|
+
if (this.graph) {
|
|
8861
|
+
const rows = await storage.listMemories({
|
|
8862
|
+
organization,
|
|
8863
|
+
includeArchived: true
|
|
8864
|
+
});
|
|
8865
|
+
for (const row of rows) {
|
|
8866
|
+
await this.graph.deleteMemory(row.id);
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
7874
8869
|
return storage.clearOrganization(organization);
|
|
7875
8870
|
});
|
|
7876
8871
|
trace.success({ provider: storage.name, returnedCount: deleted });
|
|
@@ -7887,8 +8882,10 @@ var Wolbarg = class {
|
|
|
7887
8882
|
await this.requireReady();
|
|
7888
8883
|
const provider = this.requireCheckpointProvider();
|
|
7889
8884
|
const source = this.requireMemoryDbPath();
|
|
8885
|
+
this.assertGraphCheckpointSupported("checkpoint");
|
|
7890
8886
|
const tCheckpoint = performance.now();
|
|
7891
8887
|
const meta2 = await provider.checkpoint(name, source, options);
|
|
8888
|
+
await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
|
|
7892
8889
|
trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
|
|
7893
8890
|
trace.success({
|
|
7894
8891
|
provider: provider.name,
|
|
@@ -7917,8 +8914,13 @@ var Wolbarg = class {
|
|
|
7917
8914
|
await this.storage.close();
|
|
7918
8915
|
storageClosed = true;
|
|
7919
8916
|
}
|
|
8917
|
+
const graphClosed = await this.closeGraphForSnapshot();
|
|
7920
8918
|
const tRollback = performance.now();
|
|
7921
8919
|
const meta2 = await provider.rollback(name, target);
|
|
8920
|
+
await this.restoreGraphAlongside(meta2.snapshotPath, "rollback");
|
|
8921
|
+
if (graphClosed) {
|
|
8922
|
+
await this.graph?.open();
|
|
8923
|
+
}
|
|
7922
8924
|
trace.mark("databaseWriteMs", performance.now() - tRollback);
|
|
7923
8925
|
await this.storage?.open();
|
|
7924
8926
|
storageClosed = false;
|
|
@@ -8004,6 +9006,7 @@ var Wolbarg = class {
|
|
|
8004
9006
|
try {
|
|
8005
9007
|
await this.requireReady();
|
|
8006
9008
|
const source = this.requireMemoryDbPath();
|
|
9009
|
+
this.assertGraphCheckpointSupported("export");
|
|
8007
9010
|
if (this.storage?.name === "sqlite") {
|
|
8008
9011
|
}
|
|
8009
9012
|
const result = await this.transfer.exportTo(
|
|
@@ -8011,6 +9014,7 @@ var Wolbarg = class {
|
|
|
8011
9014
|
source,
|
|
8012
9015
|
this.organization ?? void 0
|
|
8013
9016
|
);
|
|
9017
|
+
await this.snapshotGraphAlongside(result.path, "export");
|
|
8014
9018
|
trace.success({
|
|
8015
9019
|
provider: "sqlite",
|
|
8016
9020
|
extra: { path: result.path, sizeBytes: result.sizeBytes }
|
|
@@ -8036,10 +9040,16 @@ var Wolbarg = class {
|
|
|
8036
9040
|
await this.storage.close();
|
|
8037
9041
|
storageClosed = true;
|
|
8038
9042
|
}
|
|
9043
|
+
const graphClosed = await this.closeGraphForSnapshot();
|
|
9044
|
+
this.assertGraphCheckpointSupported("import");
|
|
8039
9045
|
const result = await this.transfer.importFrom(
|
|
8040
9046
|
exportPath,
|
|
8041
9047
|
target
|
|
8042
9048
|
);
|
|
9049
|
+
await this.restoreGraphAlongside(result.path, "import");
|
|
9050
|
+
if (graphClosed) {
|
|
9051
|
+
await this.graph?.open();
|
|
9052
|
+
}
|
|
8043
9053
|
await this.storage?.open();
|
|
8044
9054
|
storageClosed = false;
|
|
8045
9055
|
if (this.embeddingDimensions !== null) {
|
|
@@ -8093,6 +9103,8 @@ var Wolbarg = class {
|
|
|
8093
9103
|
this.pgListenBackend = null;
|
|
8094
9104
|
await this.telemetry.close().catch(() => void 0);
|
|
8095
9105
|
await this.checkpointProvider?.close().catch(() => void 0);
|
|
9106
|
+
await this.graph?.close().catch(() => void 0);
|
|
9107
|
+
this.graph = null;
|
|
8096
9108
|
if (this.storage) {
|
|
8097
9109
|
await this.storage.close();
|
|
8098
9110
|
}
|
|
@@ -8138,6 +9150,106 @@ var Wolbarg = class {
|
|
|
8138
9150
|
}
|
|
8139
9151
|
return this.checkpointProvider;
|
|
8140
9152
|
}
|
|
9153
|
+
requireGraph(method) {
|
|
9154
|
+
if (!this.graph) {
|
|
9155
|
+
throw new ProviderNotConfiguredError(
|
|
9156
|
+
"graph",
|
|
9157
|
+
method,
|
|
9158
|
+
"pass graph: sqliteGraph({ path }) or graph: neo4jGraph({ url, username, password })"
|
|
9159
|
+
);
|
|
9160
|
+
}
|
|
9161
|
+
return this.graph;
|
|
9162
|
+
}
|
|
9163
|
+
assertGraphCheckpointSupported(operation) {
|
|
9164
|
+
if (!this.graph) return;
|
|
9165
|
+
if (!this.graph.supportsFileSnapshot()) {
|
|
9166
|
+
throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
|
|
9167
|
+
}
|
|
9168
|
+
}
|
|
9169
|
+
graphSnapshotDir(alongsidePath) {
|
|
9170
|
+
return `${alongsidePath}.graph`;
|
|
9171
|
+
}
|
|
9172
|
+
async closeGraphForSnapshot() {
|
|
9173
|
+
if (!this.graph || !this.graph.supportsFileSnapshot()) return false;
|
|
9174
|
+
await this.graph.close();
|
|
9175
|
+
return true;
|
|
9176
|
+
}
|
|
9177
|
+
async snapshotGraphAlongside(alongsidePath, operation) {
|
|
9178
|
+
if (!this.graph) return;
|
|
9179
|
+
if (!this.graph.supportsFileSnapshot()) {
|
|
9180
|
+
throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
|
|
9181
|
+
}
|
|
9182
|
+
const dataPath = this.graph.getDataPath();
|
|
9183
|
+
if (!dataPath) return;
|
|
9184
|
+
const dest = this.graphSnapshotDir(alongsidePath);
|
|
9185
|
+
await this.graph.close();
|
|
9186
|
+
try {
|
|
9187
|
+
if (fs6__default.default.existsSync(dest)) {
|
|
9188
|
+
fs6__default.default.rmSync(dest, { recursive: true, force: true });
|
|
9189
|
+
}
|
|
9190
|
+
if (fs6__default.default.existsSync(dataPath)) {
|
|
9191
|
+
const stat = fs6__default.default.statSync(dataPath);
|
|
9192
|
+
if (stat.isDirectory()) {
|
|
9193
|
+
fs6__default.default.cpSync(dataPath, dest, { recursive: true });
|
|
9194
|
+
} else {
|
|
9195
|
+
fs6__default.default.mkdirSync(dest, { recursive: true });
|
|
9196
|
+
fs6__default.default.cpSync(dataPath, path7__default.default.join(dest, path7__default.default.basename(dataPath)));
|
|
9197
|
+
const parent = path7__default.default.dirname(dataPath);
|
|
9198
|
+
const base = path7__default.default.basename(dataPath);
|
|
9199
|
+
for (const entry of fs6__default.default.readdirSync(parent)) {
|
|
9200
|
+
if (entry === base) continue;
|
|
9201
|
+
if (entry.startsWith(base)) {
|
|
9202
|
+
fs6__default.default.cpSync(
|
|
9203
|
+
path7__default.default.join(parent, entry),
|
|
9204
|
+
path7__default.default.join(dest, entry),
|
|
9205
|
+
{ recursive: true }
|
|
9206
|
+
);
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
} finally {
|
|
9212
|
+
await this.graph.open();
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
async restoreGraphAlongside(alongsidePath, operation) {
|
|
9216
|
+
if (!this.graph) return;
|
|
9217
|
+
if (!this.graph.supportsFileSnapshot()) {
|
|
9218
|
+
throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
|
|
9219
|
+
}
|
|
9220
|
+
const dataPath = this.graph.getDataPath();
|
|
9221
|
+
if (!dataPath) return;
|
|
9222
|
+
const src = this.graphSnapshotDir(alongsidePath);
|
|
9223
|
+
if (!fs6__default.default.existsSync(src)) {
|
|
9224
|
+
return;
|
|
9225
|
+
}
|
|
9226
|
+
if (fs6__default.default.existsSync(dataPath)) {
|
|
9227
|
+
fs6__default.default.rmSync(dataPath, { recursive: true, force: true });
|
|
9228
|
+
}
|
|
9229
|
+
const entries = fs6__default.default.readdirSync(src);
|
|
9230
|
+
if (entries.length === 1 && entries[0] === path7__default.default.basename(dataPath)) {
|
|
9231
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(dataPath), { recursive: true });
|
|
9232
|
+
fs6__default.default.cpSync(path7__default.default.join(src, entries[0]), dataPath, { recursive: true });
|
|
9233
|
+
} else {
|
|
9234
|
+
fs6__default.default.mkdirSync(path7__default.default.dirname(dataPath), { recursive: true });
|
|
9235
|
+
fs6__default.default.cpSync(src, dataPath, { recursive: true });
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
async hydrateRelated(memoryId, options) {
|
|
9239
|
+
const graph = this.requireGraph("getRelated");
|
|
9240
|
+
const related = await graph.getRelated(memoryId, options);
|
|
9241
|
+
const { storage, organization } = await this.requireReady();
|
|
9242
|
+
const out = [];
|
|
9243
|
+
for (const stub of related) {
|
|
9244
|
+
const row = await storage.getMemoryById(stub.id, organization);
|
|
9245
|
+
if (row) {
|
|
9246
|
+
out.push(toMemoryRecord(row));
|
|
9247
|
+
} else {
|
|
9248
|
+
out.push(stub);
|
|
9249
|
+
}
|
|
9250
|
+
}
|
|
9251
|
+
return out;
|
|
9252
|
+
}
|
|
8141
9253
|
requireMemoryDbPath() {
|
|
8142
9254
|
if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
|
|
8143
9255
|
throw new ConfigurationError(
|
|
@@ -8147,7 +9259,7 @@ var Wolbarg = class {
|
|
|
8147
9259
|
}
|
|
8148
9260
|
);
|
|
8149
9261
|
}
|
|
8150
|
-
return
|
|
9262
|
+
return path7__default.default.isAbsolute(this.memoryDbPath) ? this.memoryDbPath : path7__default.default.resolve(process.cwd(), this.memoryDbPath);
|
|
8151
9263
|
}
|
|
8152
9264
|
};
|
|
8153
9265
|
function wolbarg(options) {
|
|
@@ -8243,10 +9355,16 @@ function sqliteTelemetry(url) {
|
|
|
8243
9355
|
function sqliteCheckpoint(directory) {
|
|
8244
9356
|
return new SqliteCheckpointProvider({ directory });
|
|
8245
9357
|
}
|
|
9358
|
+
function sqliteGraph(options) {
|
|
9359
|
+
return new SqliteGraphProvider(options);
|
|
9360
|
+
}
|
|
9361
|
+
function neo4jGraph(options) {
|
|
9362
|
+
return new Neo4jGraphProvider(options);
|
|
9363
|
+
}
|
|
8246
9364
|
function createTelemetryProvider(config) {
|
|
8247
9365
|
if (config.database.provider !== "sqlite") {
|
|
8248
9366
|
throw new ConfigurationError(
|
|
8249
|
-
`Unsupported telemetry provider "${config.database.provider}". Only "sqlite" is implemented
|
|
9367
|
+
`Unsupported telemetry provider "${config.database.provider}". Only "sqlite" is implemented. Telemetry supports sqlite or postgres only \u2014 not kuzu/neo4j.`
|
|
8250
9368
|
);
|
|
8251
9369
|
}
|
|
8252
9370
|
const url = config.database.url ?? config.database.connectionString ?? "";
|
|
@@ -8708,19 +9826,23 @@ exports.ConfigurationError = ConfigurationError;
|
|
|
8708
9826
|
exports.DatabaseError = DatabaseError;
|
|
8709
9827
|
exports.EmbeddingError = EmbeddingError;
|
|
8710
9828
|
exports.FixedChunkingStrategy = FixedChunkingStrategy;
|
|
9829
|
+
exports.GraphCheckpointNotSupportedError = GraphCheckpointNotSupportedError;
|
|
8711
9830
|
exports.HeadingChunkingStrategy = HeadingChunkingStrategy;
|
|
8712
9831
|
exports.InitializationError = InitializationError;
|
|
8713
9832
|
exports.LlmCompressionProvider = LlmCompressionProvider;
|
|
8714
9833
|
exports.MarkdownChunkingStrategy = MarkdownChunkingStrategy;
|
|
8715
9834
|
exports.MemoryNotFoundError = MemoryNotFoundError;
|
|
9835
|
+
exports.Neo4jGraphProvider = Neo4jGraphProvider;
|
|
8716
9836
|
exports.NoopTelemetryProvider = NoopTelemetryProvider;
|
|
8717
9837
|
exports.ParagraphChunkingStrategy = ParagraphChunkingStrategy;
|
|
8718
9838
|
exports.PostgresStorageProvider = PostgresStorageProvider;
|
|
8719
9839
|
exports.ProviderNotConfiguredError = ProviderNotConfiguredError;
|
|
9840
|
+
exports.SDK_VERSION = SDK_VERSION;
|
|
8720
9841
|
exports.SentenceChunkingStrategy = SentenceChunkingStrategy;
|
|
8721
9842
|
exports.SqliteCheckpointProvider = SqliteCheckpointProvider;
|
|
8722
9843
|
exports.SqliteDatabaseProvider = SqliteDatabaseProvider;
|
|
8723
9844
|
exports.SqliteEventDatabase = SqliteEventDatabase;
|
|
9845
|
+
exports.SqliteGraphProvider = SqliteGraphProvider;
|
|
8724
9846
|
exports.SqliteStorageProvider = SqliteStorageProvider;
|
|
8725
9847
|
exports.SqliteTelemetryProvider = SqliteTelemetryProvider;
|
|
8726
9848
|
exports.StorageLockedError = StorageLockedError;
|
|
@@ -8746,6 +9868,7 @@ exports.geminiVision = geminiVision;
|
|
|
8746
9868
|
exports.jinaReranker = jinaReranker;
|
|
8747
9869
|
exports.lmStudioEmbedding = lmStudioEmbedding;
|
|
8748
9870
|
exports.meta = meta;
|
|
9871
|
+
exports.neo4jGraph = neo4jGraph;
|
|
8749
9872
|
exports.ollamaEmbedding = ollamaEmbedding;
|
|
8750
9873
|
exports.ollamaLlm = ollamaLlm;
|
|
8751
9874
|
exports.openRouterEmbedding = openRouterEmbedding;
|
|
@@ -8762,6 +9885,7 @@ exports.runBenchmark = runBenchmark;
|
|
|
8762
9885
|
exports.sqlite = sqlite;
|
|
8763
9886
|
exports.sqliteCheckpoint = sqliteCheckpoint;
|
|
8764
9887
|
exports.sqliteConfig = sqliteConfig;
|
|
9888
|
+
exports.sqliteGraph = sqliteGraph;
|
|
8765
9889
|
exports.sqliteTelemetry = sqliteTelemetry;
|
|
8766
9890
|
exports.summarizeBenchmark = summarizeBenchmark;
|
|
8767
9891
|
exports.tesseract = tesseract;
|