@remnic/core 1.1.15 → 1.1.17

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 (44) hide show
  1. package/dist/access-cli.js +2 -2
  2. package/dist/access-http.d.ts +1 -1
  3. package/dist/access-http.js +5 -5
  4. package/dist/access-mcp.d.ts +1 -1
  5. package/dist/access-mcp.js +4 -4
  6. package/dist/access-schema.d.ts +17 -3
  7. package/dist/access-schema.js +3 -1
  8. package/dist/{access-service-BCMine1s.d.ts → access-service-DZXc7qwR.d.ts} +11 -1
  9. package/dist/access-service.d.ts +1 -1
  10. package/dist/access-service.js +2 -2
  11. package/dist/{chunk-VWFIQOTJ.js → chunk-66H2DZYB.js} +8 -1
  12. package/dist/chunk-66H2DZYB.js.map +1 -0
  13. package/dist/{chunk-BNATB54A.js → chunk-BYACCC5C.js} +3 -3
  14. package/dist/{chunk-ZYRMKWVW.js → chunk-CDQNR7SV.js} +4 -4
  15. package/dist/{chunk-HJ2WMBFB.js → chunk-GCR4JFKK.js} +15 -4
  16. package/dist/chunk-GCR4JFKK.js.map +1 -0
  17. package/dist/{chunk-GSP6ZKOY.js → chunk-HMZYQPT5.js} +85 -19
  18. package/dist/chunk-HMZYQPT5.js.map +1 -0
  19. package/dist/{chunk-5D2G67ZQ.js → chunk-VLQWOGYM.js} +29 -3
  20. package/dist/chunk-VLQWOGYM.js.map +1 -0
  21. package/dist/{cli-B71zQ6XK.d.ts → cli-kVwab1_L.d.ts} +1 -1
  22. package/dist/cli.d.ts +2 -2
  23. package/dist/cli.js +6 -6
  24. package/dist/index.d.ts +4 -4
  25. package/dist/index.js +8 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  28. package/dist/offline-sync.d.ts +10 -1
  29. package/dist/offline-sync.js +3 -1
  30. package/package.json +1 -1
  31. package/src/access-http.test.ts +73 -0
  32. package/src/access-http.ts +15 -0
  33. package/src/access-schema.ts +12 -0
  34. package/src/access-service-namespace.test.ts +64 -1
  35. package/src/access-service.ts +44 -0
  36. package/src/index.ts +1 -0
  37. package/src/offline-sync.test.ts +174 -0
  38. package/src/offline-sync.ts +110 -18
  39. package/dist/chunk-5D2G67ZQ.js.map +0 -1
  40. package/dist/chunk-GSP6ZKOY.js.map +0 -1
  41. package/dist/chunk-HJ2WMBFB.js.map +0 -1
  42. package/dist/chunk-VWFIQOTJ.js.map +0 -1
  43. /package/dist/{chunk-BNATB54A.js.map → chunk-BYACCC5C.js.map} +0 -0
  44. /package/dist/{chunk-ZYRMKWVW.js.map → chunk-CDQNR7SV.js.map} +0 -0
@@ -127,6 +127,14 @@ export interface OfflineSyncFileWriteTarget extends OfflineSyncFileTarget {
127
127
  content: Buffer;
128
128
  }
129
129
 
130
+ interface OfflineSyncFileRecordOptions {
131
+ root: SafeArchiveRoot;
132
+ relPath: string;
133
+ filePath: string;
134
+ includeContent: boolean;
135
+ readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
136
+ }
137
+
130
138
  const SYNC_INTERNAL_DIR = ".offline-sync";
131
139
  const EXCLUDED_FILE_NAMES = new Set([
132
140
  ".sync-state.json",
@@ -135,6 +143,9 @@ const EXCLUDED_FILE_NAMES = new Set([
135
143
  const EXCLUDED_REL_PATHS = new Set([
136
144
  "state/fact-hashes.ready",
137
145
  "state/fact-hashes.txt",
146
+ "state/lcm.sqlite",
147
+ "state/lcm.sqlite-shm",
148
+ "state/lcm.sqlite-wal",
138
149
  ]);
139
150
 
140
151
  const EXCLUDED_FILE_PREFIXES = [
@@ -406,6 +417,24 @@ function filterBaseFilesForMode(
406
417
  return files.filter((file) => !shouldExcludeRelPath(file.path, includeTranscripts));
407
418
  }
408
419
 
420
+ async function readOfflineSyncFileRecord(
421
+ options: OfflineSyncFileRecordOptions,
422
+ ): Promise<OfflineSyncFileRecord> {
423
+ const relPath = validateArchiveRelativePath(options.relPath, "offlineSyncFile.path");
424
+ const bytes = options.readFile
425
+ ? await options.readFile({ root: options.root.abs, path: relPath, filePath: options.filePath })
426
+ : await readFile(options.filePath);
427
+ const digest = sha256Buffer(bytes);
428
+ const st = await stat(options.filePath);
429
+ return {
430
+ path: relPath,
431
+ sha256: digest.sha256,
432
+ bytes: digest.bytes,
433
+ mtimeMs: st.mtimeMs,
434
+ ...(options.includeContent ? { contentBase64: bytes.toString("base64") } : {}),
435
+ };
436
+ }
437
+
409
438
  export async function buildOfflineSyncSnapshot(options: {
410
439
  root: string;
411
440
  sourceId: string;
@@ -432,18 +461,13 @@ export async function buildOfflineSyncSnapshot(options: {
432
461
  continue;
433
462
  }
434
463
  if (!entry.isFile()) continue;
435
- const bytes = options.readFile
436
- ? await options.readFile({ root: root.abs, path: relPosix, filePath: abs })
437
- : await readFile(abs);
438
- const digest = sha256Buffer(bytes);
439
- const st = await stat(abs);
440
- files.push({
441
- path: validateArchiveRelativePath(relPosix, "buildOfflineSyncSnapshot"),
442
- sha256: digest.sha256,
443
- bytes: digest.bytes,
444
- mtimeMs: st.mtimeMs,
445
- ...(options.includeContent === true ? { contentBase64: bytes.toString("base64") } : {}),
446
- });
464
+ files.push(await readOfflineSyncFileRecord({
465
+ root,
466
+ relPath: relPosix,
467
+ filePath: abs,
468
+ includeContent: options.includeContent === true,
469
+ readFile: options.readFile,
470
+ }));
447
471
  }
448
472
  }
449
473
 
@@ -459,6 +483,53 @@ export async function buildOfflineSyncSnapshot(options: {
459
483
  };
460
484
  }
461
485
 
486
+ export async function buildOfflineSyncSnapshotForPaths(options: {
487
+ root: string;
488
+ sourceId: string;
489
+ paths: readonly string[];
490
+ includeContent?: boolean;
491
+ includeTranscripts?: boolean;
492
+ now?: Date;
493
+ readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
494
+ }): Promise<OfflineSyncSnapshot> {
495
+ const rootAbs = path.resolve(options.root);
496
+ const root = await prepareSafeArchiveRoot(rootAbs, "buildOfflineSyncSnapshotForPaths", "root");
497
+ const includeTranscripts = options.includeTranscripts !== false;
498
+ const files: OfflineSyncFileRecord[] = [];
499
+ const seen = new Set<string>();
500
+
501
+ for (const rawPath of options.paths) {
502
+ const relPath = normalizeRelativePath(rawPath, "paths[]");
503
+ if (seen.has(relPath)) continue;
504
+ seen.add(relPath);
505
+ if (shouldExcludeRelPath(relPath, includeTranscripts)) {
506
+ throw new Error(`offline sync snapshot path is excluded: ${relPath}`);
507
+ }
508
+ const filePath = await resolveSafeArchiveTarget(root, relPath);
509
+ const st = await lstat(filePath).catch((error: unknown) => {
510
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
511
+ throw error;
512
+ });
513
+ if (!st || st.isSymbolicLink() || !st.isFile()) continue;
514
+ files.push(await readOfflineSyncFileRecord({
515
+ root,
516
+ relPath,
517
+ filePath,
518
+ includeContent: options.includeContent === true,
519
+ readFile: options.readFile,
520
+ }));
521
+ }
522
+
523
+ return {
524
+ format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
525
+ schemaVersion: 1,
526
+ createdAt: (options.now ?? new Date()).toISOString(),
527
+ sourceId: normalizeSourceId(options.sourceId, "sourceId"),
528
+ includeTranscripts,
529
+ files: files.sort(compareByPath),
530
+ };
531
+ }
532
+
462
533
  export async function buildOfflineSyncChangeset(options: {
463
534
  root: string;
464
535
  sourceId: string;
@@ -475,7 +546,7 @@ export async function buildOfflineSyncChangeset(options: {
475
546
  const current = await buildOfflineSyncSnapshot({
476
547
  root: options.root,
477
548
  sourceId: options.sourceId,
478
- includeContent: true,
549
+ includeContent: false,
479
550
  includeTranscripts,
480
551
  now: options.now,
481
552
  readFile: options.readFile,
@@ -487,11 +558,24 @@ export async function buildOfflineSyncChangeset(options: {
487
558
  const baseEntry = base.get(relPath);
488
559
  const currentEntry = currentMap.get(relPath);
489
560
  if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
561
+ const file = await buildOfflineSyncSnapshotForPaths({
562
+ root: options.root,
563
+ sourceId: options.sourceId,
564
+ paths: [relPath],
565
+ includeContent: true,
566
+ includeTranscripts,
567
+ now: options.now,
568
+ readFile: options.readFile,
569
+ });
570
+ const record = file.files[0];
571
+ if (!record || typeof record.contentBase64 !== "string" || record.sha256 !== currentEntry.sha256) {
572
+ throw new Error(`offline sync file changed while building changeset: ${relPath}`);
573
+ }
490
574
  changes.push({
491
575
  type: "upsert",
492
576
  path: relPath,
493
577
  ...(baseEntry ? { baseSha256: baseEntry.sha256 } : {}),
494
- file: currentEntry as OfflineSyncFileRecord & { contentBase64: string },
578
+ file: record as OfflineSyncFileRecord & { contentBase64: string },
495
579
  });
496
580
  continue;
497
581
  }
@@ -535,13 +619,15 @@ export async function applyOfflineSyncSnapshot(options: {
535
619
  writeFile?: (target: OfflineSyncFileWriteTarget) => Promise<void>;
536
620
  deleteFile?: (target: OfflineSyncFileTarget) => Promise<void>;
537
621
  }): Promise<OfflineSyncApplySnapshotResult> {
538
- const snapshot = normalizeOfflineSyncSnapshot(options.snapshot, { requireContent: true });
622
+ const snapshot = normalizeOfflineSyncSnapshot(options.snapshot);
539
623
  const baseMap = byPath(filterBaseFilesForMode(
540
624
  normalizeFileStates(options.baseFiles),
541
625
  snapshot.includeTranscripts,
542
626
  ));
543
627
  const incomingMap = byPath(snapshot.files);
544
- const incomingBuffers = verifyRecordContents(snapshot.files, "offline sync snapshot");
628
+ const incomingBuffers = verifyRecordContents(snapshot.files, "offline sync snapshot", {
629
+ requireContent: false,
630
+ });
545
631
  const root = await ensureSyncRoot(options.root, "applyOfflineSyncSnapshot");
546
632
  const current = await buildOfflineSyncSnapshot({
547
633
  root: root.abs,
@@ -582,7 +668,9 @@ export async function applyOfflineSyncSnapshot(options: {
582
668
  reason: "local_deleted_remote_modified",
583
669
  baseSha256: base.sha256,
584
670
  incomingSha256: incoming.sha256,
585
- incomingBuffer: incomingBuffers.get(relPath),
671
+ incomingBuffer: options.writeConflictCopies === false
672
+ ? incomingBuffers.get(relPath)
673
+ : requiredBuffer(incomingBuffers, relPath),
586
674
  writeConflictCopies: options.writeConflictCopies !== false,
587
675
  sourceId: snapshot.sourceId,
588
676
  writeFile: options.writeFile,
@@ -615,7 +703,9 @@ export async function applyOfflineSyncSnapshot(options: {
615
703
  baseSha256: base?.sha256,
616
704
  localSha256: currentEntry?.sha256,
617
705
  incomingSha256: incoming.sha256,
618
- incomingBuffer: incomingBuffers.get(relPath),
706
+ incomingBuffer: options.writeConflictCopies === false
707
+ ? incomingBuffers.get(relPath)
708
+ : requiredBuffer(incomingBuffers, relPath),
619
709
  writeConflictCopies: options.writeConflictCopies !== false,
620
710
  sourceId: snapshot.sourceId,
621
711
  writeFile: options.writeFile,
@@ -774,10 +864,12 @@ export async function applyOfflineSyncChangeset(options: {
774
864
  function verifyRecordContents(
775
865
  records: readonly OfflineSyncFileRecord[],
776
866
  context: string,
867
+ options: { requireContent?: boolean } = {},
777
868
  ): Map<string, Buffer> {
778
869
  const buffers = new Map<string, Buffer>();
779
870
  for (const record of records) {
780
871
  if (typeof record.contentBase64 !== "string") {
872
+ if (options.requireContent === false) continue;
781
873
  throw new Error(`${context}: contentBase64 is required for ${record.path}`);
782
874
  }
783
875
  const buffer = Buffer.from(record.contentBase64, "base64");