@remnic/core 1.1.22 → 1.1.24

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 (103) hide show
  1. package/dist/access-cli.js +15 -15
  2. package/dist/access-http.d.ts +9 -1
  3. package/dist/access-http.js +9 -9
  4. package/dist/access-mcp.d.ts +1 -1
  5. package/dist/access-mcp.js +8 -8
  6. package/dist/access-schema.js +3 -3
  7. package/dist/{access-service-DT9L2DW4.d.ts → access-service-CEyV8XJ5.d.ts} +19 -2
  8. package/dist/access-service.d.ts +1 -1
  9. package/dist/access-service.js +6 -6
  10. package/dist/briefing.js +3 -3
  11. package/dist/causal-consolidation.js +4 -4
  12. package/dist/{chunk-YO3AZEE5.js → chunk-25YQM6XW.js} +3 -3
  13. package/dist/{chunk-LDJANWTK.js → chunk-2DM72JF3.js} +12 -12
  14. package/dist/{chunk-TLM762GT.js → chunk-2WIPXV3Y.js} +2 -2
  15. package/dist/{chunk-QOHBYVZG.js → chunk-3F24QTRI.js} +2 -2
  16. package/dist/{chunk-5IQC4OG6.js → chunk-4H6DURG6.js} +2 -2
  17. package/dist/{chunk-26OQECWH.js → chunk-6CB4E7ZV.js} +4 -4
  18. package/dist/{chunk-NOQ74SJN.js → chunk-7D6O46PF.js} +2 -2
  19. package/dist/{chunk-FF46Q3SN.js → chunk-AMVN77EU.js} +360 -32
  20. package/dist/chunk-AMVN77EU.js.map +1 -0
  21. package/dist/{chunk-7Q2P774N.js → chunk-F33CJ5CH.js} +13 -3
  22. package/dist/chunk-F33CJ5CH.js.map +1 -0
  23. package/dist/{chunk-FSODDMR2.js → chunk-IANK6Y5W.js} +2 -2
  24. package/dist/{chunk-UA6OCL6S.js → chunk-JUYT2J3K.js} +106 -11
  25. package/dist/chunk-JUYT2J3K.js.map +1 -0
  26. package/dist/{chunk-NGPO6S3M.js → chunk-LCTP7YRU.js} +42 -5
  27. package/dist/chunk-LCTP7YRU.js.map +1 -0
  28. package/dist/{chunk-GGCJ253V.js → chunk-MVAOT247.js} +8 -8
  29. package/dist/{chunk-SH5S7XYD.js → chunk-MXFBBHJU.js} +72 -2
  30. package/dist/chunk-MXFBBHJU.js.map +1 -0
  31. package/dist/{chunk-VMQRBXJ5.js → chunk-NW7JW5GA.js} +2 -2
  32. package/dist/{chunk-SZKCBLS5.js → chunk-PUXCIHRL.js} +2 -2
  33. package/dist/{chunk-2IRT26RZ.js → chunk-QYHQ2JHL.js} +2 -2
  34. package/dist/{chunk-CN4P6SVA.js → chunk-RCZRL5BE.js} +2 -2
  35. package/dist/{chunk-SGIXDVSF.js → chunk-S27EXIHY.js} +2 -2
  36. package/dist/{chunk-5ML4TH3E.js → chunk-TFORLO3O.js} +4 -4
  37. package/dist/{chunk-TOFUTKQN.js → chunk-TR4DK5OH.js} +2 -2
  38. package/dist/{chunk-6ORWKANA.js → chunk-VYU7PXUS.js} +2 -2
  39. package/dist/{chunk-FFU4GMST.js → chunk-WNARATI3.js} +2 -2
  40. package/dist/{chunk-KSFBM6TV.js → chunk-YITUHONZ.js} +2 -2
  41. package/dist/{cli-BN0CkYzI.d.ts → cli-BguVmIwO.d.ts} +1 -1
  42. package/dist/cli.d.ts +2 -2
  43. package/dist/cli.js +18 -18
  44. package/dist/compounding/engine.js +3 -3
  45. package/dist/connectors/codex-materialize-runner.js +3 -3
  46. package/dist/connectors/index.js +3 -3
  47. package/dist/entity-retrieval.js +3 -3
  48. package/dist/index.d.ts +4 -4
  49. package/dist/index.js +30 -24
  50. package/dist/index.js.map +1 -1
  51. package/dist/maintenance/memory-governance.js +3 -3
  52. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  53. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  54. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  55. package/dist/namespaces/migrate.js +4 -4
  56. package/dist/namespaces/storage.js +3 -3
  57. package/dist/offline-sync.d.ts +38 -1
  58. package/dist/offline-sync.js +8 -2
  59. package/dist/operator-toolkit.js +6 -6
  60. package/dist/orchestrator.js +11 -11
  61. package/dist/schemas.d.ts +22 -22
  62. package/dist/secure-store/index.js +2 -2
  63. package/dist/semantic-consolidation.js +4 -4
  64. package/dist/semantic-rule-promotion.js +3 -3
  65. package/dist/semantic-rule-verifier.js +3 -3
  66. package/dist/storage.d.ts +2 -0
  67. package/dist/storage.js +2 -2
  68. package/dist/transfer/types.d.ts +12 -12
  69. package/dist/verified-recall.js +3 -3
  70. package/package.json +1 -1
  71. package/src/access-http.test.ts +239 -0
  72. package/src/access-http.ts +128 -7
  73. package/src/access-service-offline-file-content.test.ts +37 -0
  74. package/src/access-service.ts +70 -0
  75. package/src/index.ts +4 -0
  76. package/src/offline-sync.test.ts +395 -79
  77. package/src/offline-sync.ts +473 -32
  78. package/src/secure-store/secure-fs.ts +84 -3
  79. package/src/storage.ts +12 -0
  80. package/dist/chunk-7Q2P774N.js.map +0 -1
  81. package/dist/chunk-FF46Q3SN.js.map +0 -1
  82. package/dist/chunk-NGPO6S3M.js.map +0 -1
  83. package/dist/chunk-SH5S7XYD.js.map +0 -1
  84. package/dist/chunk-UA6OCL6S.js.map +0 -1
  85. /package/dist/{chunk-YO3AZEE5.js.map → chunk-25YQM6XW.js.map} +0 -0
  86. /package/dist/{chunk-LDJANWTK.js.map → chunk-2DM72JF3.js.map} +0 -0
  87. /package/dist/{chunk-TLM762GT.js.map → chunk-2WIPXV3Y.js.map} +0 -0
  88. /package/dist/{chunk-QOHBYVZG.js.map → chunk-3F24QTRI.js.map} +0 -0
  89. /package/dist/{chunk-5IQC4OG6.js.map → chunk-4H6DURG6.js.map} +0 -0
  90. /package/dist/{chunk-26OQECWH.js.map → chunk-6CB4E7ZV.js.map} +0 -0
  91. /package/dist/{chunk-NOQ74SJN.js.map → chunk-7D6O46PF.js.map} +0 -0
  92. /package/dist/{chunk-FSODDMR2.js.map → chunk-IANK6Y5W.js.map} +0 -0
  93. /package/dist/{chunk-GGCJ253V.js.map → chunk-MVAOT247.js.map} +0 -0
  94. /package/dist/{chunk-VMQRBXJ5.js.map → chunk-NW7JW5GA.js.map} +0 -0
  95. /package/dist/{chunk-SZKCBLS5.js.map → chunk-PUXCIHRL.js.map} +0 -0
  96. /package/dist/{chunk-2IRT26RZ.js.map → chunk-QYHQ2JHL.js.map} +0 -0
  97. /package/dist/{chunk-CN4P6SVA.js.map → chunk-RCZRL5BE.js.map} +0 -0
  98. /package/dist/{chunk-SGIXDVSF.js.map → chunk-S27EXIHY.js.map} +0 -0
  99. /package/dist/{chunk-5ML4TH3E.js.map → chunk-TFORLO3O.js.map} +0 -0
  100. /package/dist/{chunk-TOFUTKQN.js.map → chunk-TR4DK5OH.js.map} +0 -0
  101. /package/dist/{chunk-6ORWKANA.js.map → chunk-VYU7PXUS.js.map} +0 -0
  102. /package/dist/{chunk-FFU4GMST.js.map → chunk-WNARATI3.js.map} +0 -0
  103. /package/dist/{chunk-KSFBM6TV.js.map → chunk-YITUHONZ.js.map} +0 -0
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  MAGIC_HEADER_SIZE,
12
12
  isEncryptedFile
13
- } from "./chunk-SH5S7XYD.js";
13
+ } from "./chunk-MXFBBHJU.js";
14
14
  import {
15
15
  parseFlexibleIsoTimestamp
16
16
  } from "./chunk-P7FMDTKL.js";
@@ -24,6 +24,7 @@ import {
24
24
  readdir,
25
25
  readFile,
26
26
  rename,
27
+ rm,
27
28
  stat,
28
29
  unlink,
29
30
  writeFile
@@ -33,34 +34,13 @@ var OFFLINE_SYNC_SNAPSHOT_FORMAT = "remnic.offline-sync.snapshot.v1";
33
34
  var OFFLINE_SYNC_CHANGESET_FORMAT = "remnic.offline-sync.changeset.v1";
34
35
  var OFFLINE_SYNC_STATE_VERSION = 1;
35
36
  var OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES = 64 * 1024 * 1024;
37
+ var OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES = 8 * 1024 * 1024;
38
+ var OFFLINE_SYNC_APPLY_MAX_BODY_BYTES = 16 * 1024 * 1024;
36
39
  var SYNC_INTERNAL_DIR = ".offline-sync";
40
+ var OFFLINE_SYNC_UPLOAD_STAGING_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
37
41
  var EXCLUDED_FILE_NAMES = /* @__PURE__ */ new Set([
38
42
  ".sync-state.json"
39
43
  ]);
40
- var DERIVED_RUNTIME_STATE_BASENAMES = /* @__PURE__ */ new Set([
41
- ".artifact-write-version.log",
42
- ".memory-status-version.log",
43
- "fact-hashes.ready",
44
- "fact-hashes.txt",
45
- "buffer-surprise-ledger.jsonl",
46
- "buffer.json",
47
- "embeddings.json",
48
- "entity-mention-index.json",
49
- "index_tags.json",
50
- "index_time.json",
51
- "last_graph_recall.json",
52
- "last_intent.json",
53
- "last_qmd_recall.json",
54
- "last_recall.json",
55
- "lcm.sqlite",
56
- "lcm.sqlite-shm",
57
- "lcm.sqlite-wal",
58
- "memory-lifecycle-ledger.jsonl",
59
- "memory-projection.sqlite",
60
- "memory-projection.sqlite-shm",
61
- "memory-projection.sqlite-wal",
62
- "recall_impressions.jsonl"
63
- ]);
64
44
  var EXCLUDED_FILE_PREFIXES = [
65
45
  ".remnic-sync.",
66
46
  ".remnic-sync-state."
@@ -281,7 +261,6 @@ function shouldExcludeRelPath(relPosix, includeTranscripts) {
281
261
  const parts = relPosix.split("/");
282
262
  if (parts.some((part) => DEFAULT_TRANSFER_EXCLUDE_DIRS.has(part))) return true;
283
263
  if (parts.some((part) => part === SYNC_INTERNAL_DIR)) return true;
284
- if (isDerivedRuntimeStatePath(parts)) return true;
285
264
  if (!includeTranscripts && parts[0] === "transcripts") return true;
286
265
  const basename = parts[parts.length - 1] ?? "";
287
266
  if (isCanonicalRuntimeStatePath(parts) && basename.includes(".tmp-")) return true;
@@ -291,11 +270,7 @@ function shouldExcludeRelPath(relPosix, includeTranscripts) {
291
270
  function shouldIgnoreIncomingRuntimePath(relPosix) {
292
271
  const parts = relPosix.split("/");
293
272
  const basename = parts[parts.length - 1] ?? "";
294
- return isDerivedRuntimeStatePath(parts) || isCanonicalRuntimeStatePath(parts) && basename.includes(".tmp-");
295
- }
296
- function isDerivedRuntimeStatePath(parts) {
297
- const basename = parts[parts.length - 1] ?? "";
298
- return isCanonicalRuntimeStatePath(parts) && DERIVED_RUNTIME_STATE_BASENAMES.has(basename);
273
+ return isCanonicalRuntimeStatePath(parts) && basename.includes(".tmp-");
299
274
  }
300
275
  function isCanonicalRuntimeStatePath(parts) {
301
276
  if (parts[0] === "state") return true;
@@ -477,6 +452,9 @@ async function readOfflineSyncFileContentChunk(options) {
477
452
  }
478
453
  async function buildOfflineSyncChangeset(options) {
479
454
  const includeTranscripts = options.includeTranscripts !== false;
455
+ const excludedPaths = new Set(
456
+ (options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]"))
457
+ );
480
458
  const base = byPath(filterBaseFilesForMode(
481
459
  normalizeFileStates(options.baseFiles),
482
460
  includeTranscripts
@@ -492,6 +470,7 @@ async function buildOfflineSyncChangeset(options) {
492
470
  const currentMap = byPath(current.files);
493
471
  const changes = [];
494
472
  for (const relPath of unionPaths(base, currentMap)) {
473
+ if (excludedPaths.has(relPath)) continue;
495
474
  const baseEntry = base.get(relPath);
496
475
  const currentEntry = currentMap.get(relPath);
497
476
  if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
@@ -874,6 +853,352 @@ async function writeSafeFile(root, relPath, content, writeFileHook) {
874
853
  throw error;
875
854
  }
876
855
  }
856
+ async function applyOfflineSyncFileContentChunk(options) {
857
+ const root = await ensureSyncRoot(options.root, "applyOfflineSyncFileContentChunk");
858
+ const sourceId = normalizeSourceId(options.sourceId, "sourceId");
859
+ const relPath = normalizeRelativePath(options.path, "path");
860
+ const includeTranscripts = options.includeTranscripts !== false;
861
+ if (shouldExcludeRelPath(relPath, includeTranscripts)) {
862
+ throw new Error(`offline sync file content path is excluded: ${relPath}`);
863
+ }
864
+ const sha256 = assertSha256(options.sha256, "sha256");
865
+ const bytes = assertNonNegativeInteger(options.bytes, "bytes");
866
+ const mtimeMs = assertNonNegativeFinite(options.mtimeMs, "mtimeMs");
867
+ const offset = options.offset === void 0 ? 0 : assertNonNegativeInteger(options.offset, "offset");
868
+ const baseSha256 = options.baseSha256 === void 0 ? void 0 : assertSha256(options.baseSha256, "baseSha256");
869
+ if (!Buffer.isBuffer(options.content)) {
870
+ throw new Error("content must be a Buffer");
871
+ }
872
+ if (options.content.length > OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES) {
873
+ throw new Error(
874
+ `content chunk must be ${OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES} bytes or fewer`
875
+ );
876
+ }
877
+ if (bytes > 0 && options.content.length === 0) {
878
+ throw new Error("content chunk must be non-empty before EOF");
879
+ }
880
+ if (offset > bytes || offset + options.content.length > bytes) {
881
+ throw new Error(`content chunk range exceeds declared file size for ${relPath}`);
882
+ }
883
+ if (options.writeFile && !options.writeFileChunks) {
884
+ throw new Error("offline sync upload storage hooks require writeFileChunks");
885
+ }
886
+ if (options.writeFile && !options.writeStagingFile) {
887
+ throw new Error("offline sync upload storage hooks require writeStagingFile");
888
+ }
889
+ if (offset === 0) {
890
+ await pruneOfflineUploadStaging(root);
891
+ }
892
+ const upload = await writeOfflineUploadChunk({
893
+ root,
894
+ sourceId,
895
+ relPath,
896
+ sha256,
897
+ bytes,
898
+ offset,
899
+ content: options.content,
900
+ readFile: options.readFile,
901
+ writeFile: options.writeFile,
902
+ writeStagingFile: options.writeStagingFile
903
+ });
904
+ const done = offset + options.content.length === bytes;
905
+ const baseResult = {
906
+ path: relPath,
907
+ sha256,
908
+ bytes,
909
+ mtimeMs,
910
+ offset,
911
+ chunkBytes: options.content.length,
912
+ done
913
+ };
914
+ if (!done) {
915
+ return {
916
+ ...baseResult,
917
+ applied: false,
918
+ skipped: false
919
+ };
920
+ }
921
+ const digest = await digestOfflineUploadStagingContent({
922
+ root,
923
+ upload,
924
+ readFile: options.readFile
925
+ });
926
+ if (digest.sha256 !== sha256 || digest.bytes !== bytes) {
927
+ await cleanupOfflineUpload(upload).catch(() => {
928
+ });
929
+ throw new Error(`offline sync upload checksum mismatch for ${relPath}`);
930
+ }
931
+ const currentSnapshot = await buildOfflineSyncSnapshotForPaths({
932
+ root: root.abs,
933
+ sourceId: "local",
934
+ paths: [relPath],
935
+ includeContent: false,
936
+ includeTranscripts,
937
+ readFile: options.readFile
938
+ });
939
+ const currentFile = currentSnapshot.files[0];
940
+ const uploadedState = {
941
+ path: relPath,
942
+ sha256,
943
+ bytes,
944
+ mtimeMs
945
+ };
946
+ try {
947
+ if (currentFile?.sha256 === sha256) {
948
+ return {
949
+ ...baseResult,
950
+ applied: false,
951
+ skipped: true,
952
+ currentFile: toFileState(currentFile)
953
+ };
954
+ }
955
+ if (!baseSha256 && currentFile) {
956
+ const conflict = await recordConflict({
957
+ root,
958
+ relPath,
959
+ reason: "remote_exists_for_local_create",
960
+ localSha256: currentFile.sha256,
961
+ incomingSha256: sha256,
962
+ writeConflictCopies: false,
963
+ sourceId,
964
+ writeFile: options.writeFile
965
+ });
966
+ return {
967
+ ...baseResult,
968
+ applied: false,
969
+ skipped: false,
970
+ conflict,
971
+ currentFile: toFileState(currentFile)
972
+ };
973
+ }
974
+ if (baseSha256 && currentFile?.sha256 !== baseSha256) {
975
+ const conflict = await recordConflict({
976
+ root,
977
+ relPath,
978
+ reason: currentFile ? "remote_changed_for_local_update" : "remote_deleted_for_local_update",
979
+ baseSha256,
980
+ localSha256: currentFile?.sha256,
981
+ incomingSha256: sha256,
982
+ writeConflictCopies: false,
983
+ sourceId,
984
+ writeFile: options.writeFile
985
+ });
986
+ return {
987
+ ...baseResult,
988
+ applied: false,
989
+ skipped: false,
990
+ conflict,
991
+ ...currentFile ? { currentFile: toFileState(currentFile) } : {}
992
+ };
993
+ }
994
+ await writeSafeFileFromUpload(root, relPath, upload, options.readFile, options.writeFileChunks);
995
+ return {
996
+ ...baseResult,
997
+ applied: true,
998
+ skipped: false,
999
+ currentFile: uploadedState
1000
+ };
1001
+ } finally {
1002
+ await cleanupOfflineUpload(upload).catch(() => {
1003
+ });
1004
+ }
1005
+ }
1006
+ function offlineUploadRelPath(options) {
1007
+ const key = hashText([
1008
+ options.sourceId,
1009
+ options.relPath,
1010
+ options.sha256,
1011
+ String(options.bytes)
1012
+ ].join("\0"));
1013
+ return `${SYNC_INTERNAL_DIR}/uploads/${key}.part`;
1014
+ }
1015
+ async function offlineUploadPath(root, options) {
1016
+ const relPath = offlineUploadRelPath(options);
1017
+ return {
1018
+ kind: "single",
1019
+ relPath,
1020
+ filePath: await resolveSafeArchiveTarget(root, relPath)
1021
+ };
1022
+ }
1023
+ async function offlineUploadChunkPath(root, options) {
1024
+ const uploadRelPath = offlineUploadRelPath(options);
1025
+ const relPath = `${uploadRelPath}/${String(options.offset).padStart(20, "0")}.part`;
1026
+ return {
1027
+ kind: "chunks",
1028
+ relPath,
1029
+ filePath: await resolveSafeArchiveTarget(root, relPath)
1030
+ };
1031
+ }
1032
+ async function writeOfflineUploadChunk(options) {
1033
+ if ((options.writeFile || options.writeStagingFile) && !options.readFile) {
1034
+ throw new Error("offline sync upload chunk storage hooks require readFile");
1035
+ }
1036
+ const uploadRoot = {
1037
+ ...await offlineUploadPath(options.root, options),
1038
+ kind: "chunks"
1039
+ };
1040
+ if (options.offset === 0) {
1041
+ await rm(uploadRoot.filePath, { recursive: true, force: true }).catch(() => {
1042
+ });
1043
+ } else {
1044
+ const existing = await stat(uploadRoot.filePath).catch((error) => {
1045
+ if (error.code === "ENOENT") return null;
1046
+ throw error;
1047
+ });
1048
+ if (!existing || !existing.isDirectory()) {
1049
+ throw new Error(`offline sync upload is missing initial chunk for ${options.relPath}`);
1050
+ }
1051
+ }
1052
+ const chunk = await offlineUploadChunkPath(options.root, { ...options, offset: options.offset });
1053
+ const writeStagingFile = options.writeStagingFile ?? options.writeFile;
1054
+ if (writeStagingFile) {
1055
+ await writeOfflineUploadContent({
1056
+ root: options.root,
1057
+ relPath: chunk.relPath,
1058
+ filePath: chunk.filePath,
1059
+ content: options.content,
1060
+ writeFile: writeStagingFile
1061
+ });
1062
+ return uploadRoot;
1063
+ }
1064
+ await mkdir(path.dirname(chunk.filePath), { recursive: true });
1065
+ const existingChunk = await lstat(chunk.filePath).catch((error) => {
1066
+ if (error.code === "ENOENT") return null;
1067
+ throw error;
1068
+ });
1069
+ if (existingChunk?.isSymbolicLink()) {
1070
+ throw new Error(`offline sync upload chunk is a symlink: ${chunk.relPath}`);
1071
+ }
1072
+ await writeFile(chunk.filePath, options.content, { mode: 384 });
1073
+ return uploadRoot;
1074
+ }
1075
+ async function pruneOfflineUploadStaging(root) {
1076
+ const uploadsRelPath = `${SYNC_INTERNAL_DIR}/uploads`;
1077
+ const uploadsPath = await resolveSafeArchiveTarget(root, uploadsRelPath);
1078
+ const entries = await readdir(uploadsPath, { withFileTypes: true }).catch((error) => {
1079
+ if (error.code === "ENOENT") return [];
1080
+ throw error;
1081
+ });
1082
+ const now = Date.now();
1083
+ await Promise.all(entries.map(async (entry) => {
1084
+ if (!/^[a-f0-9]{64}\.part$/i.test(entry.name)) return;
1085
+ const relPath = `${uploadsRelPath}/${entry.name}`;
1086
+ const filePath = await resolveSafeArchiveTarget(root, relPath);
1087
+ const info = await lstat(filePath).catch((error) => {
1088
+ if (error.code === "ENOENT") return null;
1089
+ throw error;
1090
+ });
1091
+ if (!info) return;
1092
+ if (now - info.mtimeMs <= OFFLINE_SYNC_UPLOAD_STAGING_MAX_AGE_MS) return;
1093
+ await rm(filePath, { recursive: true, force: true });
1094
+ }));
1095
+ }
1096
+ async function* readOfflineUploadStagingChunks(options) {
1097
+ if (options.upload.kind === "single") {
1098
+ yield await readOfflineUploadContent({
1099
+ root: options.root,
1100
+ relPath: options.upload.relPath,
1101
+ filePath: options.upload.filePath,
1102
+ readFile: options.readFile
1103
+ });
1104
+ return;
1105
+ }
1106
+ const entries = await readdir(options.upload.filePath);
1107
+ const chunkNames = entries.filter((entry) => /^\d{20}\.part$/.test(entry)).sort();
1108
+ if (chunkNames.length === 0) {
1109
+ throw new Error(`offline sync upload is missing chunks for ${options.upload.relPath}`);
1110
+ }
1111
+ let expectedOffset = 0;
1112
+ for (const chunkName of chunkNames) {
1113
+ const offset = Number(chunkName.slice(0, 20));
1114
+ if (!Number.isSafeInteger(offset) || offset !== expectedOffset) {
1115
+ throw new Error(
1116
+ `offline sync upload offset mismatch for ${options.upload.relPath}: expected ${expectedOffset}, got ${offset}`
1117
+ );
1118
+ }
1119
+ const relPath = `${options.upload.relPath}/${chunkName}`;
1120
+ const filePath = await resolveSafeArchiveTarget(options.root, relPath);
1121
+ const content = await readOfflineUploadContent({
1122
+ root: options.root,
1123
+ relPath,
1124
+ filePath,
1125
+ readFile: options.readFile
1126
+ });
1127
+ expectedOffset += content.length;
1128
+ yield content;
1129
+ }
1130
+ }
1131
+ async function digestOfflineUploadStagingContent(options) {
1132
+ const hash = createHash("sha256");
1133
+ let bytes = 0;
1134
+ for await (const chunk of readOfflineUploadStagingChunks(options)) {
1135
+ hash.update(chunk);
1136
+ bytes += chunk.length;
1137
+ }
1138
+ return { sha256: hash.digest("hex"), bytes };
1139
+ }
1140
+ async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFileChunks) {
1141
+ const target = await resolveSafeArchiveTarget(root, relPath);
1142
+ const chunks = readOfflineUploadStagingChunks({ root, upload, readFile: readFile2 });
1143
+ if (writeFileChunks) {
1144
+ await writeFileChunks({ root: root.abs, path: relPath, filePath: target, chunks });
1145
+ return;
1146
+ }
1147
+ await mkdir(path.dirname(target), { recursive: true });
1148
+ const tmp = path.join(
1149
+ path.dirname(target),
1150
+ `.remnic-sync.${process.pid}.${randomUUID()}.tmp`
1151
+ );
1152
+ const handle = await open(tmp, "w", 384);
1153
+ try {
1154
+ for await (const chunk of chunks) {
1155
+ if (chunk.length > 0) await handle.write(chunk);
1156
+ }
1157
+ await handle.close();
1158
+ const targetStat = await lstat(target).catch((error) => {
1159
+ if (error.code === "ENOENT") return null;
1160
+ throw error;
1161
+ });
1162
+ if (targetStat?.isSymbolicLink()) {
1163
+ throw new Error(`offline sync target is a symlink: ${relPath}`);
1164
+ }
1165
+ await rename(tmp, target);
1166
+ } catch (error) {
1167
+ await handle.close().catch(() => {
1168
+ });
1169
+ await unlink(tmp).catch(() => {
1170
+ });
1171
+ throw error;
1172
+ }
1173
+ }
1174
+ async function cleanupOfflineUpload(upload) {
1175
+ if (upload.kind === "chunks") {
1176
+ await rm(upload.filePath, { recursive: true, force: true });
1177
+ return;
1178
+ }
1179
+ await unlink(upload.filePath).catch((error) => {
1180
+ if (error.code === "ENOENT") return;
1181
+ throw error;
1182
+ });
1183
+ }
1184
+ async function readOfflineUploadContent(options) {
1185
+ if (options.readFile) {
1186
+ return options.readFile({
1187
+ root: options.root.abs,
1188
+ path: options.relPath,
1189
+ filePath: options.filePath
1190
+ });
1191
+ }
1192
+ return readFile(options.filePath);
1193
+ }
1194
+ async function writeOfflineUploadContent(options) {
1195
+ await options.writeFile({
1196
+ root: options.root.abs,
1197
+ path: options.relPath,
1198
+ filePath: options.filePath,
1199
+ content: options.content
1200
+ });
1201
+ }
877
1202
  async function deleteSafeFile(root, relPath, deleteFile) {
878
1203
  const target = await resolveSafeArchiveTarget(root, relPath);
879
1204
  if (deleteFile) {
@@ -974,6 +1299,8 @@ export {
974
1299
  OFFLINE_SYNC_CHANGESET_FORMAT,
975
1300
  OFFLINE_SYNC_STATE_VERSION,
976
1301
  OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
1302
+ OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES,
1303
+ OFFLINE_SYNC_APPLY_MAX_BODY_BYTES,
977
1304
  normalizeOfflineSyncSnapshot,
978
1305
  normalizeOfflineSyncChangeset,
979
1306
  buildOfflineSyncSnapshot,
@@ -984,6 +1311,7 @@ export {
984
1311
  summarizeOfflineSyncPendingChanges,
985
1312
  applyOfflineSyncSnapshot,
986
1313
  applyOfflineSyncChangeset,
1314
+ applyOfflineSyncFileContentChunk,
987
1315
  defaultOfflineSyncStatePath,
988
1316
  readOfflineSyncState,
989
1317
  writeOfflineSyncState,
@@ -991,4 +1319,4 @@ export {
991
1319
  normalizeOfflineSyncState,
992
1320
  fileStatesFromSnapshot
993
1321
  };
994
- //# sourceMappingURL=chunk-FF46Q3SN.js.map
1322
+ //# sourceMappingURL=chunk-AMVN77EU.js.map