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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import path5 from "node:path";
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, path7) {
402
+ function joinUrl(baseUrl, path8) {
389
403
  const base = baseUrl.replace(/\/+$/, "");
390
- const suffix = path7.startsWith("/") ? path7 : `/${path7}`;
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 path5.extname(filename).toLowerCase();
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: path5.basename(source.path),
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 path7 = jsonPath(field);
1321
- if (!path7) {
1337
+ const path8 = jsonPath(field);
1338
+ if (!path8) {
1322
1339
  return null;
1323
1340
  }
1324
- const extract = `json_extract(metadata_json, '${path7}')`;
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
- fs5.mkdirSync(path5.dirname(dbPath), { recursive: true });
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 = fs5.statSync(dbPath).size;
2608
+ let total = fs6.statSync(dbPath).size;
2558
2609
  for (const suffix of ["-wal", "-shm"]) {
2559
2610
  const side = `${dbPath}${suffix}`;
2560
- if (fs5.existsSync(side)) {
2561
- total += fs5.statSync(side).size;
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 path5.isAbsolute(connectionString) ? connectionString : path5.resolve(process.cwd(), connectionString);
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 path7 = parts.join(",");
3389
- return `metadata_json #>> '{${path7}}'`;
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
- var SDK_VERSION = "0.3.0";
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 (!fs5.existsSync(resolvedSource)) {
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
- fs5.mkdirSync(path5.dirname(resolvedExport), { recursive: true });
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
- fs5.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
5174
+ fs6.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
5120
5175
  return {
5121
5176
  path: dbExportPath,
5122
5177
  manifest,
5123
- sizeBytes: fs5.statSync(dbExportPath).size
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 : fs5.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
5129
- if (!fs5.existsSync(dbExportPath)) {
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 (fs5.existsSync(manifestPath)) {
5141
- manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
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 (fs5.existsSync(side)) {
5163
- fs5.rmSync(side, { force: true });
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
- fs5.mkdirSync(path5.dirname(destPath), { recursive: true });
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
- fs5.copyFileSync(sourcePath, destPath);
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 path5.isAbsolute(p) ? p : path5.resolve(process.cwd(), p);
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 in v0.3.0; PostgreSQL will be added later without changing application code.`,
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
- fs5.mkdirSync(path5.dirname(dbPath), { recursive: true });
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: ${describe(error)}`,
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: ${describe(error)}`,
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 path5.isAbsolute(url) ? url : path5.resolve(process.cwd(), url);
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 describe(error) {
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 ?? path5.resolve(process.cwd(), ".wolbarg", "checkpoints");
7058
+ this.directory = options?.directory ?? path7.resolve(process.cwd(), ".wolbarg", "checkpoints");
6216
7059
  }
6217
7060
  async open() {
6218
- fs5.mkdirSync(this.directory, { recursive: true });
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 (fs5.existsSync(metaPath)) {
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 (!fs5.existsSync(resolvedSource)) {
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 = fs5.statSync(snapshotPath);
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: SDK_VERSION2,
7098
+ sdkVersion: SDK_VERSION,
6256
7099
  provider: this.name,
6257
7100
  snapshotPath,
6258
7101
  sourcePath: resolvedSource,
6259
7102
  sizeBytes: stats.size
6260
7103
  };
6261
- fs5.writeFileSync(metaPath, JSON.stringify(meta2, null, 2), "utf8");
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
- fs5.mkdirSync(path5.dirname(resolvedTarget), { recursive: true });
7119
+ fs6.mkdirSync(path7.dirname(resolvedTarget), { recursive: true });
6277
7120
  for (const suffix of ["", "-wal", "-shm"]) {
6278
7121
  const side = `${resolvedTarget}${suffix}`;
6279
- if (fs5.existsSync(side)) {
6280
- fs5.rmSync(side, { force: true });
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 (fs5.existsSync(metaPath)) {
6292
- fs5.rmSync(metaPath, { force: true });
7134
+ if (fs6.existsSync(metaPath)) {
7135
+ fs6.rmSync(metaPath, { force: true });
6293
7136
  removed = true;
6294
7137
  }
6295
- if (fs5.existsSync(snapshotPath)) {
6296
- fs5.rmSync(snapshotPath, { force: true });
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 (!fs5.existsSync(this.directory)) {
7146
+ if (!fs6.existsSync(this.directory)) {
6304
7147
  return [];
6305
7148
  }
6306
- const files = fs5.readdirSync(this.directory).filter((f) => f.endsWith(".json"));
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 = fs5.readFileSync(path5.join(this.directory, file), "utf8");
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 (!fs5.existsSync(metaPath)) {
7163
+ if (!fs6.existsSync(metaPath)) {
6321
7164
  return null;
6322
7165
  }
6323
7166
  try {
6324
- return JSON.parse(fs5.readFileSync(metaPath, "utf8"));
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 path5.join(this.directory, `${sanitize(name)}.json`);
7234
+ return path7.join(this.directory, `${sanitize(name)}.json`);
6331
7235
  }
6332
7236
  snapshotPath(name) {
6333
- return path5.join(this.directory, `${sanitize(name)}.db`);
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 path5.isAbsolute(connectionString) ? connectionString : path5.resolve(process.cwd(), connectionString);
7260
+ return path7.isAbsolute(connectionString) ? connectionString : path7.resolve(process.cwd(), connectionString);
6357
7261
  }
6358
7262
  async function safeSqliteBackup(sourcePath, destPath) {
6359
- fs5.mkdirSync(path5.dirname(destPath), { recursive: true });
6360
- if (fs5.existsSync(destPath)) {
6361
- fs5.rmSync(destPath, { force: true });
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
- fs5.copyFileSync(sourcePath, destPath);
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 ok = await storage.deleteMemoryById(
7741
- options.id.trim(),
7742
- organization
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: "id" in options && options.id ? [options.id.trim()] : null,
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 path5.isAbsolute(this.memoryDbPath) ? this.memoryDbPath : path5.resolve(process.cwd(), this.memoryDbPath);
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 in v0.3.0.`
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