@remnic/core 1.1.29 → 1.1.31
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 +2 -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-CkZyb35d.d.ts} +10 -2
- 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-6CB4E7ZV.js → chunk-3ZLVGM76.js} +4 -4
- 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-RCZRL5BE.js → chunk-MRILGULB.js} +2 -2
- 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-WDSIV3AK.js → chunk-TPU5L5EY.js} +12 -12
- package/dist/{chunk-AMVN77EU.js → chunk-U7EJOMFC.js} +371 -91
- package/dist/chunk-U7EJOMFC.js.map +1 -0
- 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/{chunk-NW7JW5GA.js → chunk-YROHKYBY.js} +41 -6
- package/dist/chunk-YROHKYBY.js.map +1 -0
- package/dist/{chunk-JUYT2J3K.js → chunk-YU5KIWYQ.js} +136 -8
- package/dist/chunk-YU5KIWYQ.js.map +1 -0
- package/dist/{chunk-LCTP7YRU.js → chunk-ZAVUCJ4H.js} +38 -7
- package/dist/chunk-ZAVUCJ4H.js.map +1 -0
- package/dist/{cli-BguVmIwO.d.ts → cli-kuh9PwZ5.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 +34 -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 +56 -1
- package/dist/offline-sync.js +15 -1
- package/dist/operator-toolkit.js +5 -5
- package/dist/orchestrator.js +9 -9
- package/dist/schemas.d.ts +22 -22
- 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/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +2 -2
- package/package.json +1 -1
- package/src/access-http.test.ts +355 -0
- package/src/access-http.ts +149 -1
- 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 +47 -1
- package/src/index.ts +7 -0
- package/src/offline-sync.test.ts +1055 -1
- package/src/offline-sync.ts +465 -97
- 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-6CB4E7ZV.js.map → chunk-3ZLVGM76.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-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-WDSIV3AK.js.map → chunk-TPU5L5EY.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) {
|
|
@@ -314,10 +378,58 @@ async function readPlainFileContentChunk(options) {
|
|
|
314
378
|
await handle.close();
|
|
315
379
|
}
|
|
316
380
|
}
|
|
381
|
+
async function* iterateOfflineSyncSnapshotFileRecords(options) {
|
|
382
|
+
const rootAbs = path.resolve(options.root);
|
|
383
|
+
const root = await prepareSafeArchiveRoot(rootAbs, "iterateOfflineSyncSnapshotFileRecords", "root");
|
|
384
|
+
const includeTranscripts = options.includeTranscripts !== false;
|
|
385
|
+
async function* walk(dirAbs) {
|
|
386
|
+
let entries = await readdir(dirAbs, { withFileTypes: true });
|
|
387
|
+
entries = entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
const abs = path.join(dirAbs, entry.name);
|
|
390
|
+
const relPosix = path.relative(root.abs, abs).split(path.sep).join("/");
|
|
391
|
+
if (shouldExcludeRelPath(relPosix, includeTranscripts)) continue;
|
|
392
|
+
if (entry.isSymbolicLink()) continue;
|
|
393
|
+
if (entry.isDirectory()) {
|
|
394
|
+
yield* walk(abs);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (!entry.isFile()) continue;
|
|
398
|
+
yield await readOfflineSyncFileRecord({
|
|
399
|
+
root,
|
|
400
|
+
relPath: relPosix,
|
|
401
|
+
filePath: abs,
|
|
402
|
+
includeContent: options.includeContent === true,
|
|
403
|
+
readFile: options.readFile,
|
|
404
|
+
readFileDigest: options.readFileDigest
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
yield* walk(root.abs);
|
|
409
|
+
}
|
|
317
410
|
async function buildOfflineSyncSnapshot(options) {
|
|
411
|
+
const includeTranscripts = options.includeTranscripts !== false;
|
|
412
|
+
const files = [];
|
|
413
|
+
for await (const file of iterateOfflineSyncSnapshotFileRecords(options)) files.push(file);
|
|
414
|
+
return {
|
|
415
|
+
format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
|
|
416
|
+
schemaVersion: 1,
|
|
417
|
+
createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
418
|
+
sourceId: normalizeSourceId(options.sourceId, "sourceId"),
|
|
419
|
+
includeTranscripts,
|
|
420
|
+
files: files.sort(compareByPath)
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
async function buildOfflineSyncSnapshotFromBase(options) {
|
|
318
424
|
const rootAbs = path.resolve(options.root);
|
|
319
|
-
const root = await prepareSafeArchiveRoot(rootAbs, "
|
|
425
|
+
const root = await prepareSafeArchiveRoot(rootAbs, "buildOfflineSyncSnapshotFromBase", "root");
|
|
320
426
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
427
|
+
const base = byPath(filterBaseFilesForMode(
|
|
428
|
+
normalizeFileStates(options.baseFiles),
|
|
429
|
+
includeTranscripts
|
|
430
|
+
));
|
|
431
|
+
const rawBaseCapturedAtMs = options.baseCapturedAt?.getTime();
|
|
432
|
+
const baseCapturedAtMs = rawBaseCapturedAtMs !== void 0 && Number.isFinite(rawBaseCapturedAtMs) ? rawBaseCapturedAtMs : null;
|
|
321
433
|
const files = [];
|
|
322
434
|
async function walk(dirAbs) {
|
|
323
435
|
let entries = await readdir(dirAbs, { withFileTypes: true });
|
|
@@ -332,12 +444,19 @@ async function buildOfflineSyncSnapshot(options) {
|
|
|
332
444
|
continue;
|
|
333
445
|
}
|
|
334
446
|
if (!entry.isFile()) continue;
|
|
447
|
+
const st = await stat(abs);
|
|
448
|
+
const baseEntry = base.get(relPosix);
|
|
449
|
+
if (options.includeContent !== true && baseEntry && baseCapturedAtMs !== null && await canReuseFastBaseFileStateFromDisk(baseEntry, abs, st, baseCapturedAtMs)) {
|
|
450
|
+
files.push(baseEntry);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
335
453
|
files.push(await readOfflineSyncFileRecord({
|
|
336
454
|
root,
|
|
337
455
|
relPath: relPosix,
|
|
338
456
|
filePath: abs,
|
|
339
457
|
includeContent: options.includeContent === true,
|
|
340
|
-
readFile: options.readFile
|
|
458
|
+
readFile: options.readFile,
|
|
459
|
+
readFileDigest: options.readFileDigest
|
|
341
460
|
}));
|
|
342
461
|
}
|
|
343
462
|
}
|
|
@@ -375,7 +494,8 @@ async function buildOfflineSyncSnapshotForPaths(options) {
|
|
|
375
494
|
relPath,
|
|
376
495
|
filePath,
|
|
377
496
|
includeContent: options.includeContent === true,
|
|
378
|
-
readFile: options.readFile
|
|
497
|
+
readFile: options.readFile,
|
|
498
|
+
readFileDigest: options.readFileDigest
|
|
379
499
|
}));
|
|
380
500
|
}
|
|
381
501
|
return {
|
|
@@ -452,25 +572,45 @@ async function readOfflineSyncFileContentChunk(options) {
|
|
|
452
572
|
}
|
|
453
573
|
async function buildOfflineSyncChangeset(options) {
|
|
454
574
|
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({
|
|
575
|
+
const current = await buildOfflineSyncSnapshotFromBase({
|
|
463
576
|
root: options.root,
|
|
464
577
|
sourceId: options.sourceId,
|
|
578
|
+
baseFiles: options.baseFiles,
|
|
579
|
+
baseCapturedAt: options.baseCapturedAt,
|
|
465
580
|
includeContent: false,
|
|
466
581
|
includeTranscripts,
|
|
467
582
|
now: options.now,
|
|
583
|
+
readFile: options.readFile,
|
|
584
|
+
readFileDigest: options.readFileDigest
|
|
585
|
+
});
|
|
586
|
+
return buildOfflineSyncChangesetFromSnapshot({
|
|
587
|
+
root: options.root,
|
|
588
|
+
sourceId: options.sourceId,
|
|
589
|
+
baseFiles: options.baseFiles,
|
|
590
|
+
currentFiles: current.files,
|
|
591
|
+
excludePaths: options.excludePaths,
|
|
592
|
+
includeTranscripts,
|
|
593
|
+
now: options.now,
|
|
468
594
|
readFile: options.readFile
|
|
469
595
|
});
|
|
470
|
-
|
|
596
|
+
}
|
|
597
|
+
async function buildOfflineSyncChangesetFromSnapshot(options) {
|
|
598
|
+
const includeTranscripts = options.includeTranscripts !== false;
|
|
599
|
+
const excludedPaths = new Set(
|
|
600
|
+
(options.excludePaths ?? []).map((relPath) => normalizeRelativePath(relPath, "excludePaths[]"))
|
|
601
|
+
);
|
|
602
|
+
const base = byPath(filterBaseFilesForMode(
|
|
603
|
+
normalizeFileStates(options.baseFiles),
|
|
604
|
+
includeTranscripts
|
|
605
|
+
));
|
|
606
|
+
const currentMap = byPath(filterBaseFilesForMode(
|
|
607
|
+
normalizeFileStates(options.currentFiles),
|
|
608
|
+
includeTranscripts
|
|
609
|
+
));
|
|
471
610
|
const changes = [];
|
|
472
611
|
for (const relPath of unionPaths(base, currentMap)) {
|
|
473
612
|
if (excludedPaths.has(relPath)) continue;
|
|
613
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) continue;
|
|
474
614
|
const baseEntry = base.get(relPath);
|
|
475
615
|
const currentEntry = currentMap.get(relPath);
|
|
476
616
|
if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
|
|
@@ -508,7 +648,7 @@ async function buildOfflineSyncChangeset(options) {
|
|
|
508
648
|
schemaVersion: 1,
|
|
509
649
|
createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
510
650
|
sourceId: normalizeSourceId(options.sourceId, "sourceId"),
|
|
511
|
-
includeTranscripts
|
|
651
|
+
includeTranscripts,
|
|
512
652
|
changes: changes.sort(compareByPath)
|
|
513
653
|
};
|
|
514
654
|
}
|
|
@@ -523,22 +663,37 @@ function summarizeOfflineSyncChangeset(changeset) {
|
|
|
523
663
|
}
|
|
524
664
|
async function summarizeOfflineSyncPendingChanges(options) {
|
|
525
665
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
526
|
-
const
|
|
527
|
-
normalizeFileStates(options.baseFiles),
|
|
528
|
-
includeTranscripts
|
|
529
|
-
));
|
|
530
|
-
const current = await buildOfflineSyncSnapshot({
|
|
666
|
+
const current = await buildOfflineSyncSnapshotFromBase({
|
|
531
667
|
root: options.root,
|
|
532
668
|
sourceId: options.sourceId,
|
|
669
|
+
baseFiles: options.baseFiles,
|
|
670
|
+
baseCapturedAt: options.baseCapturedAt,
|
|
533
671
|
includeContent: false,
|
|
534
672
|
includeTranscripts,
|
|
535
673
|
now: options.now,
|
|
536
|
-
readFile: options.readFile
|
|
674
|
+
readFile: options.readFile,
|
|
675
|
+
readFileDigest: options.readFileDigest
|
|
537
676
|
});
|
|
538
|
-
|
|
677
|
+
return summarizeOfflineSyncPendingFiles({
|
|
678
|
+
baseFiles: options.baseFiles,
|
|
679
|
+
currentFiles: current.files,
|
|
680
|
+
includeTranscripts
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function summarizeOfflineSyncPendingFiles(options) {
|
|
684
|
+
const includeTranscripts = options.includeTranscripts !== false;
|
|
685
|
+
const base = byPath(filterBaseFilesForMode(
|
|
686
|
+
normalizeFileStates(options.baseFiles),
|
|
687
|
+
includeTranscripts
|
|
688
|
+
));
|
|
689
|
+
const currentMap = byPath(filterBaseFilesForMode(
|
|
690
|
+
normalizeFileStates(options.currentFiles),
|
|
691
|
+
includeTranscripts
|
|
692
|
+
));
|
|
539
693
|
let upserts = 0;
|
|
540
694
|
let deletes = 0;
|
|
541
695
|
for (const relPath of unionPaths(base, currentMap)) {
|
|
696
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) continue;
|
|
542
697
|
const baseEntry = base.get(relPath);
|
|
543
698
|
const currentEntry = currentMap.get(relPath);
|
|
544
699
|
if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
|
|
@@ -566,30 +721,61 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
566
721
|
requireContent: false
|
|
567
722
|
});
|
|
568
723
|
const root = await ensureSyncRoot(options.root, "applyOfflineSyncSnapshot");
|
|
569
|
-
const
|
|
724
|
+
const currentFiles = options.currentFiles ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), snapshot.includeTranscripts).sort(compareByPath) : (await buildOfflineSyncSnapshot({
|
|
570
725
|
root: root.abs,
|
|
571
726
|
sourceId: "local",
|
|
572
727
|
includeContent: false,
|
|
573
728
|
includeTranscripts: snapshot.includeTranscripts,
|
|
574
|
-
readFile: options.readFile
|
|
575
|
-
|
|
576
|
-
|
|
729
|
+
readFile: options.readFile,
|
|
730
|
+
readFileDigest: options.readFileDigest
|
|
731
|
+
})).files;
|
|
732
|
+
const currentMap = byPath(currentFiles);
|
|
733
|
+
const deferredPaths = new Set(options.deferredPaths ?? []);
|
|
577
734
|
const nextBase = new Map(baseMap);
|
|
578
735
|
const conflicts = [];
|
|
579
736
|
let upserted = 0;
|
|
580
737
|
let deleted = 0;
|
|
581
738
|
let skipped = 0;
|
|
582
739
|
let pendingLocal = 0;
|
|
740
|
+
const conflictIncomingBuffer = (relPath) => {
|
|
741
|
+
if (options.writeConflictCopies === false) return void 0;
|
|
742
|
+
const buffer = incomingBuffers.get(relPath);
|
|
743
|
+
if (buffer || options.allowMissingConflictContent === true) return buffer;
|
|
744
|
+
return requiredBuffer(incomingBuffers, relPath);
|
|
745
|
+
};
|
|
583
746
|
for (const relPath of unionPaths(baseMap, incomingMap, currentMap)) {
|
|
584
747
|
const base = baseMap.get(relPath);
|
|
585
748
|
const incoming = incomingMap.get(relPath);
|
|
586
749
|
const currentEntry = currentMap.get(relPath);
|
|
750
|
+
if (deferredPaths.has(relPath)) {
|
|
751
|
+
if (base) nextBase.set(relPath, base);
|
|
752
|
+
else nextBase.delete(relPath);
|
|
753
|
+
skipped += 1;
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
587
756
|
if (incoming) {
|
|
588
757
|
if (currentEntry?.sha256 === incoming.sha256) {
|
|
589
|
-
|
|
758
|
+
if (await setSafeFileMtime(root, relPath, incoming.mtimeMs)) {
|
|
759
|
+
nextBase.set(relPath, toFileState(incoming));
|
|
760
|
+
} else {
|
|
761
|
+
if (base) nextBase.set(relPath, base);
|
|
762
|
+
else nextBase.delete(relPath);
|
|
763
|
+
pendingLocal += 1;
|
|
764
|
+
}
|
|
590
765
|
skipped += 1;
|
|
591
766
|
continue;
|
|
592
767
|
}
|
|
768
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath) && currentEntry && base && incoming.sha256 === base.sha256) {
|
|
769
|
+
nextBase.set(relPath, base);
|
|
770
|
+
skipped += 1;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) {
|
|
774
|
+
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
|
|
775
|
+
nextBase.set(relPath, toFileState(incoming));
|
|
776
|
+
upserted += 1;
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
593
779
|
if (!currentEntry && base && incoming.sha256 === base.sha256) {
|
|
594
780
|
nextBase.set(relPath, base);
|
|
595
781
|
pendingLocal += 1;
|
|
@@ -603,7 +789,7 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
603
789
|
reason: "local_deleted_remote_modified",
|
|
604
790
|
baseSha256: base.sha256,
|
|
605
791
|
incomingSha256: incoming.sha256,
|
|
606
|
-
incomingBuffer:
|
|
792
|
+
incomingBuffer: conflictIncomingBuffer(relPath),
|
|
607
793
|
writeConflictCopies: options.writeConflictCopies !== false,
|
|
608
794
|
sourceId: snapshot.sourceId,
|
|
609
795
|
writeFile: options.writeFile
|
|
@@ -612,13 +798,13 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
612
798
|
continue;
|
|
613
799
|
}
|
|
614
800
|
if (!currentEntry && !base) {
|
|
615
|
-
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile);
|
|
801
|
+
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
|
|
616
802
|
nextBase.set(relPath, toFileState(incoming));
|
|
617
803
|
upserted += 1;
|
|
618
804
|
continue;
|
|
619
805
|
}
|
|
620
806
|
if (base && currentEntry && currentEntry.sha256 === base.sha256) {
|
|
621
|
-
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile);
|
|
807
|
+
await writeSafeFile(root, relPath, requiredBuffer(incomingBuffers, relPath), options.writeFile, incoming.mtimeMs);
|
|
622
808
|
nextBase.set(relPath, toFileState(incoming));
|
|
623
809
|
upserted += 1;
|
|
624
810
|
continue;
|
|
@@ -636,7 +822,7 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
636
822
|
baseSha256: base?.sha256,
|
|
637
823
|
localSha256: currentEntry?.sha256,
|
|
638
824
|
incomingSha256: incoming.sha256,
|
|
639
|
-
incomingBuffer:
|
|
825
|
+
incomingBuffer: conflictIncomingBuffer(relPath),
|
|
640
826
|
writeConflictCopies: options.writeConflictCopies !== false,
|
|
641
827
|
sourceId: snapshot.sourceId,
|
|
642
828
|
writeFile: options.writeFile
|
|
@@ -649,6 +835,17 @@ async function applyOfflineSyncSnapshot(options) {
|
|
|
649
835
|
skipped += 1;
|
|
650
836
|
continue;
|
|
651
837
|
}
|
|
838
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath) && base) {
|
|
839
|
+
await deleteSafeFile(root, relPath, options.deleteFile);
|
|
840
|
+
nextBase.delete(relPath);
|
|
841
|
+
deleted += 1;
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) {
|
|
845
|
+
pendingLocal += 1;
|
|
846
|
+
skipped += 1;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
652
849
|
if (base && currentEntry.sha256 === base.sha256) {
|
|
653
850
|
await deleteSafeFile(root, relPath, options.deleteFile);
|
|
654
851
|
nextBase.delete(relPath);
|
|
@@ -690,14 +887,16 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
690
887
|
const root = await ensureSyncRoot(options.root, "applyOfflineSyncChangeset");
|
|
691
888
|
const records = changeset.changes.filter((change) => change.type === "upsert").map((change) => change.file);
|
|
692
889
|
const incomingBuffers = verifyRecordContents(records, "offline sync changeset");
|
|
693
|
-
const
|
|
890
|
+
const currentFiles = options.currentFiles ? filterBaseFilesForMode(normalizeFileStates(options.currentFiles), changeset.includeTranscripts).sort(compareByPath) : (await buildOfflineSyncSnapshotForPaths({
|
|
694
891
|
root: root.abs,
|
|
695
892
|
sourceId: "local",
|
|
893
|
+
paths: changeset.changes.map((change) => change.path),
|
|
696
894
|
includeContent: false,
|
|
697
895
|
includeTranscripts: changeset.includeTranscripts,
|
|
698
|
-
readFile: options.readFile
|
|
699
|
-
|
|
700
|
-
|
|
896
|
+
readFile: options.readFile,
|
|
897
|
+
readFileDigest: options.readFileDigest
|
|
898
|
+
})).files;
|
|
899
|
+
const currentMap = byPath(currentFiles);
|
|
701
900
|
const conflicts = [];
|
|
702
901
|
let appliedUpserts = 0;
|
|
703
902
|
let appliedDeletes = 0;
|
|
@@ -706,12 +905,18 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
706
905
|
const currentEntry = currentMap.get(change.path);
|
|
707
906
|
if (change.type === "upsert") {
|
|
708
907
|
if (currentEntry?.sha256 === change.file.sha256) {
|
|
709
|
-
|
|
908
|
+
if (await setSafeFileMtime(root, change.path, change.file.mtimeMs)) {
|
|
909
|
+
skipped += 1;
|
|
910
|
+
} else {
|
|
911
|
+
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
|
|
912
|
+
currentMap.set(change.path, toFileState(change.file));
|
|
913
|
+
appliedUpserts += 1;
|
|
914
|
+
}
|
|
710
915
|
continue;
|
|
711
916
|
}
|
|
712
917
|
if (!change.baseSha256) {
|
|
713
918
|
if (!currentEntry) {
|
|
714
|
-
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile);
|
|
919
|
+
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
|
|
715
920
|
currentMap.set(change.path, toFileState(change.file));
|
|
716
921
|
appliedUpserts += 1;
|
|
717
922
|
continue;
|
|
@@ -730,7 +935,7 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
730
935
|
continue;
|
|
731
936
|
}
|
|
732
937
|
if (currentEntry?.sha256 === change.baseSha256) {
|
|
733
|
-
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile);
|
|
938
|
+
await writeSafeFile(root, change.path, requiredBuffer(incomingBuffers, change.path), options.writeFile, change.file.mtimeMs);
|
|
734
939
|
currentMap.set(change.path, toFileState(change.file));
|
|
735
940
|
appliedUpserts += 1;
|
|
736
941
|
continue;
|
|
@@ -771,7 +976,15 @@ async function applyOfflineSyncChangeset(options) {
|
|
|
771
976
|
appliedDeletes,
|
|
772
977
|
skipped,
|
|
773
978
|
conflicts,
|
|
774
|
-
currentFiles: [...currentMap.values()].sort(compareByPath)
|
|
979
|
+
currentFiles: options.returnCurrentFiles === false ? [...currentMap.values()].sort(compareByPath) : (await buildOfflineSyncSnapshot({
|
|
980
|
+
root: root.abs,
|
|
981
|
+
sourceId: "local",
|
|
982
|
+
includeContent: false,
|
|
983
|
+
includeTranscripts: changeset.includeTranscripts,
|
|
984
|
+
readFile: options.readFile,
|
|
985
|
+
readFileDigest: options.readFileDigest
|
|
986
|
+
})).files,
|
|
987
|
+
...options.returnCurrentFiles === false ? { currentFilesComplete: false } : {}
|
|
775
988
|
};
|
|
776
989
|
}
|
|
777
990
|
function verifyRecordContents(records, context, options = {}) {
|
|
@@ -826,10 +1039,11 @@ function toFileState(file) {
|
|
|
826
1039
|
mtimeMs: file.mtimeMs
|
|
827
1040
|
};
|
|
828
1041
|
}
|
|
829
|
-
async function writeSafeFile(root, relPath, content, writeFileHook) {
|
|
1042
|
+
async function writeSafeFile(root, relPath, content, writeFileHook, mtimeMs) {
|
|
830
1043
|
const target = await resolveSafeArchiveTarget(root, relPath);
|
|
831
1044
|
if (writeFileHook) {
|
|
832
1045
|
await writeFileHook({ root: root.abs, path: relPath, filePath: target, content });
|
|
1046
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
833
1047
|
return;
|
|
834
1048
|
}
|
|
835
1049
|
await mkdir(path.dirname(target), { recursive: true });
|
|
@@ -847,12 +1061,28 @@ async function writeSafeFile(root, relPath, content, writeFileHook) {
|
|
|
847
1061
|
throw new Error(`offline sync target is a symlink: ${relPath}`);
|
|
848
1062
|
}
|
|
849
1063
|
await rename(tmp, target);
|
|
1064
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
850
1065
|
} catch (error) {
|
|
851
1066
|
await unlink(tmp).catch(() => {
|
|
852
1067
|
});
|
|
853
1068
|
throw error;
|
|
854
1069
|
}
|
|
855
1070
|
}
|
|
1071
|
+
async function setSafeFileMtime(root, relPath, mtimeMs) {
|
|
1072
|
+
if (mtimeMs === void 0) return true;
|
|
1073
|
+
const target = await resolveSafeArchiveTarget(root, relPath);
|
|
1074
|
+
const targetStat = await lstat(target).catch((error) => {
|
|
1075
|
+
if (error.code === "ENOENT") return null;
|
|
1076
|
+
throw error;
|
|
1077
|
+
});
|
|
1078
|
+
if (!targetStat) return false;
|
|
1079
|
+
if (targetStat.isSymbolicLink()) {
|
|
1080
|
+
throw new Error(`offline sync target is a symlink: ${relPath}`);
|
|
1081
|
+
}
|
|
1082
|
+
const mtime = new Date(assertOfflineSyncMtimeMs(mtimeMs, "mtimeMs"));
|
|
1083
|
+
await utimes(target, mtime, mtime);
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
856
1086
|
async function applyOfflineSyncFileContentChunk(options) {
|
|
857
1087
|
const root = await ensureSyncRoot(options.root, "applyOfflineSyncFileContentChunk");
|
|
858
1088
|
const sourceId = normalizeSourceId(options.sourceId, "sourceId");
|
|
@@ -863,9 +1093,10 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
863
1093
|
}
|
|
864
1094
|
const sha256 = assertSha256(options.sha256, "sha256");
|
|
865
1095
|
const bytes = assertNonNegativeInteger(options.bytes, "bytes");
|
|
866
|
-
const mtimeMs =
|
|
1096
|
+
const mtimeMs = assertOfflineSyncMtimeMs(options.mtimeMs, "mtimeMs");
|
|
867
1097
|
const offset = options.offset === void 0 ? 0 : assertNonNegativeInteger(options.offset, "offset");
|
|
868
1098
|
const baseSha256 = options.baseSha256 === void 0 ? void 0 : assertSha256(options.baseSha256, "baseSha256");
|
|
1099
|
+
const preferIncomingRuntimeFile = shouldPreferIncomingOfflineRuntimeFile(relPath);
|
|
869
1100
|
if (!Buffer.isBuffer(options.content)) {
|
|
870
1101
|
throw new Error("content must be a Buffer");
|
|
871
1102
|
}
|
|
@@ -886,8 +1117,85 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
886
1117
|
if (options.writeFile && !options.writeStagingFile) {
|
|
887
1118
|
throw new Error("offline sync upload storage hooks require writeStagingFile");
|
|
888
1119
|
}
|
|
1120
|
+
const baseResult = {
|
|
1121
|
+
path: relPath,
|
|
1122
|
+
sha256,
|
|
1123
|
+
bytes,
|
|
1124
|
+
mtimeMs,
|
|
1125
|
+
offset,
|
|
1126
|
+
chunkBytes: options.content.length,
|
|
1127
|
+
done: offset + options.content.length === bytes
|
|
1128
|
+
};
|
|
1129
|
+
const currentFileConflict = async (currentFile2) => {
|
|
1130
|
+
if (!baseSha256 && currentFile2 && !preferIncomingRuntimeFile) {
|
|
1131
|
+
const conflict = await recordConflict({
|
|
1132
|
+
root,
|
|
1133
|
+
relPath,
|
|
1134
|
+
reason: "remote_exists_for_local_create",
|
|
1135
|
+
localSha256: currentFile2.sha256,
|
|
1136
|
+
incomingSha256: sha256,
|
|
1137
|
+
writeConflictCopies: false,
|
|
1138
|
+
sourceId,
|
|
1139
|
+
writeFile: options.writeFile
|
|
1140
|
+
});
|
|
1141
|
+
return {
|
|
1142
|
+
conflict,
|
|
1143
|
+
currentFile: currentFile2
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
if (baseSha256 && currentFile2?.sha256 !== baseSha256 && !preferIncomingRuntimeFile) {
|
|
1147
|
+
const conflict = await recordConflict({
|
|
1148
|
+
root,
|
|
1149
|
+
relPath,
|
|
1150
|
+
reason: currentFile2 ? "remote_changed_for_local_update" : "remote_deleted_for_local_update",
|
|
1151
|
+
baseSha256,
|
|
1152
|
+
localSha256: currentFile2?.sha256,
|
|
1153
|
+
incomingSha256: sha256,
|
|
1154
|
+
writeConflictCopies: false,
|
|
1155
|
+
sourceId,
|
|
1156
|
+
writeFile: options.writeFile
|
|
1157
|
+
});
|
|
1158
|
+
return {
|
|
1159
|
+
conflict,
|
|
1160
|
+
...currentFile2 ? { currentFile: currentFile2 } : {}
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
return null;
|
|
1164
|
+
};
|
|
889
1165
|
if (offset === 0) {
|
|
890
1166
|
await pruneOfflineUploadStaging(root);
|
|
1167
|
+
const currentSnapshot2 = await buildOfflineSyncSnapshotForPaths({
|
|
1168
|
+
root: root.abs,
|
|
1169
|
+
sourceId: "local",
|
|
1170
|
+
paths: [relPath],
|
|
1171
|
+
includeContent: false,
|
|
1172
|
+
includeTranscripts,
|
|
1173
|
+
readFile: options.readFile,
|
|
1174
|
+
readFileDigest: options.readFileDigest
|
|
1175
|
+
});
|
|
1176
|
+
const currentFile2 = currentSnapshot2.files[0];
|
|
1177
|
+
if (currentFile2?.sha256 === sha256) {
|
|
1178
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1179
|
+
return {
|
|
1180
|
+
...baseResult,
|
|
1181
|
+
done: true,
|
|
1182
|
+
chunkBytes: 0,
|
|
1183
|
+
applied: false,
|
|
1184
|
+
skipped: true,
|
|
1185
|
+
currentFile: toFileState(currentFile2)
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
const conflictResult = await currentFileConflict(currentFile2 ? toFileState(currentFile2) : void 0);
|
|
1189
|
+
if (conflictResult) {
|
|
1190
|
+
return {
|
|
1191
|
+
...baseResult,
|
|
1192
|
+
done: true,
|
|
1193
|
+
chunkBytes: 0,
|
|
1194
|
+
applied: false,
|
|
1195
|
+
skipped: false,
|
|
1196
|
+
...conflictResult
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
891
1199
|
}
|
|
892
1200
|
const upload = await writeOfflineUploadChunk({
|
|
893
1201
|
root,
|
|
@@ -901,16 +1209,7 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
901
1209
|
writeFile: options.writeFile,
|
|
902
1210
|
writeStagingFile: options.writeStagingFile
|
|
903
1211
|
});
|
|
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
|
-
};
|
|
1212
|
+
const done = baseResult.done;
|
|
914
1213
|
if (!done) {
|
|
915
1214
|
return {
|
|
916
1215
|
...baseResult,
|
|
@@ -934,7 +1233,8 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
934
1233
|
paths: [relPath],
|
|
935
1234
|
includeContent: false,
|
|
936
1235
|
includeTranscripts,
|
|
937
|
-
readFile: options.readFile
|
|
1236
|
+
readFile: options.readFile,
|
|
1237
|
+
readFileDigest: options.readFileDigest
|
|
938
1238
|
});
|
|
939
1239
|
const currentFile = currentSnapshot.files[0];
|
|
940
1240
|
const uploadedState = {
|
|
@@ -945,53 +1245,24 @@ async function applyOfflineSyncFileContentChunk(options) {
|
|
|
945
1245
|
};
|
|
946
1246
|
try {
|
|
947
1247
|
if (currentFile?.sha256 === sha256) {
|
|
1248
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
948
1249
|
return {
|
|
949
1250
|
...baseResult,
|
|
950
1251
|
applied: false,
|
|
951
1252
|
skipped: true,
|
|
952
|
-
currentFile:
|
|
1253
|
+
currentFile: uploadedState
|
|
953
1254
|
};
|
|
954
1255
|
}
|
|
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
|
-
});
|
|
1256
|
+
const conflictResult = await currentFileConflict(currentFile ? toFileState(currentFile) : void 0);
|
|
1257
|
+
if (conflictResult) {
|
|
966
1258
|
return {
|
|
967
1259
|
...baseResult,
|
|
968
1260
|
applied: false,
|
|
969
1261
|
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) } : {}
|
|
1262
|
+
...conflictResult
|
|
992
1263
|
};
|
|
993
1264
|
}
|
|
994
|
-
await writeSafeFileFromUpload(root, relPath, upload, options.readFile, options.writeFileChunks);
|
|
1265
|
+
await writeSafeFileFromUpload(root, relPath, upload, options.readFile, options.writeFileChunks, mtimeMs);
|
|
995
1266
|
return {
|
|
996
1267
|
...baseResult,
|
|
997
1268
|
applied: true,
|
|
@@ -1137,11 +1408,12 @@ async function digestOfflineUploadStagingContent(options) {
|
|
|
1137
1408
|
}
|
|
1138
1409
|
return { sha256: hash.digest("hex"), bytes };
|
|
1139
1410
|
}
|
|
1140
|
-
async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFileChunks) {
|
|
1411
|
+
async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFileChunks, mtimeMs) {
|
|
1141
1412
|
const target = await resolveSafeArchiveTarget(root, relPath);
|
|
1142
1413
|
const chunks = readOfflineUploadStagingChunks({ root, upload, readFile: readFile2 });
|
|
1143
1414
|
if (writeFileChunks) {
|
|
1144
1415
|
await writeFileChunks({ root: root.abs, path: relPath, filePath: target, chunks });
|
|
1416
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1145
1417
|
return;
|
|
1146
1418
|
}
|
|
1147
1419
|
await mkdir(path.dirname(target), { recursive: true });
|
|
@@ -1163,6 +1435,7 @@ async function writeSafeFileFromUpload(root, relPath, upload, readFile2, writeFi
|
|
|
1163
1435
|
throw new Error(`offline sync target is a symlink: ${relPath}`);
|
|
1164
1436
|
}
|
|
1165
1437
|
await rename(tmp, target);
|
|
1438
|
+
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1166
1439
|
} catch (error) {
|
|
1167
1440
|
await handle.close().catch(() => {
|
|
1168
1441
|
});
|
|
@@ -1301,14 +1574,21 @@ export {
|
|
|
1301
1574
|
OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
|
|
1302
1575
|
OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES,
|
|
1303
1576
|
OFFLINE_SYNC_APPLY_MAX_BODY_BYTES,
|
|
1577
|
+
OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES,
|
|
1578
|
+
OFFLINE_SYNC_MAX_MTIME_MS,
|
|
1304
1579
|
normalizeOfflineSyncSnapshot,
|
|
1305
1580
|
normalizeOfflineSyncChangeset,
|
|
1581
|
+
shouldPreferIncomingOfflineRuntimeFile,
|
|
1582
|
+
iterateOfflineSyncSnapshotFileRecords,
|
|
1306
1583
|
buildOfflineSyncSnapshot,
|
|
1584
|
+
buildOfflineSyncSnapshotFromBase,
|
|
1307
1585
|
buildOfflineSyncSnapshotForPaths,
|
|
1308
1586
|
readOfflineSyncFileContentChunk,
|
|
1309
1587
|
buildOfflineSyncChangeset,
|
|
1588
|
+
buildOfflineSyncChangesetFromSnapshot,
|
|
1310
1589
|
summarizeOfflineSyncChangeset,
|
|
1311
1590
|
summarizeOfflineSyncPendingChanges,
|
|
1591
|
+
summarizeOfflineSyncPendingFiles,
|
|
1312
1592
|
applyOfflineSyncSnapshot,
|
|
1313
1593
|
applyOfflineSyncChangeset,
|
|
1314
1594
|
applyOfflineSyncFileContentChunk,
|
|
@@ -1319,4 +1599,4 @@ export {
|
|
|
1319
1599
|
normalizeOfflineSyncState,
|
|
1320
1600
|
fileStatesFromSnapshot
|
|
1321
1601
|
};
|
|
1322
|
-
//# sourceMappingURL=chunk-
|
|
1602
|
+
//# sourceMappingURL=chunk-U7EJOMFC.js.map
|