wiki-graph-sdk 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,8 +34,11 @@ __export(conversions_exports, {
34
34
  WikiGraphConversionManager: () => WikiGraphConversionManager
35
35
  });
36
36
  module.exports = __toCommonJS(conversions_exports);
37
- var import_promises2 = require("fs/promises");
38
- var import_wiki_graph_core2 = require("wiki-graph-core");
37
+ var import_crypto = require("crypto");
38
+ var import_fs = require("fs");
39
+ var import_promises3 = require("fs/promises");
40
+ var import_path4 = require("path");
41
+ var import_wiki_graph_core3 = require("wiki-graph-core");
39
42
 
40
43
  // src/llm.ts
41
44
  var import_anthropic = require("@ai-sdk/anthropic");
@@ -336,6 +339,14 @@ function decodeNodeResourceIdentity(identity, kind) {
336
339
  return void 0;
337
340
  }
338
341
  }
342
+ function getNodeResourcePath(resource) {
343
+ const brandedPath = getBrandedNodeResourcePath(resource, resource.kind);
344
+ if (brandedPath !== void 0) return brandedPath;
345
+ if (typeof resource === "object" && resource !== null && "path" in resource && typeof resource.path === "string") {
346
+ return resource.path;
347
+ }
348
+ throw new TypeError("Expected a Node File or Directory resource");
349
+ }
339
350
  function assertChildName(name) {
340
351
  if (name.length === 0 || name === "." || name === ".." || pathModule.isAbsolute(name) || name.includes("/") || name.includes("\\")) {
341
352
  throw new TypeError(`Directory child must be a relative name: ${name}`);
@@ -347,42 +358,42 @@ var NodeDatabaseConnection = class {
347
358
  }
348
359
  database;
349
360
  async close() {
350
- await new Promise((resolve3, reject) => {
361
+ await new Promise((resolve4, reject) => {
351
362
  this.database.close((error) => {
352
363
  if (error) reject(error);
353
- else resolve3();
364
+ else resolve4();
354
365
  });
355
366
  });
356
367
  }
357
368
  async execute(sql) {
358
- await new Promise((resolve3, reject) => {
369
+ await new Promise((resolve4, reject) => {
359
370
  this.database.exec(sql, (error) => {
360
371
  if (error) reject(error);
361
- else resolve3();
372
+ else resolve4();
362
373
  });
363
374
  });
364
375
  }
365
376
  async queryAll(sql, params = []) {
366
- return await new Promise((resolve3, reject) => {
377
+ return await new Promise((resolve4, reject) => {
367
378
  this.database.all(sql, [...params], (error, rows) => {
368
379
  if (error) reject(error);
369
- else resolve3(rows);
380
+ else resolve4(rows);
370
381
  });
371
382
  });
372
383
  }
373
384
  async queryOne(sql, params = []) {
374
- return await new Promise((resolve3, reject) => {
385
+ return await new Promise((resolve4, reject) => {
375
386
  this.database.get(sql, [...params], (error, row) => {
376
387
  if (error) reject(error);
377
- else resolve3(row);
388
+ else resolve4(row);
378
389
  });
379
390
  });
380
391
  }
381
392
  async run(sql, params = []) {
382
- await new Promise((resolve3, reject) => {
393
+ await new Promise((resolve4, reject) => {
383
394
  this.database.run(sql, [...params], (error) => {
384
395
  if (error) reject(error);
385
- else resolve3();
396
+ else resolve4();
386
397
  });
387
398
  });
388
399
  }
@@ -396,10 +407,10 @@ async function openNativeNodeDatabase(file, flags) {
396
407
  if (path === void 0) {
397
408
  throw new TypeError("The Node database adapter requires a NodeFile");
398
409
  }
399
- return await new Promise((resolve3, reject) => {
410
+ return await new Promise((resolve4, reject) => {
400
411
  const database = new nodeSqlite3.Database(path, flags, (error) => {
401
412
  if (error) reject(error);
402
- else resolve3(database);
413
+ else resolve4(database);
403
414
  });
404
415
  });
405
416
  }
@@ -408,19 +419,19 @@ async function openNodeZip(file) {
408
419
  if (path === void 0) {
409
420
  throw new TypeError("The Node ZIP adapter requires a NodeFile");
410
421
  }
411
- const zipFile = await new Promise((resolve3, reject) => {
422
+ const zipFile = await new Promise((resolve4, reject) => {
412
423
  yauzl.open(
413
424
  path,
414
425
  { autoClose: false, lazyEntries: true },
415
426
  (error, opened) => {
416
427
  if (error || opened === void 0)
417
428
  reject(error ?? new Error("Cannot open ZIP"));
418
- else resolve3(opened);
429
+ else resolve4(opened);
419
430
  }
420
431
  );
421
432
  });
422
433
  const entries = await new Promise(
423
- (resolve3, reject) => {
434
+ (resolve4, reject) => {
424
435
  const discovered = /* @__PURE__ */ new Map();
425
436
  const rejectAndClose = (error) => {
426
437
  zipFile.close();
@@ -430,7 +441,7 @@ async function openNodeZip(file) {
430
441
  };
431
442
  const resolveEntries = () => {
432
443
  zipFile.off("error", rejectAndClose);
433
- resolve3(discovered);
444
+ resolve4(discovered);
434
445
  };
435
446
  zipFile.on("entry", (entry) => {
436
447
  if (!entry.fileName.endsWith("/")) {
@@ -540,12 +551,12 @@ async function collectDecodedNodeStreamRange(input, offset, length) {
540
551
  return Buffer.concat(chunks, collected);
541
552
  }
542
553
  async function openNodeZipEntryStream(zipFile, entry, options) {
543
- return await new Promise((resolve3, reject) => {
554
+ return await new Promise((resolve4, reject) => {
544
555
  const callback = (error, opened) => {
545
556
  if (error || opened === void 0) {
546
557
  reject(error ?? new Error(`Cannot read ZIP entry: ${entry.fileName}`));
547
558
  } else {
548
- resolve3(opened);
559
+ resolve4(opened);
549
560
  }
550
561
  };
551
562
  if (options === void 0) zipFile.openReadStream(entry, callback);
@@ -676,11 +687,11 @@ async function writeNodeStream(input, writer) {
676
687
  );
677
688
  }
678
689
  async function collectNodeStream(input) {
679
- return await new Promise((resolve3, reject) => {
690
+ return await new Promise((resolve4, reject) => {
680
691
  const chunks = [];
681
692
  input.on("data", (chunk) => chunks.push(chunk));
682
693
  input.once("error", reject);
683
- input.once("end", () => resolve3(Buffer.concat(chunks)));
694
+ input.once("end", () => resolve4(Buffer.concat(chunks)));
684
695
  });
685
696
  }
686
697
  var nodeWikiGraphPlatform = {
@@ -1233,6 +1244,107 @@ function resolveWikiGraphRuntimePath(path) {
1233
1244
  return (0, import_path2.isAbsolute)(path) ? path : (0, import_path2.resolve)(context.cwd, path);
1234
1245
  }
1235
1246
 
1247
+ // src/archive/target.ts
1248
+ var import_promises2 = require("fs/promises");
1249
+ var import_path3 = require("path");
1250
+ var import_wiki_graph_core2 = require("wiki-graph-core");
1251
+ var WikiGraphArchiveOwnershipError = class extends Error {
1252
+ code = "WIKI_GRAPH_ARCHIVE_OWNERSHIP_MISMATCH";
1253
+ constructor(message) {
1254
+ super(message);
1255
+ this.name = "WikiGraphArchiveOwnershipError";
1256
+ }
1257
+ };
1258
+ async function assertStandaloneWikiGraphArchivePath(inputPath) {
1259
+ const path = resolveWikiGraphRuntimePath(inputPath);
1260
+ const candidate = await canonicalPath(path);
1261
+ const candidateIdentity = await readFileIdentity(path);
1262
+ const libraries = await (0, import_wiki_graph_core2.listWikiGraphLibraries)();
1263
+ const matches = await findMatchingMembers(
1264
+ libraries,
1265
+ candidate,
1266
+ candidateIdentity
1267
+ );
1268
+ if (matches.length > 0) {
1269
+ const match = requireUnambiguousManagedMatch(path, matches);
1270
+ throw new WikiGraphArchiveOwnershipError(
1271
+ `Standalone archive path belongs to registered library ${(0, import_wiki_graph_core2.formatWikiGraphLibraryUri)(
1272
+ match.library.isDefault ? void 0 : match.library.publicId
1273
+ )}: ${path}. Use the library UUID URI ${match.archive.uri} instead.`
1274
+ );
1275
+ }
1276
+ for (const library of libraries) {
1277
+ const rootPath = getNodeResourcePath(library.folder);
1278
+ const root = await canonicalPath(rootPath);
1279
+ if (!isWithin(root, candidate)) continue;
1280
+ throw new WikiGraphArchiveOwnershipError(
1281
+ `Standalone archive path belongs to registered library ${(0, import_wiki_graph_core2.formatWikiGraphLibraryUri)(
1282
+ library.isDefault ? void 0 : library.publicId
1283
+ )}: ${path}. Scan the library, then address the archive by its library UUID URI.`
1284
+ );
1285
+ }
1286
+ return path;
1287
+ }
1288
+ async function canonicalPath(path) {
1289
+ try {
1290
+ return await (0, import_promises2.realpath)(path);
1291
+ } catch {
1292
+ const absolute = (0, import_path3.resolve)(path);
1293
+ const parent = (0, import_path3.dirname)(absolute);
1294
+ return parent === absolute ? absolute : (0, import_path3.join)(await canonicalPath(parent), (0, import_path3.basename)(absolute));
1295
+ }
1296
+ }
1297
+ function isWithin(root, candidate) {
1298
+ const child = (0, import_path3.relative)(root, candidate);
1299
+ return child === "" || !child.startsWith("..") && !(0, import_path3.isAbsolute)(child);
1300
+ }
1301
+ async function findMatchingMembers(libraries, candidate, candidateIdentity) {
1302
+ const members = [];
1303
+ for (const library of libraries) {
1304
+ const archives = await (0, import_wiki_graph_core2.listWikiGraphLibraryArchives)({
1305
+ isDefault: library.isDefault,
1306
+ kind: "scope",
1307
+ ...library.isDefault ? {} : { publicId: library.publicId }
1308
+ });
1309
+ for (const archive of archives) {
1310
+ if (archive.file === void 0) continue;
1311
+ members.push({
1312
+ archive,
1313
+ library,
1314
+ memberPath: getNodeResourcePath(archive.file)
1315
+ });
1316
+ }
1317
+ }
1318
+ const exact = [];
1319
+ for (const member of members) {
1320
+ if (await canonicalPath(member.memberPath) === candidate) {
1321
+ exact.push(member);
1322
+ }
1323
+ }
1324
+ if (exact.length > 0 || candidateIdentity === void 0) return exact;
1325
+ const identityMatches = [];
1326
+ for (const member of members) {
1327
+ if (await readFileIdentity(member.memberPath) === candidateIdentity) {
1328
+ identityMatches.push(member);
1329
+ }
1330
+ }
1331
+ return identityMatches;
1332
+ }
1333
+ function requireUnambiguousManagedMatch(path, matches) {
1334
+ if (matches.length === 1) return matches[0];
1335
+ throw new WikiGraphArchiveOwnershipError(
1336
+ `Archive file identity is ambiguous across managed archives: ${path}. Use an explicit library UUID URI (${matches.map((match) => match.archive.uri).join(", ")}).`
1337
+ );
1338
+ }
1339
+ async function readFileIdentity(path) {
1340
+ try {
1341
+ const value = await (0, import_promises2.stat)(path);
1342
+ return `${value.dev}:${value.ino}`;
1343
+ } catch {
1344
+ return void 0;
1345
+ }
1346
+ }
1347
+
1236
1348
  // src/conversions.ts
1237
1349
  var WikiGraphConversionManager = class {
1238
1350
  #runtime;
@@ -1247,6 +1359,9 @@ var WikiGraphConversionManager = class {
1247
1359
  if (options.targetStage !== void 0 && options.output.format !== "wikg") {
1248
1360
  throw new Error("targetStage is only supported for wikg output.");
1249
1361
  }
1362
+ if (options.output.format === "pcex") {
1363
+ throw new Error("pcex is supported only as a conversion input.");
1364
+ }
1250
1365
  if (options.targetStage !== void 0 && options.input.format === "wikg") {
1251
1366
  throw new Error(
1252
1367
  "targetStage is only supported when creating a wikg archive."
@@ -1257,7 +1372,7 @@ var WikiGraphConversionManager = class {
1257
1372
  const config = await loadWikiGraphRuntimeConfig({
1258
1373
  ...options.llmJSON === void 0 ? {} : { llmJSON: options.llmJSON }
1259
1374
  });
1260
- const app = new import_wiki_graph_core2.WikiGraph({
1375
+ const app = new import_wiki_graph_core3.WikiGraph({
1261
1376
  ...options.verbose === true ? { verbose: true } : {},
1262
1377
  ...requiresLLM ? { llm: buildWikiGraphLLMOptions(config) } : {}
1263
1378
  });
@@ -1265,7 +1380,17 @@ var WikiGraphConversionManager = class {
1265
1380
  options.digestDirectory,
1266
1381
  requiresDigest
1267
1382
  );
1383
+ let chapterCount;
1384
+ let chapterPaths;
1268
1385
  const write = async (archive) => {
1386
+ if (requiresDigest) await options.onSourceImported?.();
1387
+ if (typeof archive.readToc === "function") {
1388
+ const toc = await archive.readToc();
1389
+ chapterCount = toc === void 0 ? void 0 : countTocChapters(toc.items);
1390
+ if (options.output.format === "wikg") {
1391
+ chapterPaths = toc === void 0 ? [] : collectTocChapterPaths(toc.items);
1392
+ }
1393
+ }
1269
1394
  await writeArchive(archive, options.output.path, options.output.format);
1270
1395
  };
1271
1396
  if (options.input.format === "wikg") {
@@ -1273,7 +1398,9 @@ var WikiGraphConversionManager = class {
1273
1398
  throw new Error("wikg input requires a file path.");
1274
1399
  }
1275
1400
  await app.openSession(
1276
- new NodeFile(resolveWikiGraphRuntimePath(options.input.path)),
1401
+ new NodeFile(
1402
+ await assertStandaloneWikiGraphArchivePath(options.input.path)
1403
+ ),
1277
1404
  write
1278
1405
  );
1279
1406
  } else if ("stream" in options.input) {
@@ -1298,16 +1425,29 @@ var WikiGraphConversionManager = class {
1298
1425
  };
1299
1426
  if (options.input.format === "epub") {
1300
1427
  await app.digestEpubSession(digestOptions, write);
1428
+ } else if (options.input.format === "pcex") {
1429
+ await app.digestPcexSession(
1430
+ {
1431
+ ...digestOptions,
1432
+ pdfDigest: options.input.sourcePdf.digest,
1433
+ ...options.input.sourcePdf.name === void 0 ? {} : { pdfName: options.input.sourcePdf.name }
1434
+ },
1435
+ write
1436
+ );
1301
1437
  } else if (options.input.format === "markdown") {
1302
1438
  await app.digestMarkdownSession(digestOptions, write);
1303
1439
  } else {
1304
1440
  await app.digestTxtSession(digestOptions, write);
1305
1441
  }
1306
1442
  }
1443
+ const sourceArtifact = await resolveSourceArtifact(options.input);
1307
1444
  return {
1445
+ ...chapterCount === void 0 ? {} : { chapterCount },
1446
+ ...chapterPaths === void 0 ? {} : { chapterPaths },
1308
1447
  inputFormat: options.input.format,
1309
1448
  outputFormat: options.output.format,
1310
- outputPath: options.output.path
1449
+ outputPath: options.output.path,
1450
+ ...sourceArtifact === void 0 ? {} : { sourceArtifact }
1311
1451
  };
1312
1452
  }
1313
1453
  };
@@ -1316,15 +1456,71 @@ async function prepareDigestDirectory(path, required) {
1316
1456
  if (!required || normalized === void 0 || normalized === "")
1317
1457
  return void 0;
1318
1458
  const resolved = resolveWikiGraphRuntimePath(normalized);
1319
- await (0, import_promises2.rm)(resolved, { force: true, recursive: true });
1459
+ await (0, import_promises3.rm)(resolved, { force: true, recursive: true });
1320
1460
  return new NodeDirectory(resolved);
1321
1461
  }
1322
1462
  async function writeArchive(archive, path, format) {
1323
- const file = new NodeFile(resolveWikiGraphRuntimePath(path));
1463
+ const outputPath = format === "wikg" ? await assertStandaloneWikiGraphArchivePath(path) : resolveWikiGraphRuntimePath(path);
1464
+ const file = new NodeFile(outputPath);
1324
1465
  if (format === "epub") await archive.exportEpub(file);
1325
1466
  else if (format === "wikg") await archive.saveAs(file);
1467
+ else if (format === "pcex") throw new Error("pcex output is not supported.");
1326
1468
  else await archive.exportText(file);
1327
1469
  }
1470
+ async function resolveSourceArtifact(input) {
1471
+ if (!("path" in input) || input.format === "wikg") return void 0;
1472
+ if (input.format === "pcex") {
1473
+ return {
1474
+ digest: input.sourcePdf.digest.trim().toLowerCase(),
1475
+ mediaType: "application/pdf",
1476
+ ...input.sourcePdf.name === void 0 ? {} : { name: input.sourcePdf.name }
1477
+ };
1478
+ }
1479
+ if (input.format !== "epub") return void 0;
1480
+ const path = resolveWikiGraphRuntimePath(input.path);
1481
+ const hash = (0, import_crypto.createHash)("sha256");
1482
+ try {
1483
+ for await (const chunk of (0, import_fs.createReadStream)(path)) {
1484
+ hash.update(chunk);
1485
+ }
1486
+ } catch (error) {
1487
+ if (isNodeErrorCode(error, "ENOENT")) return void 0;
1488
+ throw error;
1489
+ }
1490
+ return {
1491
+ digest: hash.digest("hex"),
1492
+ mediaType: "application/epub+zip",
1493
+ name: (0, import_path4.basename)(path)
1494
+ };
1495
+ }
1496
+ function isNodeErrorCode(error, code) {
1497
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
1498
+ }
1499
+ function countTocChapters(items) {
1500
+ let count = 0;
1501
+ for (const item of items) {
1502
+ if (item.serialId !== void 0) count += 1;
1503
+ count += countTocChapters(
1504
+ item.children
1505
+ );
1506
+ }
1507
+ return count;
1508
+ }
1509
+ function collectTocChapterPaths(items, ancestorKeys = []) {
1510
+ const paths = [];
1511
+ for (const item of items) {
1512
+ const key = item.key ?? `chapter-${item.serialId ?? "group"}`;
1513
+ const chapterPath = [...ancestorKeys, key];
1514
+ if (item.serialId !== void 0) paths.push(chapterPath.join("/"));
1515
+ paths.push(
1516
+ ...collectTocChapterPaths(
1517
+ item.children,
1518
+ chapterPath
1519
+ )
1520
+ );
1521
+ }
1522
+ return paths;
1523
+ }
1328
1524
  // Annotate the CommonJS export names for ESM import in node:
1329
1525
  0 && (module.exports = {
1330
1526
  WikiGraphConversionManager