wiki-graph-sdk 0.9.0 → 0.9.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.
Files changed (51) hide show
  1. package/dist/archives.cjs +306 -135
  2. package/dist/archives.cjs.map +1 -1
  3. package/dist/archives.js +315 -140
  4. package/dist/archives.js.map +1 -1
  5. package/dist/conversions.cjs +268 -99
  6. package/dist/conversions.cjs.map +1 -1
  7. package/dist/conversions.js +274 -103
  8. package/dist/conversions.js.map +1 -1
  9. package/dist/default-worker.cjs +258 -101
  10. package/dist/default-worker.cjs.map +1 -1
  11. package/dist/default-worker.js +264 -105
  12. package/dist/default-worker.js.map +1 -1
  13. package/dist/embedding.cjs +214 -54
  14. package/dist/embedding.cjs.map +1 -1
  15. package/dist/embedding.js +214 -54
  16. package/dist/embedding.js.map +1 -1
  17. package/dist/gc.cjs +214 -54
  18. package/dist/gc.cjs.map +1 -1
  19. package/dist/gc.js +214 -54
  20. package/dist/gc.js.map +1 -1
  21. package/dist/index.cjs +343 -165
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +357 -174
  25. package/dist/index.js.map +1 -1
  26. package/dist/local-config.cjs +214 -54
  27. package/dist/local-config.cjs.map +1 -1
  28. package/dist/local-config.js +214 -54
  29. package/dist/local-config.js.map +1 -1
  30. package/dist/maintenance.cjs +264 -97
  31. package/dist/maintenance.cjs.map +1 -1
  32. package/dist/maintenance.js +270 -101
  33. package/dist/maintenance.js.map +1 -1
  34. package/dist/node-platform.cjs +214 -54
  35. package/dist/node-platform.cjs.map +1 -1
  36. package/dist/node-platform.d.ts +2 -0
  37. package/dist/node-platform.js +214 -54
  38. package/dist/node-platform.js.map +1 -1
  39. package/dist/runtime-config.cjs +214 -54
  40. package/dist/runtime-config.cjs.map +1 -1
  41. package/dist/runtime-config.js +214 -54
  42. package/dist/runtime-config.js.map +1 -1
  43. package/dist/stage.cjs +214 -54
  44. package/dist/stage.cjs.map +1 -1
  45. package/dist/stage.js +214 -54
  46. package/dist/stage.js.map +1 -1
  47. package/dist/worker.cjs +257 -96
  48. package/dist/worker.cjs.map +1 -1
  49. package/dist/worker.js +263 -100
  50. package/dist/worker.js.map +1 -1
  51. package/package.json +2 -2
@@ -115,6 +115,15 @@ var NodeFile = class {
115
115
  this.name = name;
116
116
  }
117
117
  path;
118
+ async getPhysicalIdentity() {
119
+ const value = await fsPromises.stat(this.path).catch((error) => {
120
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")
121
+ return void 0;
122
+ throw error;
123
+ });
124
+ if (value === void 0) return void 0;
125
+ return `${value.dev}:${value.ino}`;
126
+ }
118
127
  kind = "file";
119
128
  [NODE_RESOURCE_KIND] = "file";
120
129
  identity;
@@ -239,6 +248,15 @@ var NodeDirectory = class _NodeDirectory {
239
248
  this.name = pathModule.basename(path);
240
249
  }
241
250
  path;
251
+ async getPhysicalIdentity() {
252
+ const value = await fsPromises.stat(this.path).catch((error) => {
253
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")
254
+ return void 0;
255
+ throw error;
256
+ });
257
+ if (value === void 0) return void 0;
258
+ return `${value.dev}:${value.ino}`;
259
+ }
242
260
  kind = "directory";
243
261
  [NODE_RESOURCE_KIND] = "directory";
244
262
  identity;
@@ -248,8 +266,10 @@ var NodeDirectory = class _NodeDirectory {
248
266
  const file = new NodeFile(pathModule.join(this.path, name));
249
267
  try {
250
268
  return (await fsPromises.stat(file.path)).isFile() ? file : void 0;
251
- } catch {
252
- return void 0;
269
+ } catch (error) {
270
+ if (typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
271
+ return void 0;
272
+ throw error;
253
273
  }
254
274
  }
255
275
  async getLastModified() {
@@ -267,8 +287,10 @@ var NodeDirectory = class _NodeDirectory {
267
287
  const directory = new _NodeDirectory(pathModule.join(this.path, name));
268
288
  try {
269
289
  return (await fsPromises.stat(directory.path)).isDirectory() ? directory : void 0;
270
- } catch {
271
- return void 0;
290
+ } catch (error) {
291
+ if (typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
292
+ return void 0;
293
+ throw error;
272
294
  }
273
295
  }
274
296
  async list() {
@@ -392,14 +414,23 @@ async function openNativeNodeDatabase(file, flags) {
392
414
  });
393
415
  });
394
416
  }
395
- async function openNodeZip(file) {
417
+ async function openNodeZip(file, options = {}) {
396
418
  const path = getBrandedNodeResourcePath(file, "file");
397
419
  if (path === void 0) {
398
420
  throw new TypeError("The Node ZIP adapter requires a NodeFile");
399
421
  }
400
- const zipFile = await new Promise((resolve4, reject) => {
401
- yauzl.open(
402
- path,
422
+ const handle = await fsPromises.open(path, "r");
423
+ let size;
424
+ try {
425
+ size = (await handle.stat()).size;
426
+ } catch (error) {
427
+ await handle.close();
428
+ throw error;
429
+ }
430
+ const open2 = () => new Promise((resolve4, reject) => {
431
+ yauzl.fromRandomAccessReader(
432
+ new NodeZipSource(handle),
433
+ size,
403
434
  { autoClose: false, lazyEntries: true },
404
435
  (error, opened) => {
405
436
  if (error || opened === void 0)
@@ -408,63 +439,192 @@ async function openNodeZip(file) {
408
439
  }
409
440
  );
410
441
  });
411
- const entries = await new Promise(
412
- (resolve4, reject) => {
413
- const discovered = /* @__PURE__ */ new Map();
414
- const rejectAndClose = (error) => {
415
- zipFile.close();
416
- reject(
417
- error instanceof Error ? error : new Error("Cannot scan ZIP central directory")
418
- );
419
- };
420
- const resolveEntries = () => {
421
- zipFile.off("error", rejectAndClose);
422
- resolve4(discovered);
423
- };
424
- zipFile.on("entry", (entry) => {
425
- if (!entry.fileName.endsWith("/")) {
426
- discovered.set(entry.fileName, entry);
427
- }
428
- zipFile.readEntry();
429
- });
430
- zipFile.once("error", rejectAndClose);
431
- zipFile.once("end", resolveEntries);
432
- zipFile.readEntry();
433
- }
434
- );
442
+ try {
443
+ (await open2()).close();
444
+ } catch (error) {
445
+ await handle.close();
446
+ throw error;
447
+ }
435
448
  let closed = false;
449
+ let pending = Promise.resolve();
450
+ let closing;
451
+ const run = (operation) => {
452
+ if (closed)
453
+ return Promise.reject(new Error("Cannot read a closed ZIP archive"));
454
+ const result = pending.then(async () => {
455
+ const zip = await open2();
456
+ try {
457
+ return await operation(zip);
458
+ } finally {
459
+ zip.close();
460
+ }
461
+ });
462
+ pending = result.then(
463
+ () => void 0,
464
+ () => void 0
465
+ );
466
+ return result;
467
+ };
468
+ const withEntry = (name, operation) => run(async (zip) => {
469
+ let exact;
470
+ let alias;
471
+ let aliasCollision = false;
472
+ await scanNodeZip(zip, (entry) => {
473
+ if (entry.fileName === name) exact = entry;
474
+ if (normalizeNodeZipName(entry.fileName) === name) {
475
+ if (alias !== void 0 && alias.fileName !== entry.fileName)
476
+ aliasCollision = true;
477
+ alias = entry;
478
+ }
479
+ });
480
+ if ((exact === void 0 || options.entryNameMode === "normalized") && aliasCollision) {
481
+ let bestPosition = -1;
482
+ const candidates = await open2();
483
+ try {
484
+ await scanNodeZip(candidates, async (entry) => {
485
+ if (normalizeNodeZipName(entry.fileName) !== name) return;
486
+ const probe = await open2();
487
+ let position = 0;
488
+ try {
489
+ await scanNodeZip(probe, (candidate) => {
490
+ if (candidate.fileName === entry.fileName) return false;
491
+ position++;
492
+ });
493
+ } finally {
494
+ probe.close();
495
+ }
496
+ if (position >= bestPosition) {
497
+ bestPosition = position;
498
+ alias = entry;
499
+ }
500
+ });
501
+ return await operation(candidates, alias);
502
+ } finally {
503
+ candidates.close();
504
+ }
505
+ }
506
+ return await operation(
507
+ zip,
508
+ options.entryNameMode === "normalized" ? alias : exact ?? alias
509
+ );
510
+ });
436
511
  return {
437
512
  close: () => {
438
- if (!closed) zipFile.close();
439
513
  closed = true;
440
- return Promise.resolve();
514
+ closing ??= pending.then(() => handle.close());
515
+ return closing;
441
516
  },
442
- copyEntry: async (name, target) => {
443
- if (closed) throw new Error("Cannot read a closed ZIP archive");
444
- const entry = entries.get(name);
517
+ copyEntry: (name, target) => withEntry(name, async (zip, entry) => {
445
518
  if (entry === void 0) return false;
446
- await copyNodeZipEntry(zipFile, entry, target);
519
+ await copyNodeZipEntry(zip, entry, target);
447
520
  return true;
448
- },
449
- getEntrySize: (name) => {
450
- if (closed) throw new Error("Cannot read a closed ZIP archive");
451
- return Promise.resolve(entries.get(name)?.uncompressedSize);
452
- },
453
- listEntries: () => Promise.resolve([...entries.keys()]),
454
- readEntryRange: async (name, offset, length) => {
455
- if (closed) throw new Error("Cannot read a closed ZIP archive");
456
- const entry = entries.get(name);
521
+ }),
522
+ getEntrySize: (name) => withEntry(
523
+ name,
524
+ (_zip, entry) => Promise.resolve(entry?.uncompressedSize)
525
+ ),
526
+ listEntries: () => run(async (zip) => {
527
+ const names = /* @__PURE__ */ new Set();
528
+ await scanNodeZip(zip, (entry) => {
529
+ names.add(entry.fileName);
530
+ });
531
+ return [...names];
532
+ }),
533
+ readEntryRange: (name, offset, length) => withEntry(name, async (zip, entry) => {
457
534
  if (entry === void 0) return void 0;
458
- return await readNodeZipEntryRange(zipFile, entry, offset, length);
459
- },
460
- readEntry: async (name) => {
461
- if (closed) throw new Error("Cannot read a closed ZIP archive");
462
- const entry = entries.get(name);
535
+ return await readNodeZipEntryRange(zip, entry, offset, length);
536
+ }),
537
+ readEntry: (name) => withEntry(name, async (zip, entry) => {
463
538
  if (entry === void 0) return void 0;
464
- return await readNodeZipEntry(zipFile, entry);
465
- }
539
+ return await readNodeZipEntry(zip, entry);
540
+ })
466
541
  };
467
542
  }
543
+ var NodeZipSource = class extends yauzl.RandomAccessReader {
544
+ constructor(handle) {
545
+ super();
546
+ this.handle = handle;
547
+ }
548
+ handle;
549
+ read(buffer, offset, length, position, callback) {
550
+ const read2 = (written) => {
551
+ if (written === length) {
552
+ callback(null);
553
+ return;
554
+ }
555
+ fs.read(
556
+ this.handle.fd,
557
+ buffer,
558
+ offset + written,
559
+ length - written,
560
+ position + written,
561
+ (error, bytesRead) => {
562
+ if (error) callback(error);
563
+ else if (bytesRead === 0)
564
+ callback(new Error("Unexpected end of ZIP file"));
565
+ else read2(written + bytesRead);
566
+ }
567
+ );
568
+ };
569
+ read2(0);
570
+ }
571
+ _readStreamForRange(start, end) {
572
+ const handle = this.handle;
573
+ return Readable.from(
574
+ (async function* () {
575
+ for (let position = start; position < end; ) {
576
+ const buffer = Buffer.allocUnsafe(
577
+ Math.min(64 * 1024, end - position)
578
+ );
579
+ const { bytesRead } = await handle.read(
580
+ buffer,
581
+ 0,
582
+ buffer.length,
583
+ position
584
+ );
585
+ if (bytesRead === 0) return;
586
+ position += bytesRead;
587
+ yield buffer.subarray(0, bytesRead);
588
+ }
589
+ })()
590
+ );
591
+ }
592
+ };
593
+ function normalizeNodeZipName(name) {
594
+ const normalized = name.replaceAll("\\", "/").trim().split("/").filter((part) => part !== "" && part !== ".").join("/");
595
+ return normalized.split("/").includes("..") ? void 0 : normalized;
596
+ }
597
+ async function scanNodeZip(zip, visit) {
598
+ await new Promise((resolve4, reject) => {
599
+ const cleanup = () => {
600
+ zip.off("entry", onEntry);
601
+ zip.off("end", onEnd);
602
+ zip.off("error", onError);
603
+ };
604
+ const onError = (error) => {
605
+ cleanup();
606
+ reject(error);
607
+ };
608
+ const onEnd = () => {
609
+ cleanup();
610
+ resolve4();
611
+ };
612
+ const onEntry = (entry) => {
613
+ void Promise.resolve().then(() => entry.fileName.endsWith("/") ? void 0 : visit(entry)).then((keepReading) => {
614
+ if (keepReading === false) onEnd();
615
+ else zip.readEntry();
616
+ }).catch(
617
+ (error) => onError(
618
+ error instanceof Error ? error : new Error("Cannot scan ZIP", { cause: error })
619
+ )
620
+ );
621
+ };
622
+ zip.on("entry", onEntry);
623
+ zip.once("end", onEnd);
624
+ zip.once("error", onError);
625
+ zip.readEntry();
626
+ });
627
+ }
468
628
  async function copyNodeZipEntry(zipFile, entry, target) {
469
629
  const writer = await target.openWriter();
470
630
  try {
@@ -1235,17 +1395,29 @@ function resolveWikiGraphRuntimePath(path) {
1235
1395
 
1236
1396
  // src/archive/target.ts
1237
1397
  import { realpath, stat as stat2 } from "fs/promises";
1238
- import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join3, relative, resolve as resolve3 } from "path";
1398
+ import { basename as basename2, dirname as dirname2, join as join3, resolve as resolve3 } from "path";
1239
1399
  import {
1240
1400
  formatLocatedWikiGraphUri,
1241
1401
  formatWikiGraphLibraryUri,
1242
- listWikiGraphLibraries,
1243
- listWikiGraphLibraryArchives,
1402
+ findWikiGraphLibraryByPhysicalIdentity,
1403
+ findWikiGraphLibraryArchivePhysicalAliases,
1244
1404
  parseLocatedWikiGraphUri,
1245
1405
  parseWikiGraphLibraryUri,
1246
1406
  resolveWikiGraphLibrary,
1247
- resolveWikiGraphLibraryArchiveFile
1407
+ resolveWikiGraphLibraryArchiveFile,
1408
+ isWikiGraphSchemaVersionError,
1409
+ WikiGraphSchemaVersionError
1248
1410
  } from "wiki-graph-core";
1411
+ function rethrowArchiveSchemaError(error, target) {
1412
+ if (isWikiGraphSchemaVersionError(error) && error.target.kind === "archive") {
1413
+ throw new WikiGraphSchemaVersionError(
1414
+ target.kind === "standalone" ? { kind: "archive", path: resolveWikiGraphRuntimePath(target.path) } : { kind: "library", uri: target.uri.split("/arc/")[0] },
1415
+ error.actualVersion,
1416
+ error.supportedVersion
1417
+ );
1418
+ }
1419
+ throw error;
1420
+ }
1249
1421
  var WikiGraphArchiveOwnershipError = class extends Error {
1250
1422
  code = "WIKI_GRAPH_ARCHIVE_OWNERSHIP_MISMATCH";
1251
1423
  constructor(message) {
@@ -1257,12 +1429,7 @@ async function assertStandaloneWikiGraphArchivePath(inputPath) {
1257
1429
  const path = resolveWikiGraphRuntimePath(inputPath);
1258
1430
  const candidate = await canonicalPath(path);
1259
1431
  const candidateIdentity = await readFileIdentity(path);
1260
- const libraries = await listWikiGraphLibraries();
1261
- const matches = await findMatchingMembers(
1262
- libraries,
1263
- candidate,
1264
- candidateIdentity
1265
- );
1432
+ const matches = await findMatchingMembers(candidate, candidateIdentity);
1266
1433
  if (matches.length > 0) {
1267
1434
  const match = requireUnambiguousManagedMatch(path, matches);
1268
1435
  throw new WikiGraphArchiveOwnershipError(
@@ -1271,63 +1438,61 @@ async function assertStandaloneWikiGraphArchivePath(inputPath) {
1271
1438
  )}: ${path}. Use the library UUID URI ${match.archive.uri} instead.`
1272
1439
  );
1273
1440
  }
1274
- for (const library of libraries) {
1275
- const rootPath = getNodeResourcePath(library.folder);
1276
- const root = await canonicalPath(rootPath);
1277
- if (!isWithin(root, candidate)) continue;
1278
- throw new WikiGraphArchiveOwnershipError(
1279
- `Standalone archive path belongs to registered library ${formatWikiGraphLibraryUri(
1280
- library.isDefault ? void 0 : library.publicId
1281
- )}: ${path}. Scan the library, then address the archive by its library UUID URI.`
1441
+ const ancestors = /* @__PURE__ */ new Set();
1442
+ for (const start of [candidate, resolve3(path)]) {
1443
+ for (let parent = dirname2(start); ; parent = dirname2(parent)) {
1444
+ ancestors.add(parent);
1445
+ if (dirname2(parent) === parent) break;
1446
+ }
1447
+ }
1448
+ for (const parent of ancestors) {
1449
+ const identity = await readFileIdentity(parent);
1450
+ const library = await findWikiGraphLibraryByPhysicalIdentity(
1451
+ identity,
1452
+ new NodeDirectory(parent).identity
1282
1453
  );
1454
+ if (library !== void 0) {
1455
+ throw new WikiGraphArchiveOwnershipError(
1456
+ `Standalone archive path belongs to registered library ${formatWikiGraphLibraryUri(
1457
+ library.isDefault ? void 0 : library.publicId
1458
+ )}: ${path}. Scan the library, then address the archive by its library UUID URI.`
1459
+ );
1460
+ }
1283
1461
  }
1284
1462
  return path;
1285
1463
  }
1286
1464
  async function canonicalPath(path) {
1287
1465
  try {
1288
1466
  return await realpath(path);
1289
- } catch {
1467
+ } catch (error) {
1468
+ if (!(typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")))
1469
+ throw error;
1290
1470
  const absolute = resolve3(path);
1291
1471
  const parent = dirname2(absolute);
1292
1472
  return parent === absolute ? absolute : join3(await canonicalPath(parent), basename2(absolute));
1293
1473
  }
1294
1474
  }
1295
- function isWithin(root, candidate) {
1296
- const child = relative(root, candidate);
1297
- return child === "" || !child.startsWith("..") && !isAbsolute3(child);
1298
- }
1299
- async function findMatchingMembers(libraries, candidate, candidateIdentity) {
1475
+ async function findMatchingMembers(candidate, candidateIdentity) {
1476
+ if (candidateIdentity === void 0) return [];
1477
+ const archives = await findWikiGraphLibraryArchivePhysicalAliases(candidateIdentity);
1300
1478
  const members = [];
1301
- for (const library of libraries) {
1302
- const archives = await listWikiGraphLibraryArchives({
1303
- isDefault: library.isDefault,
1304
- kind: "scope",
1305
- ...library.isDefault ? {} : { publicId: library.publicId }
1306
- });
1307
- for (const archive of archives) {
1308
- const physical = archive.file ?? archive.directory;
1309
- if (physical === void 0) continue;
1479
+ for (const archive of archives) {
1480
+ const parsed = parseWikiGraphLibraryUri(archive.libraryUri);
1481
+ const library = await resolveWikiGraphLibrary(parsed);
1482
+ const physical = archive.file ?? archive.directory;
1483
+ if (library !== void 0 && physical !== void 0)
1310
1484
  members.push({
1311
1485
  archive,
1312
1486
  library,
1313
1487
  memberPath: getNodeResourcePath(physical)
1314
1488
  });
1315
- }
1316
1489
  }
1317
1490
  const exact = [];
1318
1491
  for (const member of members) {
1319
- if (await canonicalPath(member.memberPath) === candidate) {
1492
+ if (await canonicalPath(member.memberPath) === candidate)
1320
1493
  exact.push(member);
1321
- }
1322
- }
1323
- if (exact.length > 0 || candidateIdentity === void 0) return exact;
1324
- const identityMatches = [];
1325
- for (const member of members) {
1326
- if (await readFileIdentity(member.memberPath) === candidateIdentity) {
1327
- identityMatches.push(member);
1328
- }
1329
1494
  }
1330
- return identityMatches;
1495
+ return exact.length > 0 ? exact : members;
1331
1496
  }
1332
1497
  function requireUnambiguousManagedMatch(path, matches) {
1333
1498
  if (matches.length === 1) return matches[0];
@@ -1339,8 +1504,10 @@ async function readFileIdentity(path) {
1339
1504
  try {
1340
1505
  const value = await stat2(path);
1341
1506
  return `${value.dev}:${value.ino}`;
1342
- } catch {
1343
- return void 0;
1507
+ } catch (error) {
1508
+ if (typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
1509
+ return void 0;
1510
+ throw error;
1344
1511
  }
1345
1512
  }
1346
1513
 
@@ -1402,11 +1569,15 @@ var WikiGraphConversionManager = class {
1402
1569
  if (!("path" in options.input)) {
1403
1570
  throw new Error("wikg input requires a file path.");
1404
1571
  }
1572
+ const inputPath = options.input.path;
1405
1573
  await app.openSession(
1406
- new NodeFile(
1407
- await assertStandaloneWikiGraphArchivePath(options.input.path)
1408
- ),
1574
+ new NodeFile(await assertStandaloneWikiGraphArchivePath(inputPath)),
1409
1575
  write
1576
+ ).catch(
1577
+ (error) => rethrowArchiveSchemaError(error, {
1578
+ kind: "standalone",
1579
+ path: inputPath
1580
+ })
1410
1581
  );
1411
1582
  } else if ("stream" in options.input) {
1412
1583
  await app.digestTextStreamSession(