@remnic/core 1.1.29 → 1.1.30
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/access-cli.js +13 -13
- package/dist/access-http.d.ts +1 -1
- package/dist/access-http.js +8 -8
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +7 -7
- package/dist/access-schema.d.ts +55 -5
- package/dist/access-schema.js +4 -2
- package/dist/{access-service-CEyV8XJ5.d.ts → access-service-B5hgZPCN.d.ts} +4 -1
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +5 -5
- package/dist/briefing.js +2 -2
- package/dist/causal-consolidation.js +3 -3
- package/dist/{chunk-25YQM6XW.js → chunk-2IWUMAES.js} +3 -3
- package/dist/{chunk-JUYT2J3K.js → chunk-3OWUCDKH.js} +39 -7
- package/dist/chunk-3OWUCDKH.js.map +1 -0
- package/dist/{chunk-QYHQ2JHL.js → chunk-43PJZYGL.js} +2 -2
- package/dist/{chunk-YITUHONZ.js → chunk-4KGVTPGD.js} +2 -2
- package/dist/{chunk-TR4DK5OH.js → chunk-76FLAAUC.js} +2 -2
- package/dist/{chunk-6BFAEWQS.js → chunk-77H5NU3M.js} +2 -2
- package/dist/{chunk-IANK6Y5W.js → chunk-A6KTB5R6.js} +2 -2
- package/dist/{chunk-7D6O46PF.js → chunk-BVF3AGJP.js} +2 -2
- package/dist/{chunk-4H6DURG6.js → chunk-JA3AK3PT.js} +2 -2
- package/dist/{chunk-WDSIV3AK.js → chunk-KRBK4BQH.js} +12 -12
- package/dist/{chunk-AMVN77EU.js → chunk-MG7NA5H3.js} +365 -90
- package/dist/chunk-MG7NA5H3.js.map +1 -0
- package/dist/{chunk-RCZRL5BE.js → chunk-MRILGULB.js} +2 -2
- package/dist/{chunk-NW7JW5GA.js → chunk-OC7KHOOX.js} +41 -6
- package/dist/chunk-OC7KHOOX.js.map +1 -0
- package/dist/{chunk-LCTP7YRU.js → chunk-QKZGQIPJ.js} +16 -7
- package/dist/chunk-QKZGQIPJ.js.map +1 -0
- package/dist/{chunk-CWWDIQZB.js → chunk-QLLBRHAT.js} +8 -8
- package/dist/{chunk-2WIPXV3Y.js → chunk-RR2PKP3I.js} +2 -2
- package/dist/{chunk-3F24QTRI.js → chunk-SAZS2QZB.js} +2 -2
- package/dist/{chunk-VYU7PXUS.js → chunk-SIC6U3GZ.js} +2 -2
- package/dist/{chunk-6CB4E7ZV.js → chunk-UL2NNBUL.js} +4 -4
- package/dist/{chunk-F33CJ5CH.js → chunk-VBJ7V5SK.js} +40 -8
- package/dist/chunk-VBJ7V5SK.js.map +1 -0
- package/dist/{chunk-6WV2HYTZ.js → chunk-W6AQJ2PY.js} +4 -4
- package/dist/{chunk-PUXCIHRL.js → chunk-XSZEP4SF.js} +2 -2
- package/dist/{cli-BguVmIwO.d.ts → cli-CJKI2JIe.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +17 -17
- package/dist/compounding/engine.js +2 -2
- package/dist/connectors/codex-materialize-runner.js +2 -2
- package/dist/connectors/index.js +2 -2
- package/dist/entity-retrieval.js +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +32 -22
- package/dist/index.js.map +1 -1
- package/dist/maintenance/memory-governance.js +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/namespaces/migrate.js +3 -3
- package/dist/namespaces/storage.js +2 -2
- package/dist/offline-sync.d.ts +49 -1
- package/dist/offline-sync.js +13 -1
- package/dist/operator-toolkit.js +5 -5
- package/dist/orchestrator.js +9 -9
- package/dist/semantic-consolidation.js +3 -3
- package/dist/semantic-rule-promotion.js +2 -2
- package/dist/semantic-rule-verifier.js +2 -2
- package/dist/storage.d.ts +5 -0
- package/dist/storage.js +1 -1
- package/dist/verified-recall.js +2 -2
- package/package.json +1 -1
- package/src/access-http.test.ts +184 -0
- package/src/access-http.ts +37 -0
- package/src/access-schema.ts +58 -3
- package/src/access-service-namespace.test.ts +56 -1
- package/src/access-service-offline-file-content.test.ts +17 -0
- package/src/access-service.ts +16 -1
- package/src/index.ts +6 -0
- package/src/offline-sync.test.ts +1055 -1
- package/src/offline-sync.ts +453 -96
- package/src/storage.ts +36 -2
- package/dist/chunk-AMVN77EU.js.map +0 -1
- package/dist/chunk-F33CJ5CH.js.map +0 -1
- package/dist/chunk-JUYT2J3K.js.map +0 -1
- package/dist/chunk-LCTP7YRU.js.map +0 -1
- package/dist/chunk-NW7JW5GA.js.map +0 -1
- /package/dist/{chunk-25YQM6XW.js.map → chunk-2IWUMAES.js.map} +0 -0
- /package/dist/{chunk-QYHQ2JHL.js.map → chunk-43PJZYGL.js.map} +0 -0
- /package/dist/{chunk-YITUHONZ.js.map → chunk-4KGVTPGD.js.map} +0 -0
- /package/dist/{chunk-TR4DK5OH.js.map → chunk-76FLAAUC.js.map} +0 -0
- /package/dist/{chunk-6BFAEWQS.js.map → chunk-77H5NU3M.js.map} +0 -0
- /package/dist/{chunk-IANK6Y5W.js.map → chunk-A6KTB5R6.js.map} +0 -0
- /package/dist/{chunk-7D6O46PF.js.map → chunk-BVF3AGJP.js.map} +0 -0
- /package/dist/{chunk-4H6DURG6.js.map → chunk-JA3AK3PT.js.map} +0 -0
- /package/dist/{chunk-WDSIV3AK.js.map → chunk-KRBK4BQH.js.map} +0 -0
- /package/dist/{chunk-RCZRL5BE.js.map → chunk-MRILGULB.js.map} +0 -0
- /package/dist/{chunk-CWWDIQZB.js.map → chunk-QLLBRHAT.js.map} +0 -0
- /package/dist/{chunk-2WIPXV3Y.js.map → chunk-RR2PKP3I.js.map} +0 -0
- /package/dist/{chunk-3F24QTRI.js.map → chunk-SAZS2QZB.js.map} +0 -0
- /package/dist/{chunk-VYU7PXUS.js.map → chunk-SIC6U3GZ.js.map} +0 -0
- /package/dist/{chunk-6CB4E7ZV.js.map → chunk-UL2NNBUL.js.map} +0 -0
- /package/dist/{chunk-6WV2HYTZ.js.map → chunk-W6AQJ2PY.js.map} +0 -0
- /package/dist/{chunk-PUXCIHRL.js.map → chunk-XSZEP4SF.js.map} +0 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
// src/offline-sync.ts
|
|
19
19
|
import { createHash, randomUUID } from "crypto";
|
|
20
|
+
import { createReadStream } from "fs";
|
|
20
21
|
import {
|
|
21
22
|
lstat,
|
|
22
23
|
mkdir,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
rm,
|
|
28
29
|
stat,
|
|
29
30
|
unlink,
|
|
31
|
+
utimes,
|
|
30
32
|
writeFile
|
|
31
33
|
} from "fs/promises";
|
|
32
34
|
import path from "path";
|
|
@@ -36,8 +38,11 @@ var OFFLINE_SYNC_STATE_VERSION = 1;
|
|
|
36
38
|
var OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES = 64 * 1024 * 1024;
|
|
37
39
|
var OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
38
40
|
var OFFLINE_SYNC_APPLY_MAX_BODY_BYTES = 16 * 1024 * 1024;
|
|
41
|
+
var OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES = 64 * 1024 * 1024;
|
|
42
|
+
var OFFLINE_SYNC_MAX_MTIME_MS = 864e13;
|
|
39
43
|
var SYNC_INTERNAL_DIR = ".offline-sync";
|
|
40
44
|
var OFFLINE_SYNC_UPLOAD_STAGING_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
45
|
+
var OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS = 1e3;
|
|
41
46
|
var EXCLUDED_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
42
47
|
".sync-state.json"
|
|
43
48
|
]);
|
|
@@ -72,6 +77,13 @@ function assertNonNegativeFinite(value, field) {
|
|
|
72
77
|
}
|
|
73
78
|
return value;
|
|
74
79
|
}
|
|
80
|
+
function assertOfflineSyncMtimeMs(value, field) {
|
|
81
|
+
const mtimeMs = assertNonNegativeFinite(value, field);
|
|
82
|
+
if (mtimeMs > OFFLINE_SYNC_MAX_MTIME_MS) {
|
|
83
|
+
throw new Error(`${field} must be within JavaScript Date range`);
|
|
84
|
+
}
|
|
85
|
+
return mtimeMs;
|
|
86
|
+
}
|
|
75
87
|
function assertBoolean(value, field) {
|
|
76
88
|
if (typeof value !== "boolean") {
|
|
77
89
|
throw new Error(`${field} must be a boolean`);
|
|
@@ -94,7 +106,7 @@ function normalizeFileState(input, fieldPrefix) {
|
|
|
94
106
|
path: relPath,
|
|
95
107
|
sha256: assertSha256(obj.sha256, `${fieldPrefix}.sha256`),
|
|
96
108
|
bytes: assertNonNegativeInteger(obj.bytes, `${fieldPrefix}.bytes`),
|
|
97
|
-
mtimeMs:
|
|
109
|
+
mtimeMs: assertOfflineSyncMtimeMs(obj.mtimeMs, `${fieldPrefix}.mtimeMs`)
|
|
98
110
|
};
|
|
99
111
|
}
|
|
100
112
|
function normalizeFileRecord(input, fieldPrefix, requireContent) {
|
|
@@ -276,20 +288,72 @@ function isCanonicalRuntimeStatePath(parts) {
|
|
|
276
288
|
if (parts[0] === "state") return true;
|
|
277
289
|
return parts[0] === "namespaces" && parts.length >= 4 && parts[2] === "state";
|
|
278
290
|
}
|
|
291
|
+
var REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES = /* @__PURE__ */ new Set([
|
|
292
|
+
".artifact-write-version.log",
|
|
293
|
+
".memory-status-version.log",
|
|
294
|
+
"buffer.json",
|
|
295
|
+
"embeddings.json",
|
|
296
|
+
"index_time.json",
|
|
297
|
+
"last_recall.json",
|
|
298
|
+
"memory-lifecycle-ledger.jsonl",
|
|
299
|
+
"recall_impressions.jsonl"
|
|
300
|
+
]);
|
|
301
|
+
function shouldPreferIncomingOfflineRuntimeFile(relPosix) {
|
|
302
|
+
const parts = relPosix.split("/");
|
|
303
|
+
const basename = parts[parts.length - 1] ?? "";
|
|
304
|
+
return isCanonicalRuntimeStatePath(parts) && REMOTE_AUTHORITATIVE_RUNTIME_STATE_FILES.has(basename);
|
|
305
|
+
}
|
|
279
306
|
function filterBaseFilesForMode(files, includeTranscripts) {
|
|
280
307
|
return files.filter((file) => !shouldExcludeRelPath(file.path, includeTranscripts));
|
|
281
308
|
}
|
|
309
|
+
function canReuseFastBaseFileState(baseEntry, st, baseCapturedAtMs) {
|
|
310
|
+
if (baseEntry.bytes !== st.size) return false;
|
|
311
|
+
if (Math.abs(baseEntry.mtimeMs - st.mtimeMs) > OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
if (baseCapturedAtMs === null) return false;
|
|
315
|
+
return st.ctimeMs - baseCapturedAtMs <= OFFLINE_SYNC_FAST_BASE_MTIME_TOLERANCE_MS;
|
|
316
|
+
}
|
|
317
|
+
async function canReuseFastBaseFileStateFromDisk(baseEntry, filePath, st, baseCapturedAtMs) {
|
|
318
|
+
if (!canReuseFastBaseFileState(baseEntry, st, baseCapturedAtMs)) return false;
|
|
319
|
+
return !await fileIsSecureStoreEncrypted(filePath).catch(() => true);
|
|
320
|
+
}
|
|
282
321
|
async function readOfflineSyncFileRecord(options) {
|
|
283
322
|
const relPath = validateArchiveRelativePath(options.relPath, "offlineSyncFile.path");
|
|
284
|
-
|
|
285
|
-
|
|
323
|
+
let content = null;
|
|
324
|
+
let digest;
|
|
325
|
+
if (options.includeContent) {
|
|
326
|
+
content = options.readFile ? await options.readFile({ root: options.root.abs, path: relPath, filePath: options.filePath }) : await readFile(options.filePath);
|
|
327
|
+
digest = sha256Buffer(content);
|
|
328
|
+
} else if (options.readFileDigest) {
|
|
329
|
+
digest = await options.readFileDigest({ root: options.root.abs, path: relPath, filePath: options.filePath });
|
|
330
|
+
} else if (options.readFile) {
|
|
331
|
+
content = await options.readFile({ root: options.root.abs, path: relPath, filePath: options.filePath });
|
|
332
|
+
digest = sha256Buffer(content);
|
|
333
|
+
content = null;
|
|
334
|
+
} else {
|
|
335
|
+
digest = await sha256File(options.filePath);
|
|
336
|
+
}
|
|
286
337
|
const st = await stat(options.filePath);
|
|
287
338
|
return {
|
|
288
339
|
path: relPath,
|
|
289
340
|
sha256: digest.sha256,
|
|
290
341
|
bytes: digest.bytes,
|
|
291
342
|
mtimeMs: st.mtimeMs,
|
|
292
|
-
...
|
|
343
|
+
...content ? { contentBase64: content.toString("base64") } : {}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
async function sha256File(filePath) {
|
|
347
|
+
const hash = createHash("sha256");
|
|
348
|
+
let bytes = 0;
|
|
349
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
350
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
351
|
+
hash.update(buffer);
|
|
352
|
+
bytes += buffer.length;
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
sha256: hash.digest("hex"),
|
|
356
|
+
bytes
|
|
293
357
|
};
|
|
294
358
|
}
|
|
295
359
|
async function fileIsSecureStoreEncrypted(filePath) {
|
|
@@ -337,7 +401,58 @@ async function buildOfflineSyncSnapshot(options) {
|
|
|
337
401
|
relPath: relPosix,
|
|
338
402
|
filePath: abs,
|
|
339
403
|
includeContent: options.includeContent === true,
|
|
340
|
-
readFile: options.readFile
|
|
404
|
+
readFile: options.readFile,
|
|
405
|
+
readFileDigest: options.readFileDigest
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
await walk(root.abs);
|
|
410
|
+
return {
|
|
411
|
+
format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
412
|
+
schemaVersion: 1,
|
|
413
|
+
createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
414
|
+
sourceId: normalizeSourceId(options.sourceId, "sourceId"),
|
|
415
|
+
includeTranscripts,
|
|
416
|
+
files: files.sort(compareByPath)
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
async function buildOfflineSyncSnapshotFromBase(options) {
|
|
420
|
+
const rootAbs = path.resolve(options.root);
|
|
421
|
+
const root = await prepareSafeArchiveRoot(rootAbs, "buildOfflineSyncSnapshotFromBase", "root");
|
|
422
|
+
const includeTranscripts = options.includeTranscripts !== false;
|
|
423
|
+
const base = byPath(filterBaseFilesForMode(
|
|
424
|
+
normalizeFileStates(options.baseFiles),
|
|
425
|
+
includeTranscripts
|
|
426
|
+
));
|
|
427
|
+
const rawBaseCapturedAtMs = options.baseCapturedAt?.getTime();
|
|
428
|
+
const baseCapturedAtMs = rawBaseCapturedAtMs !== void 0 && Number.isFinite(rawBaseCapturedAtMs) ? rawBaseCapturedAtMs : null;
|
|
429
|
+
const files = [];
|
|
430
|
+
async function walk(dirAbs) {
|
|
431
|
+
let entries = await readdir(dirAbs, { withFileTypes: true });
|
|
432
|
+
entries = entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
433
|
+
for (const entry of entries) {
|
|
434
|
+
const abs = path.join(dirAbs, entry.name);
|
|
435
|
+
const relPosix = path.relative(root.abs, abs).split(path.sep).join("/");
|
|
436
|
+
if (shouldExcludeRelPath(relPosix, includeTranscripts)) continue;
|
|
437
|
+
if (entry.isSymbolicLink()) continue;
|
|
438
|
+
if (entry.isDirectory()) {
|
|
439
|
+
await walk(abs);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (!entry.isFile()) continue;
|
|
443
|
+
const st = await stat(abs);
|
|
444
|
+
const baseEntry = base.get(relPosix);
|
|
445
|
+
if (options.includeContent !== true && baseEntry && baseCapturedAtMs !== null && await canReuseFastBaseFileStateFromDisk(baseEntry, abs, st, baseCapturedAtMs)) {
|
|
446
|
+
files.push(baseEntry);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
files.push(await readOfflineSyncFileRecord({
|
|
450
|
+
root,
|
|
451
|
+
relPath: relPosix,
|
|
452
|
+
filePath: abs,
|
|
453
|
+
includeContent: options.includeContent === true,
|
|
454
|
+
readFile: options.readFile,
|
|
455
|
+
readFileDigest: options.readFileDigest
|
|
341
456
|
}));
|
|
342
457
|
}
|
|
343
458
|
}
|
|
@@ -375,7 +490,8 @@ async function buildOfflineSyncSnapshotForPaths(options) {
|
|
|
375
490
|
relPath,
|
|
376
491
|
filePath,
|
|
377
492
|
includeContent: options.includeContent === true,
|
|
378
|
-
readFile: options.readFile
|
|
493
|
+
readFile: options.readFile,
|
|
494
|
+
readFileDigest: options.readFileDigest
|
|
379
495
|
}));
|
|
380
496
|
}
|
|
381
497
|
return {
|
|
@@ -452,25 +568,45 @@ async function readOfflineSyncFileContentChunk(options) {
|
|
|
452
568
|
}
|
|
453
569
|
async function buildOfflineSyncChangeset(options) {
|
|
454
570
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
455
|
-
const
|
|
456
|
-
(options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]"))
|
|
457
|
-
);
|
|
458
|
-
const base = byPath(filterBaseFilesForMode(
|
|
459
|
-
normalizeFileStates(options.baseFiles),
|
|
460
|
-
includeTranscripts
|
|
461
|
-
));
|
|
462
|
-
const current = await buildOfflineSyncSnapshot({
|
|
571
|
+
const current = await buildOfflineSyncSnapshotFromBase({
|
|
463
572
|
root: options.root,
|
|
464
573
|
sourceId: options.sourceId,
|
|
574
|
+
baseFiles: options.baseFiles,
|
|
575
|
+
baseCapturedAt: options.baseCapturedAt,
|
|
465
576
|
includeContent: false,
|
|
466
577
|
includeTranscripts,
|
|
467
578
|
now: options.now,
|
|
579
|
+
readFile: options.readFile,
|
|
580
|
+
readFileDigest: options.readFileDigest
|
|
581
|
+
});
|
|
582
|
+
return buildOfflineSyncChangesetFromSnapshot({
|
|
583
|
+
root: options.root,
|
|
584
|
+
sourceId: options.sourceId,
|
|
585
|
+
baseFiles: options.baseFiles,
|
|
586
|
+
currentFiles: current.files,
|
|
587
|
+
excludePaths: options.excludePaths,
|
|
588
|
+
includeTranscripts,
|
|
589
|
+
now: options.now,
|
|
468
590
|
readFile: options.readFile
|
|
469
591
|
});
|
|
470
|
-
|
|
592
|
+
}
|
|
593
|
+
async function buildOfflineSyncChangesetFromSnapshot(options) {
|
|
594
|
+
const includeTranscripts = options.includeTranscripts !== false;
|
|
595
|
+
const excludedPaths = new Set(
|
|
596
|
+
(options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]"))
|
|
597
|
+
);
|
|
598
|
+
const base = byPath(filterBaseFilesForMode(
|
|
599
|
+
normalizeFileStates(options.baseFiles),
|
|
600
|
+
includeTranscripts
|
|
601
|
+
));
|
|
602
|
+
const currentMap = byPath(filterBaseFilesForMode(
|
|
603
|
+
normalizeFileStates(options.currentFiles),
|
|
604
|
+
includeTranscripts
|
|
605
|
+
));
|
|
471
606
|
const changes = [];
|
|
472
607
|
for (const relPath of unionPaths(base, currentMap)) {
|
|
473
608
|
if (excludedPaths.has(relPath)) continue;
|
|
609
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) continue;
|
|
474
610
|
const baseEntry = base.get(relPath);
|
|
475
611
|
const currentEntry = currentMap.get(relPath);
|
|
476
612
|
if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
|
|
@@ -508,7 +644,7 @@ async function buildOfflineSyncChangeset(options) {
|
|
|
508
644
|
schemaVersion: 1,
|
|
509
645
|
createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
510
646
|
sourceId: normalizeSourceId(options.sourceId, "sourceId"),
|
|
511
|
-
includeTranscripts
|
|
647
|
+
includeTranscripts,
|
|
512
648
|
changes: changes.sort(compareByPath)
|
|
513
649
|
};
|
|
514
650
|
}
|
|
@@ -523,22 +659,37 @@ function summarizeOfflineSyncChangeset(changeset) {
|
|
|
523
659
|
}
|
|
524
660
|
async function summarizeOfflineSyncPendingChanges(options) {
|
|
525
661
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
526
|
-
const
|
|
527
|
-
normalizeFileStates(options.baseFiles),
|
|
528
|
-
includeTranscripts
|
|
529
|
-
));
|
|
530
|
-
const current = await buildOfflineSyncSnapshot({
|
|
662
|
+
const current = await buildOfflineSyncSnapshotFromBase({
|
|
531
663
|
root: options.root,
|
|
532
664
|
sourceId: options.sourceId,
|
|
665
|
+
baseFiles: options.baseFiles,
|
|
666
|
+
baseCapturedAt: options.baseCapturedAt,
|
|
533
667
|
includeContent: false,
|
|
534
668
|
includeTranscripts,
|
|
535
669
|
now: options.now,
|
|
536
|
-
readFile: options.readFile
|
|
670
|
+
readFile: options.readFile,
|
|
671
|
+
readFileDigest: options.readFileDigest
|
|
672
|
+
});
|
|
673
|
+
return summarizeOfflineSyncPendingFiles({
|
|
674
|
+
baseFiles: options.baseFiles,
|
|
675
|
+
currentFiles: current.files,
|
|
676
|
+
includeTranscripts
|
|
537
677
|
});
|
|
538
|
-
|
|
678
|
+
}
|
|
679
|
+
function summarizeOfflineSyncPendingFiles(options) {
|
|
680
|
+
const includeTranscripts = options.includeTranscripts !== false;
|
|
681
|
+
const base = byPath(filterBaseFilesForMode(
|
|
682
|
+
normalizeFileStates(options.baseFiles),
|
|
683
|
+
includeTranscripts
|
|
684
|
+
));
|
|
685
|
+
const currentMap = byPath(filterBaseFilesForMode(
|
|
686
|
+
normalizeFileStates(options.currentFiles),
|
|
687
|
+
includeTranscripts
|
|
688
|
+
));
|
|
539
689
|
let upserts = 0;
|
|
540
690
|
let deletes = 0;
|
|
541
691
|
for (const relPath of unionPaths(base, currentMap)) {
|
|
692
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) continue;
|
|
542
693
|
const baseEntry = base.get(relPath);
|
|
543
694
|
const currentEntry = currentMap.get(relPath);
|
|
544
695
|
if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
|
|
@@ -566,30 +717,61 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
566
717
|
requireContent: false
|
|
567
718
|
});
|
|
568
719
|
const root = await ensureSyncRoot(options.root, "applyOfflineSyncSnapshot");
|
|
569
|
-
const
|
|
720
|
+
const currentFiles = options.currentFiles ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), snapshot.includeTranscripts).sort(compareByPath) : (await buildOfflineSyncSnapshot({
|
|
570
721
|
root: root.abs,
|
|
571
722
|
sourceId: "local",
|
|
572
723
|
includeContent: false,
|
|
573
724
|
includeTranscripts: snapshot.includeTranscripts,
|
|
574
|
-
readFile: options.readFile
|
|
575
|
-
|
|
576
|
-
|
|
725
|
+
readFile: options.readFile,
|
|
726
|
+
readFileDigest: options.readFileDigest
|
|
727
|
+
})).files;
|
|
728
|
+
const currentMap = byPath(currentFiles);
|
|
729
|
+
const deferredPaths = new Set(options.deferredPaths ?? []);
|
|
577
730
|
const nextBase = new Map(baseMap);
|
|
578
731
|
const conflicts = [];
|
|
579
732
|
let upserted = 0;
|
|
580
733
|
let deleted = 0;
|
|
581
734
|
let skipped = 0;
|
|
582
735
|
let pendingLocal = 0;
|
|
736
|
+
const conflictIncomingBuffer = (relPath) => {
|
|
737
|
+
if (options.writeConflictCopies === false) return void 0;
|
|
738
|
+
const buffer = incomingBuffers.get(relPath);
|
|
739
|
+
if (buffer || options.allowMissingConflictContent === true) return buffer;
|
|
740
|
+
return requiredBuffer(incomingBuffers, relPath);
|
|
741
|
+
};
|
|
583
742
|
for (const relPath of unionPaths(baseMap, incomingMap, currentMap)) {
|
|
584
743
|
const base = baseMap.get(relPath);
|
|
585
744
|
const incoming = incomingMap.get(relPath);
|
|
586
745
|
const currentEntry = currentMap.get(relPath);
|
|
746
|
+
if (deferredPaths.has(relPath)) {
|
|
747
|
+
if (base) nextBase.set(relPath, base);
|
|
748
|
+
else nextBase.delete(relPath);
|
|
749
|
+
skipped += 1;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
587
752
|
if (incoming) {
|
|
588
753
|
if (currentEntry?.sha256 === incoming.sha256) {
|
|
589
|
-
|
|
754
|
+
if (await setSafeFileMtime(root, relPath, incoming.mtimeMs)) {
|
|
755
|
+
nextBase.set(relPath, toFileState(incoming));
|
|
756
|
+
} else {
|
|
757
|
+
if (base) nextBase.set(relPath, base);
|
|
758
|
+
else nextBase.delete(relPath);
|
|
759
|
+
pendingLocal += 1;
|
|
760
|
+
}
|
|
590
761
|
skipped += 1;
|
|
591
762
|
continue;
|
|
592
763
|
}
|
|
764
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath) && currentEntry && base && incoming.sha256 === base.sha256) {
|
|
765
|
+
nextBase.set(relPath, base);
|
|
766
|
+
skipped += 1;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) {
|
|
770
|
+
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
|
|
771
|
+
nextBase.set(relPath, toFileState(incoming));
|
|
772
|
+
upserted += 1;
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
593
775
|
if (!currentEntry && base && incoming.sha256 === base.sha256) {
|
|
594
776
|
nextBase.set(relPath, base);
|
|
595
777
|
pendingLocal += 1;
|
|
@@ -603,7 +785,7 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
603
785
|
reason: "local_deleted_remote_modified",
|
|
604
786
|
baseSha256: base.sha256,
|
|
605
787
|
incomingSha256: incoming.sha256,
|
|
606
|
-
incomingBuffer:
|
|
788
|
+
incomingBuffer: conflictIncomingBuffer(relPath),
|
|
607
789
|
writeConflictCopies: options.writeConflictCopies !== false,
|
|
608
790
|
sourceId: snapshot.sourceId,
|
|
609
791
|
writeFile: options.writeFile
|
|
@@ -612,13 +794,13 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
612
794
|
continue;
|
|
613
795
|
}
|
|
614
796
|
if (!currentEntry && !base) {
|
|
615
|
-
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile);
|
|
797
|
+
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
|
|
616
798
|
nextBase.set(relPath, toFileState(incoming));
|
|
617
799
|
upserted += 1;
|
|
618
800
|
continue;
|
|
619
801
|
}
|
|
620
802
|
if (base && currentEntry && currentEntry.sha256 === base.sha256) {
|
|
621
|
-
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile);
|
|
803
|
+
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
|
|
622
804
|
nextBase.set(relPath, toFileState(incoming));
|
|
623
805
|
upserted += 1;
|
|
624
806
|
continue;
|
|
@@ -636,7 +818,7 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
636
818
|
baseSha256: base?.sha256,
|
|
637
819
|
localSha256: currentEntry?.sha256,
|
|
638
820
|
incomingSha256: incoming.sha256,
|
|
639
|
-
incomingBuffer:
|
|
821
|
+
incomingBuffer: conflictIncomingBuffer(relPath),
|
|
640
822
|
writeConflictCopies: options.writeConflictCopies !== false,
|
|
641
823
|
sourceId: snapshot.sourceId,
|
|
642
824
|
writeFile: options.writeFile
|
|
@@ -649,6 +831,17 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
649
831
|
skipped += 1;
|
|
650
832
|
continue;
|
|
651
833
|
}
|
|
834
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath) && base) {
|
|
835
|
+
await deleteSafeFile(root, relPath, options.deleteFile);
|
|
836
|
+
nextBase.delete(relPath);
|
|
837
|
+
deleted += 1;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) {
|
|
841
|
+
pendingLocal += 1;
|
|
842
|
+
skipped += 1;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
652
845
|
if (base && currentEntry.sha256 === base.sha256) {
|
|
653
846
|
await deleteSafeFile(root, relPath, options.deleteFile);
|
|
654
847
|
nextBase.delete(relPath);
|
|
@@ -690,14 +883,16 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
690
883
|
const root = await ensureSyncRoot(options.root, "applyOfflineSyncChangeset");
|
|
691
884
|
const records = changeset.changes.filter((change) => change.type === "upsert").map((change) => change.file);
|
|
692
885
|
const incomingBuffers = verifyRecordContents(records, "offline sync changeset");
|
|
693
|
-
const
|
|
886
|
+
const currentFiles = options.currentFiles ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), changeset.includeTranscripts).sort(compareByPath) : (await buildOfflineSyncSnapshotForPaths({
|
|
694
887
|
root: root.abs,
|
|
695
888
|
sourceId: "local",
|
|
889
|
+
paths: changeset.changes.map((change) => change.path),
|
|
696
890
|
includeContent: false,
|
|
697
891
|
includeTranscripts: changeset.includeTranscripts,
|
|
698
|
-
readFile: options.readFile
|
|
699
|
-
|
|
700
|
-
|
|
892
|
+
readFile: options.readFile,
|
|
893
|
+
readFileDigest: options.readFileDigest
|
|
894
|
+
})).files;
|
|
895
|
+
const currentMap = byPath(currentFiles);
|
|
701
896
|
const conflicts = [];
|
|
702
897
|
let appliedUpserts = 0;
|
|
703
898
|
let appliedDeletes = 0;
|
|
@@ -706,12 +901,18 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
706
901
|
const currentEntry = currentMap.get(change.path);
|
|
707
902
|
if (change.type === "upsert") {
|
|
708
903
|
if (currentEntry?.sha256 === change.file.sha256) {
|
|
709
|
-
|
|
904
|
+
if (await setSafeFileMtime(root, change.path, change.file.mtimeMs)) {
|
|
905
|
+
skipped += 1;
|
|
906
|
+
} else {
|
|
907
|
+
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
|
|
908
|
+
currentMap.set(change.path, toFileState(change.file));
|
|
909
|
+
appliedUpserts += 1;
|
|
910
|
+
}
|
|
710
911
|
continue;
|
|
711
912
|
}
|
|
712
913
|
if (!change.baseSha256) {
|
|
713
914
|
if (!currentEntry) {
|
|
714
|
-
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile);
|
|
915
|
+
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
|
|
715
916
|
currentMap.set(change.path, toFileState(change.file));
|
|
716
917
|
appliedUpserts += 1;
|
|
717
918
|
continue;
|
|
@@ -730,7 +931,7 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
730
931
|
continue;
|
|
731
932
|
}
|
|
732
933
|
if (currentEntry?.sha256 === change.baseSha256) {
|
|
733
|
-
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile);
|
|
934
|
+
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
|
|
734
935
|
currentMap.set(change.path, toFileState(change.file));
|
|
735
936
|
appliedUpserts += 1;
|
|
736
937
|
continue;
|
|
@@ -771,7 +972,15 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
771
972
|
appliedDeletes,
|
|
772
973
|
skipped,
|
|
773
974
|
conflicts,
|
|
774
|
-
currentFiles: [...currentMap.values()].sort(compareByPath)
|
|
975
|
+
currentFiles: options.returnCurrentFiles === false ? [...currentMap.values()].sort(compareByPath) : (await buildOfflineSyncSnapshot({
|
|
976
|
+
root: root.abs,
|
|
977
|
+
sourceId: "local",
|
|
978
|
+
includeContent: false,
|
|
979
|
+
includeTranscripts: changeset.includeTranscripts,
|
|
980
|
+
readFile: options.readFile,
|
|
981
|
+
readFileDigest: options.readFileDigest
|
|
982
|
+
})).files,
|
|
983
|
+
...options.returnCurrentFiles === false ? { currentFilesComplete: false } : {}
|
|
775
984
|
};
|
|
776
985
|
}
|
|
777
986
|
function verifyRecordContents(records, context, options = {}) {
|
|
@@ -826,10 +1035,11 @@ function toFileState(file) {
|
|
|
826
1035
|
mtimeMs: file.mtimeMs
|
|
827
1036
|
};
|
|
828
1037
|
}
|
|
829
|
-
async function writeSafeFile(root, relPath, content, writeFileHook) {
|
|
1038
|
+
async function writeSafeFile(root, relPath, content, writeFileHook, mtimeMs) {
|
|
830
1039
|
const target = await resolveSafeArchiveTarget(root, relPath);
|
|
831
1040
|
if (writeFileHook) {
|
|
832
1041
|
await writeFileHook({ root: root.abs, path: relPath, filePath: target, content });
|
|
1042
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
833
1043
|
return;
|
|
834
1044
|
}
|
|
835
1045
|
await mkdir(path.dirname(target), { recursive: true });
|
|
@@ -847,12 +1057,28 @@ async function writeSafeFile(root, relPath, content, writeFileHook) {
|
|
|
847
1057
|
throw new Error(`offline sync target is a symlink: ${relPath}`);
|
|
848
1058
|
}
|
|
849
1059
|
await rename(tmp, target);
|
|
1060
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
850
1061
|
} catch (error) {
|
|
851
1062
|
await unlink(tmp).catch(() => {
|
|
852
1063
|
});
|
|
853
1064
|
throw error;
|
|
854
1065
|
}
|
|
855
1066
|
}
|
|
1067
|
+
async function setSafeFileMtime(root, relPath, mtimeMs) {
|
|
1068
|
+
if (mtimeMs === void 0) return true;
|
|
1069
|
+
const target = await resolveSafeArchiveTarget(root, relPath);
|
|
1070
|
+
const targetStat = await lstat(target).catch((error) => {
|
|
1071
|
+
if (error.code === "ENOENT") return null;
|
|
1072
|
+
throw error;
|
|
1073
|
+
});
|
|
1074
|
+
if (!targetStat) return false;
|
|
1075
|
+
if (targetStat.isSymbolicLink()) {
|
|
1076
|
+
throw new Error(`offline sync target is a symlink: ${relPath}`);
|
|
1077
|
+
}
|
|
1078
|
+
const mtime = new Date(assertOfflineSyncMtimeMs(mtimeMs, "mtimeMs"));
|
|
1079
|
+
await utimes(target, mtime, mtime);
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
856
1082
|
async function applyOfflineSyncFileContentChunk(options) {
|
|
857
1083
|
const root = await ensureSyncRoot(options.root, "applyOfflineSyncFileContentChunk");
|
|
858
1084
|
const sourceId = normalizeSourceId(options.sourceId, "sourceId");
|
|
@@ -863,9 +1089,10 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
863
1089
|
}
|
|
864
1090
|
const sha256 = assertSha256(options.sha256, "sha256");
|
|
865
1091
|
const bytes = assertNonNegativeInteger(options.bytes, "bytes");
|
|
866
|
-
const mtimeMs =
|
|
1092
|
+
const mtimeMs = assertOfflineSyncMtimeMs(options.mtimeMs, "mtimeMs");
|
|
867
1093
|
const offset = options.offset === void 0 ? 0 : assertNonNegativeInteger(options.offset, "offset");
|
|
868
1094
|
const baseSha256 = options.baseSha256 === void 0 ? void 0 : assertSha256(options.baseSha256, "baseSha256");
|
|
1095
|
+
const preferIncomingRuntimeFile = shouldPreferIncomingOfflineRuntimeFile(relPath);
|
|
869
1096
|
if (!Buffer.isBuffer(options.content)) {
|
|
870
1097
|
throw new Error("content must be a Buffer");
|
|
871
1098
|
}
|
|
@@ -886,8 +1113,85 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
886
1113
|
if (options.writeFile && !options.writeStagingFile) {
|
|
887
1114
|
throw new Error("offline sync upload storage hooks require writeStagingFile");
|
|
888
1115
|
}
|
|
1116
|
+
const baseResult = {
|
|
1117
|
+
path: relPath,
|
|
1118
|
+
sha256,
|
|
1119
|
+
bytes,
|
|
1120
|
+
mtimeMs,
|
|
1121
|
+
offset,
|
|
1122
|
+
chunkBytes: options.content.length,
|
|
1123
|
+
done: offset + options.content.length === bytes
|
|
1124
|
+
};
|
|
1125
|
+
const currentFileConflict = async (currentFile2) => {
|
|
1126
|
+
if (!baseSha256 && currentFile2 && !preferIncomingRuntimeFile) {
|
|
1127
|
+
const conflict = await recordConflict({
|
|
1128
|
+
root,
|
|
1129
|
+
relPath,
|
|
1130
|
+
reason: "remote_exists_for_local_create",
|
|
1131
|
+
localSha256: currentFile2.sha256,
|
|
1132
|
+
incomingSha256: sha256,
|
|
1133
|
+
writeConflictCopies: false,
|
|
1134
|
+
sourceId,
|
|
1135
|
+
writeFile: options.writeFile
|
|
1136
|
+
});
|
|
1137
|
+
return {
|
|
1138
|
+
conflict,
|
|
1139
|
+
currentFile: currentFile2
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
if (baseSha256 && currentFile2?.sha256 !== baseSha256 && !preferIncomingRuntimeFile) {
|
|
1143
|
+
const conflict = await recordConflict({
|
|
1144
|
+
root,
|
|
1145
|
+
relPath,
|
|
1146
|
+
reason: currentFile2 ? "remote_changed_for_local_update" : "remote_deleted_for_local_update",
|
|
1147
|
+
baseSha256,
|
|
1148
|
+
localSha256: currentFile2?.sha256,
|
|
1149
|
+
incomingSha256: sha256,
|
|
1150
|
+
writeConflictCopies: false,
|
|
1151
|
+
sourceId,
|
|
1152
|
+
writeFile: options.writeFile
|
|
1153
|
+
});
|
|
1154
|
+
return {
|
|
1155
|
+
conflict,
|
|
1156
|
+
...currentFile2 ? { currentFile: currentFile2 } : {}
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
return null;
|
|
1160
|
+
};
|
|
889
1161
|
if (offset === 0) {
|
|
890
1162
|
await pruneOfflineUploadStaging(root);
|
|
1163
|
+
const currentSnapshot2 = await buildOfflineSyncSnapshotForPaths({
|
|
1164
|
+
root: root.abs,
|
|
1165
|
+
sourceId: "local",
|
|
1166
|
+
paths: [relPath],
|
|
1167
|
+
includeContent: false,
|
|
1168
|
+
includeTranscripts,
|
|
1169
|
+
readFile: options.readFile,
|
|
1170
|
+
readFileDigest: options.readFileDigest
|
|
1171
|
+
});
|
|
1172
|
+
const currentFile2 = currentSnapshot2.files[0];
|
|
1173
|
+
if (currentFile2?.sha256 === sha256) {
|
|
1174
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1175
|
+
return {
|
|
1176
|
+
...baseResult,
|
|
1177
|
+
done: true,
|
|
1178
|
+
chunkBytes: 0,
|
|
1179
|
+
applied: false,
|
|
1180
|
+
skipped: true,
|
|
1181
|
+
currentFile: toFileState(currentFile2)
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
const conflictResult = await currentFileConflict(currentFile2 ? toFileState(currentFile2) : void 0);
|
|
1185
|
+
if (conflictResult) {
|
|
1186
|
+
return {
|
|
1187
|
+
...baseResult,
|
|
1188
|
+
done: true,
|
|
1189
|
+
chunkBytes: 0,
|
|
1190
|
+
applied: false,
|
|
1191
|
+
skipped: false,
|
|
1192
|
+
...conflictResult
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
891
1195
|
}
|
|
892
1196
|
const upload = await writeOfflineUploadChunk({
|
|
893
1197
|
root,
|
|
@@ -901,16 +1205,7 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
901
1205
|
writeFile: options.writeFile,
|
|
902
1206
|
writeStagingFile: options.writeStagingFile
|
|
903
1207
|
});
|
|
904
|
-
const done =
|
|
905
|
-
const baseResult = {
|
|
906
|
-
path: relPath,
|
|
907
|
-
sha256,
|
|
908
|
-
bytes,
|
|
909
|
-
mtimeMs,
|
|
910
|
-
offset,
|
|
911
|
-
chunkBytes: options.content.length,
|
|
912
|
-
done
|
|
913
|
-
};
|
|
1208
|
+
const done = baseResult.done;
|
|
914
1209
|
if (!done) {
|
|
915
1210
|
return {
|
|
916
1211
|
...baseResult,
|
|
@@ -934,7 +1229,8 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
934
1229
|
paths: [relPath],
|
|
935
1230
|
includeContent: false,
|
|
936
1231
|
includeTranscripts,
|
|
937
|
-
readFile: options.readFile
|
|
1232
|
+
readFile: options.readFile,
|
|
1233
|
+
readFileDigest: options.readFileDigest
|
|
938
1234
|
});
|
|
939
1235
|
const currentFile = currentSnapshot.files[0];
|
|
940
1236
|
const uploadedState = {
|
|
@@ -945,53 +1241,24 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
945
1241
|
};
|
|
946
1242
|
try {
|
|
947
1243
|
if (currentFile?.sha256 === sha256) {
|
|
1244
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
948
1245
|
return {
|
|
949
1246
|
...baseResult,
|
|
950
1247
|
applied: false,
|
|
951
1248
|
skipped: true,
|
|
952
|
-
currentFile:
|
|
1249
|
+
currentFile: uploadedState
|
|
953
1250
|
};
|
|
954
1251
|
}
|
|
955
|
-
|
|
956
|
-
|
|
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
|
-
});
|
|
1252
|
+
const conflictResult = await currentFileConflict(currentFile ? toFileState(currentFile) : void 0);
|
|
1253
|
+
if (conflictResult) {
|
|
966
1254
|
return {
|
|
967
1255
|
...baseResult,
|
|
968
1256
|
applied: false,
|
|
969
1257
|
skipped: false,
|
|
970
|
-
|
|
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) } : {}
|
|
1258
|
+
...conflictResult
|
|
992
1259
|
};
|
|
993
1260
|
}
|
|
994
|
-
await writeSafeFileFromUpload(root, relPath, upload, options.readFile, options.writeFileChunks);
|
|
1261
|
+
await writeSafeFileFromUpload(root, relPath, upload, options.readFile, options.writeFileChunks, mtimeMs);
|
|
995
1262
|
return {
|
|
996
1263
|
...baseResult,
|
|
997
1264
|
applied: true,
|
|
@@ -1137,11 +1404,12 @@ async function digestOfflineUploadStagingContent(options) {
|
|
|
1137
1404
|
}
|
|
1138
1405
|
return { sha256: hash.digest("hex"), bytes };
|
|
1139
1406
|
}
|
|
1140
|
-
async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFileChunks) {
|
|
1407
|
+
async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFileChunks, mtimeMs) {
|
|
1141
1408
|
const target = await resolveSafeArchiveTarget(root, relPath);
|
|
1142
1409
|
const chunks = readOfflineUploadStagingChunks({ root, upload, readFile: readFile2 });
|
|
1143
1410
|
if (writeFileChunks) {
|
|
1144
1411
|
await writeFileChunks({ root: root.abs, path: relPath, filePath: target, chunks });
|
|
1412
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1145
1413
|
return;
|
|
1146
1414
|
}
|
|
1147
1415
|
await mkdir(path.dirname(target), { recursive: true });
|
|
@@ -1163,6 +1431,7 @@ async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFi
|
|
|
1163
1431
|
throw new Error(`offline sync target is a symlink: ${relPath}`);
|
|
1164
1432
|
}
|
|
1165
1433
|
await rename(tmp, target);
|
|
1434
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1166
1435
|
} catch (error) {
|
|
1167
1436
|
await handle.close().catch(() => {
|
|
1168
1437
|
});
|
|
@@ -1301,14 +1570,20 @@ export {
|
|
|
1301
1570
|
OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
|
|
1302
1571
|
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES,
|
|
1303
1572
|
OFFLINE_SYNC_APPLY_MAX_BODY_BYTES,
|
|
1573
|
+
OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES,
|
|
1574
|
+
OFFLINE_SYNC_MAX_MTIME_MS,
|
|
1304
1575
|
normalizeOfflineSyncSnapshot,
|
|
1305
1576
|
normalizeOfflineSyncChangeset,
|
|
1577
|
+
shouldPreferIncomingOfflineRuntimeFile,
|
|
1306
1578
|
buildOfflineSyncSnapshot,
|
|
1579
|
+
buildOfflineSyncSnapshotFromBase,
|
|
1307
1580
|
buildOfflineSyncSnapshotForPaths,
|
|
1308
1581
|
readOfflineSyncFileContentChunk,
|
|
1309
1582
|
buildOfflineSyncChangeset,
|
|
1583
|
+
buildOfflineSyncChangesetFromSnapshot,
|
|
1310
1584
|
summarizeOfflineSyncChangeset,
|
|
1311
1585
|
summarizeOfflineSyncPendingChanges,
|
|
1586
|
+
summarizeOfflineSyncPendingFiles,
|
|
1312
1587
|
applyOfflineSyncSnapshot,
|
|
1313
1588
|
applyOfflineSyncChangeset,
|
|
1314
1589
|
applyOfflineSyncFileContentChunk,
|
|
@@ -1319,4 +1594,4 @@ export {
|
|
|
1319
1594
|
normalizeOfflineSyncState,
|
|
1320
1595
|
fileStatesFromSnapshot
|
|
1321
1596
|
};
|
|
1322
|
-
//# sourceMappingURL=chunk-
|
|
1597
|
+
//# sourceMappingURL=chunk-MG7NA5H3.js.map
|