zen-fs-config 0.5.28 → 0.5.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/DESIGN.md +16 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +55 -13
- package/dist/index.mjs +55 -13
- package/dist/zen-fs-config.js +55 -13
- package/package.json +1 -1
package/DESIGN.md
CHANGED
|
@@ -1187,6 +1187,22 @@ Source file: /app-a/db.json (mtime=1700000000123)
|
|
|
1187
1187
|
→ Next sync: source.mtimeMs === target.mtimeMs → skip (no spurious copy)
|
|
1188
1188
|
```
|
|
1189
1189
|
|
|
1190
|
+
### Backend mtime implementation status
|
|
1191
|
+
|
|
1192
|
+
The `writeFileWithMtime` mechanism (above) only preserves mtime if the target
|
|
1193
|
+
backend actually persists it. Implementation status per backend:
|
|
1194
|
+
|
|
1195
|
+
| Backend | Protocol can set mtime? | Strategy | `write` path writes sidecar? | Status |
|
|
1196
|
+
|---|---|---|---|---|
|
|
1197
|
+
| RemoteStorage (`zen-fs-remotestoragejs`) | No — server `Last-Modified` is the PUT arrival time, client cannot control it | `.mtime` sidecar | **Yes** — `writeFile` writes the sidecar when `preciseMtime` is enabled (default on) | Working |
|
|
1198
|
+
| Gitee (`zen-fs-gitee`) | No — Git has no per-file mtime | `.mtime` sidecar | **Yes** — `write()` / `writeSync()` and `writeFile` / `writeFileSync` persist it; `createSnapshot()` reads it (write fix since 1.2.17, snapshot fix since 1.2.18) | **Fixed in 1.2.18** |
|
|
1199
|
+
|
|
1200
|
+
Consequence of the Gitee gap: files pushed through the normal write path have
|
|
1201
|
+
no `.mtime` sidecar, so `stat()` falls back to the commit time. That commit
|
|
1202
|
+
time never equals the source's real mtime, so the sync engine re-PUTs
|
|
1203
|
+
content-identical files every cycle (empty / "no-diff" commits on Gitee). Fix
|
|
1204
|
+
tracked in `zen-fs-gitee/DESIGN.md` §5.
|
|
1205
|
+
|
|
1190
1206
|
## 13. Peer Dependencies
|
|
1191
1207
|
|
|
1192
1208
|
| Package | Role | Version | Required |
|
package/dist/index.d.mts
CHANGED
|
@@ -170,6 +170,12 @@ interface AppDataBackendDescriptor {
|
|
|
170
170
|
/** Optional: reuse account fields from a config-sync backend. */
|
|
171
171
|
accountBackendId?: string;
|
|
172
172
|
description?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Optional creation timestamp (ms). Used when deduplicating backends that
|
|
175
|
+
* point to the same endpoint — the OLDEST one is kept. Backends without
|
|
176
|
+
* this field are treated as legacy/oldest (e.g. a fixed `*-primary` id).
|
|
177
|
+
*/
|
|
178
|
+
createdAt?: number;
|
|
173
179
|
}
|
|
174
180
|
/** Descriptor for a data-sync group referenced by a config-sync group. */
|
|
175
181
|
interface AppDataGroupDescriptor {
|
package/dist/index.d.ts
CHANGED
|
@@ -170,6 +170,12 @@ interface AppDataBackendDescriptor {
|
|
|
170
170
|
/** Optional: reuse account fields from a config-sync backend. */
|
|
171
171
|
accountBackendId?: string;
|
|
172
172
|
description?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Optional creation timestamp (ms). Used when deduplicating backends that
|
|
175
|
+
* point to the same endpoint — the OLDEST one is kept. Backends without
|
|
176
|
+
* this field are treated as legacy/oldest (e.g. a fixed `*-primary` id).
|
|
177
|
+
*/
|
|
178
|
+
createdAt?: number;
|
|
173
179
|
}
|
|
174
180
|
/** Descriptor for a data-sync group referenced by a config-sync group. */
|
|
175
181
|
interface AppDataGroupDescriptor {
|
package/dist/index.js
CHANGED
|
@@ -1041,6 +1041,31 @@ function sortKeysDeep(obj) {
|
|
|
1041
1041
|
function backendDedupKey(desc) {
|
|
1042
1042
|
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
1043
1043
|
}
|
|
1044
|
+
function backendAgeRank(b) {
|
|
1045
|
+
if (typeof b.createdAt === "number") return b.createdAt;
|
|
1046
|
+
const m = /-(\d{10,})$/.exec(b.id);
|
|
1047
|
+
if (m) return Number(m[1]);
|
|
1048
|
+
return -Infinity;
|
|
1049
|
+
}
|
|
1050
|
+
function dedupeAppDataBackends(backends) {
|
|
1051
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1052
|
+
const removed = [];
|
|
1053
|
+
for (const b of backends) {
|
|
1054
|
+
const key = backendDedupKey({ id: b.id, type: b.type, options: b.options });
|
|
1055
|
+
const existing = byKey.get(key);
|
|
1056
|
+
if (!existing) {
|
|
1057
|
+
byKey.set(key, b);
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (backendAgeRank(b) < backendAgeRank(existing)) {
|
|
1061
|
+
removed.push(existing);
|
|
1062
|
+
byKey.set(key, b);
|
|
1063
|
+
} else {
|
|
1064
|
+
removed.push(b);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return { kept: Array.from(byKey.values()), removed };
|
|
1068
|
+
}
|
|
1044
1069
|
var ConfigRepo = class {
|
|
1045
1070
|
appId;
|
|
1046
1071
|
nodeId;
|
|
@@ -1471,25 +1496,29 @@ var ConfigRepo = class {
|
|
|
1471
1496
|
async updateTombstoneConfirmations() {
|
|
1472
1497
|
const tombstones = await this.readTombstones();
|
|
1473
1498
|
if (tombstones.length === 0) return;
|
|
1474
|
-
|
|
1475
|
-
const allBackendIds = backendsMeta?.backends.map((b) => b.id) ?? [this.primaryBackendId];
|
|
1499
|
+
let modified = 0;
|
|
1476
1500
|
for (const tombstone of tombstones) {
|
|
1477
1501
|
const tombstonePath = `${DELETIONS_DIR}/${tombstoneFileName(tombstone.path)}`;
|
|
1502
|
+
let changed = false;
|
|
1478
1503
|
for (const replicaId of this.replicaBackends.keys()) {
|
|
1479
1504
|
if (!tombstone.confirmedBy.includes(replicaId)) {
|
|
1480
1505
|
tombstone.confirmedBy.push(replicaId);
|
|
1506
|
+
changed = true;
|
|
1481
1507
|
}
|
|
1482
1508
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1509
|
+
if (changed) {
|
|
1510
|
+
try {
|
|
1511
|
+
await this.cachedFS.writeFile(
|
|
1512
|
+
tombstonePath,
|
|
1513
|
+
new TextEncoder().encode(JSON.stringify(tombstone, null, 2))
|
|
1514
|
+
);
|
|
1515
|
+
modified++;
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1489
1518
|
}
|
|
1490
1519
|
}
|
|
1491
|
-
log2.log(`[ConfigRepo] updateTombstoneConfirmations: ${
|
|
1492
|
-
this.invalidateTombstoneCache();
|
|
1520
|
+
log2.log(`[ConfigRepo] updateTombstoneConfirmations: ${modified} tombstone(s) updated`);
|
|
1521
|
+
if (modified > 0) this.invalidateTombstoneCache();
|
|
1493
1522
|
}
|
|
1494
1523
|
/**
|
|
1495
1524
|
* GC: remove tombstones where all backends in backends.json have confirmed.
|
|
@@ -1502,8 +1531,7 @@ var ConfigRepo = class {
|
|
|
1502
1531
|
async gcTombstones() {
|
|
1503
1532
|
const tombstones = await this.readTombstones();
|
|
1504
1533
|
if (tombstones.length === 0) return;
|
|
1505
|
-
const
|
|
1506
|
-
const allBackendIds = backendsMeta?.backends.map((b) => b.id) ?? [this.primaryBackendId];
|
|
1534
|
+
const allBackendIds = [...this.replicaBackends.keys()];
|
|
1507
1535
|
for (const tombstone of tombstones) {
|
|
1508
1536
|
const allConfirmed = allBackendIds.every((id) => tombstone.confirmedBy.includes(id));
|
|
1509
1537
|
if (allConfirmed) {
|
|
@@ -2359,6 +2387,13 @@ var AppDataGroupImpl = class {
|
|
|
2359
2387
|
}
|
|
2360
2388
|
const localSyncable = backendToSyncableFS(this.localFS, `local(${this.groupId})`);
|
|
2361
2389
|
this.fs = createChrootFS(this.localFS, "/");
|
|
2390
|
+
const { kept, removed } = dedupeAppDataBackends(this._backends);
|
|
2391
|
+
if (removed.length > 0) {
|
|
2392
|
+
log2.warn(
|
|
2393
|
+
`[AppDataGroup:${this.groupId}] dedupe: removing ${removed.length} duplicate backend(s) (${removed.map((b) => b.id).join(", ")}) \u2014 keeping ${kept.map((b) => b.id).join(", ")}`
|
|
2394
|
+
);
|
|
2395
|
+
this._backends = kept;
|
|
2396
|
+
}
|
|
2362
2397
|
for (const desc of this._backends) {
|
|
2363
2398
|
try {
|
|
2364
2399
|
const instance = await createBackend({ type: desc.type, options: desc.options });
|
|
@@ -2385,6 +2420,13 @@ var AppDataGroupImpl = class {
|
|
|
2385
2420
|
log2.warn(`[AppDataGroup:${this.groupId}] Initial sync failed:`, err);
|
|
2386
2421
|
}
|
|
2387
2422
|
this.syncEngine.watchAll();
|
|
2423
|
+
if (removed.length > 0) {
|
|
2424
|
+
try {
|
|
2425
|
+
await this.parent.updateAppDataGroupDescriptor(this.groupId, this._backends);
|
|
2426
|
+
} catch (err) {
|
|
2427
|
+
log2.warn(`[AppDataGroup:${this.groupId}] dedupe: failed to persist deduplicated backends:`, err);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2388
2430
|
}
|
|
2389
2431
|
getSyncStatuses() {
|
|
2390
2432
|
return this.syncEngine.getStatusAll();
|
|
@@ -2412,7 +2454,7 @@ var AppDataGroupImpl = class {
|
|
|
2412
2454
|
},
|
|
2413
2455
|
"/"
|
|
2414
2456
|
);
|
|
2415
|
-
const desc = { id, type, options, description };
|
|
2457
|
+
const desc = { id, type, options, description, createdAt: Date.now() };
|
|
2416
2458
|
this.dataBackends.set(id, { instance, syncable, pairId: pair.pairId, desc });
|
|
2417
2459
|
log2.log(`[AppDataGroup:${this.groupId}] addBackend: ${id} (${type}) connected, pair=${pair.pairId}`);
|
|
2418
2460
|
try {
|
package/dist/index.mjs
CHANGED
|
@@ -643,6 +643,31 @@ function sortKeysDeep(obj) {
|
|
|
643
643
|
function backendDedupKey(desc) {
|
|
644
644
|
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
645
645
|
}
|
|
646
|
+
function backendAgeRank(b) {
|
|
647
|
+
if (typeof b.createdAt === "number") return b.createdAt;
|
|
648
|
+
const m = /-(\d{10,})$/.exec(b.id);
|
|
649
|
+
if (m) return Number(m[1]);
|
|
650
|
+
return -Infinity;
|
|
651
|
+
}
|
|
652
|
+
function dedupeAppDataBackends(backends) {
|
|
653
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
654
|
+
const removed = [];
|
|
655
|
+
for (const b of backends) {
|
|
656
|
+
const key = backendDedupKey({ id: b.id, type: b.type, options: b.options });
|
|
657
|
+
const existing = byKey.get(key);
|
|
658
|
+
if (!existing) {
|
|
659
|
+
byKey.set(key, b);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (backendAgeRank(b) < backendAgeRank(existing)) {
|
|
663
|
+
removed.push(existing);
|
|
664
|
+
byKey.set(key, b);
|
|
665
|
+
} else {
|
|
666
|
+
removed.push(b);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return { kept: Array.from(byKey.values()), removed };
|
|
670
|
+
}
|
|
646
671
|
var ConfigRepo = class {
|
|
647
672
|
appId;
|
|
648
673
|
nodeId;
|
|
@@ -1073,25 +1098,29 @@ var ConfigRepo = class {
|
|
|
1073
1098
|
async updateTombstoneConfirmations() {
|
|
1074
1099
|
const tombstones = await this.readTombstones();
|
|
1075
1100
|
if (tombstones.length === 0) return;
|
|
1076
|
-
|
|
1077
|
-
const allBackendIds = backendsMeta?.backends.map((b) => b.id) ?? [this.primaryBackendId];
|
|
1101
|
+
let modified = 0;
|
|
1078
1102
|
for (const tombstone of tombstones) {
|
|
1079
1103
|
const tombstonePath = `${DELETIONS_DIR}/${tombstoneFileName(tombstone.path)}`;
|
|
1104
|
+
let changed = false;
|
|
1080
1105
|
for (const replicaId of this.replicaBackends.keys()) {
|
|
1081
1106
|
if (!tombstone.confirmedBy.includes(replicaId)) {
|
|
1082
1107
|
tombstone.confirmedBy.push(replicaId);
|
|
1108
|
+
changed = true;
|
|
1083
1109
|
}
|
|
1084
1110
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1111
|
+
if (changed) {
|
|
1112
|
+
try {
|
|
1113
|
+
await this.cachedFS.writeFile(
|
|
1114
|
+
tombstonePath,
|
|
1115
|
+
new TextEncoder().encode(JSON.stringify(tombstone, null, 2))
|
|
1116
|
+
);
|
|
1117
|
+
modified++;
|
|
1118
|
+
} catch {
|
|
1119
|
+
}
|
|
1091
1120
|
}
|
|
1092
1121
|
}
|
|
1093
|
-
log2.log(`[ConfigRepo] updateTombstoneConfirmations: ${
|
|
1094
|
-
this.invalidateTombstoneCache();
|
|
1122
|
+
log2.log(`[ConfigRepo] updateTombstoneConfirmations: ${modified} tombstone(s) updated`);
|
|
1123
|
+
if (modified > 0) this.invalidateTombstoneCache();
|
|
1095
1124
|
}
|
|
1096
1125
|
/**
|
|
1097
1126
|
* GC: remove tombstones where all backends in backends.json have confirmed.
|
|
@@ -1104,8 +1133,7 @@ var ConfigRepo = class {
|
|
|
1104
1133
|
async gcTombstones() {
|
|
1105
1134
|
const tombstones = await this.readTombstones();
|
|
1106
1135
|
if (tombstones.length === 0) return;
|
|
1107
|
-
const
|
|
1108
|
-
const allBackendIds = backendsMeta?.backends.map((b) => b.id) ?? [this.primaryBackendId];
|
|
1136
|
+
const allBackendIds = [...this.replicaBackends.keys()];
|
|
1109
1137
|
for (const tombstone of tombstones) {
|
|
1110
1138
|
const allConfirmed = allBackendIds.every((id) => tombstone.confirmedBy.includes(id));
|
|
1111
1139
|
if (allConfirmed) {
|
|
@@ -1961,6 +1989,13 @@ var AppDataGroupImpl = class {
|
|
|
1961
1989
|
}
|
|
1962
1990
|
const localSyncable = backendToSyncableFS(this.localFS, `local(${this.groupId})`);
|
|
1963
1991
|
this.fs = createChrootFS(this.localFS, "/");
|
|
1992
|
+
const { kept, removed } = dedupeAppDataBackends(this._backends);
|
|
1993
|
+
if (removed.length > 0) {
|
|
1994
|
+
log2.warn(
|
|
1995
|
+
`[AppDataGroup:${this.groupId}] dedupe: removing ${removed.length} duplicate backend(s) (${removed.map((b) => b.id).join(", ")}) \u2014 keeping ${kept.map((b) => b.id).join(", ")}`
|
|
1996
|
+
);
|
|
1997
|
+
this._backends = kept;
|
|
1998
|
+
}
|
|
1964
1999
|
for (const desc of this._backends) {
|
|
1965
2000
|
try {
|
|
1966
2001
|
const instance = await createBackend({ type: desc.type, options: desc.options });
|
|
@@ -1987,6 +2022,13 @@ var AppDataGroupImpl = class {
|
|
|
1987
2022
|
log2.warn(`[AppDataGroup:${this.groupId}] Initial sync failed:`, err);
|
|
1988
2023
|
}
|
|
1989
2024
|
this.syncEngine.watchAll();
|
|
2025
|
+
if (removed.length > 0) {
|
|
2026
|
+
try {
|
|
2027
|
+
await this.parent.updateAppDataGroupDescriptor(this.groupId, this._backends);
|
|
2028
|
+
} catch (err) {
|
|
2029
|
+
log2.warn(`[AppDataGroup:${this.groupId}] dedupe: failed to persist deduplicated backends:`, err);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
1990
2032
|
}
|
|
1991
2033
|
getSyncStatuses() {
|
|
1992
2034
|
return this.syncEngine.getStatusAll();
|
|
@@ -2014,7 +2056,7 @@ var AppDataGroupImpl = class {
|
|
|
2014
2056
|
},
|
|
2015
2057
|
"/"
|
|
2016
2058
|
);
|
|
2017
|
-
const desc = { id, type, options, description };
|
|
2059
|
+
const desc = { id, type, options, description, createdAt: Date.now() };
|
|
2018
2060
|
this.dataBackends.set(id, { instance, syncable, pairId: pair.pairId, desc });
|
|
2019
2061
|
log2.log(`[AppDataGroup:${this.groupId}] addBackend: ${id} (${type}) connected, pair=${pair.pairId}`);
|
|
2020
2062
|
try {
|
package/dist/zen-fs-config.js
CHANGED
|
@@ -26918,6 +26918,31 @@ var ZenFSConfig = (() => {
|
|
|
26918
26918
|
function backendDedupKey(desc) {
|
|
26919
26919
|
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
26920
26920
|
}
|
|
26921
|
+
function backendAgeRank(b) {
|
|
26922
|
+
if (typeof b.createdAt === "number") return b.createdAt;
|
|
26923
|
+
const m = /-(\d{10,})$/.exec(b.id);
|
|
26924
|
+
if (m) return Number(m[1]);
|
|
26925
|
+
return -Infinity;
|
|
26926
|
+
}
|
|
26927
|
+
function dedupeAppDataBackends(backends) {
|
|
26928
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
26929
|
+
const removed = [];
|
|
26930
|
+
for (const b of backends) {
|
|
26931
|
+
const key = backendDedupKey({ id: b.id, type: b.type, options: b.options });
|
|
26932
|
+
const existing = byKey.get(key);
|
|
26933
|
+
if (!existing) {
|
|
26934
|
+
byKey.set(key, b);
|
|
26935
|
+
continue;
|
|
26936
|
+
}
|
|
26937
|
+
if (backendAgeRank(b) < backendAgeRank(existing)) {
|
|
26938
|
+
removed.push(existing);
|
|
26939
|
+
byKey.set(key, b);
|
|
26940
|
+
} else {
|
|
26941
|
+
removed.push(b);
|
|
26942
|
+
}
|
|
26943
|
+
}
|
|
26944
|
+
return { kept: Array.from(byKey.values()), removed };
|
|
26945
|
+
}
|
|
26921
26946
|
var ConfigRepo = class {
|
|
26922
26947
|
constructor(appId, nodeId, primaryBackendId, cachedFS, serializer, onConflict, pollIntervalMs, cacheOptions) {
|
|
26923
26948
|
__publicField(this, "appId");
|
|
@@ -27348,25 +27373,29 @@ var ZenFSConfig = (() => {
|
|
|
27348
27373
|
async updateTombstoneConfirmations() {
|
|
27349
27374
|
const tombstones = await this.readTombstones();
|
|
27350
27375
|
if (tombstones.length === 0) return;
|
|
27351
|
-
|
|
27352
|
-
const allBackendIds = backendsMeta?.backends.map((b) => b.id) ?? [this.primaryBackendId];
|
|
27376
|
+
let modified = 0;
|
|
27353
27377
|
for (const tombstone of tombstones) {
|
|
27354
27378
|
const tombstonePath = `${DELETIONS_DIR}/${tombstoneFileName(tombstone.path)}`;
|
|
27379
|
+
let changed = false;
|
|
27355
27380
|
for (const replicaId of this.replicaBackends.keys()) {
|
|
27356
27381
|
if (!tombstone.confirmedBy.includes(replicaId)) {
|
|
27357
27382
|
tombstone.confirmedBy.push(replicaId);
|
|
27383
|
+
changed = true;
|
|
27358
27384
|
}
|
|
27359
27385
|
}
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27386
|
+
if (changed) {
|
|
27387
|
+
try {
|
|
27388
|
+
await this.cachedFS.writeFile(
|
|
27389
|
+
tombstonePath,
|
|
27390
|
+
new TextEncoder().encode(JSON.stringify(tombstone, null, 2))
|
|
27391
|
+
);
|
|
27392
|
+
modified++;
|
|
27393
|
+
} catch {
|
|
27394
|
+
}
|
|
27366
27395
|
}
|
|
27367
27396
|
}
|
|
27368
|
-
log6.log(`[ConfigRepo] updateTombstoneConfirmations: ${
|
|
27369
|
-
this.invalidateTombstoneCache();
|
|
27397
|
+
log6.log(`[ConfigRepo] updateTombstoneConfirmations: ${modified} tombstone(s) updated`);
|
|
27398
|
+
if (modified > 0) this.invalidateTombstoneCache();
|
|
27370
27399
|
}
|
|
27371
27400
|
/**
|
|
27372
27401
|
* GC: remove tombstones where all backends in backends.json have confirmed.
|
|
@@ -27379,8 +27408,7 @@ var ZenFSConfig = (() => {
|
|
|
27379
27408
|
async gcTombstones() {
|
|
27380
27409
|
const tombstones = await this.readTombstones();
|
|
27381
27410
|
if (tombstones.length === 0) return;
|
|
27382
|
-
const
|
|
27383
|
-
const allBackendIds = backendsMeta?.backends.map((b) => b.id) ?? [this.primaryBackendId];
|
|
27411
|
+
const allBackendIds = [...this.replicaBackends.keys()];
|
|
27384
27412
|
for (const tombstone of tombstones) {
|
|
27385
27413
|
const allConfirmed = allBackendIds.every((id) => tombstone.confirmedBy.includes(id));
|
|
27386
27414
|
if (allConfirmed) {
|
|
@@ -28236,6 +28264,13 @@ var ZenFSConfig = (() => {
|
|
|
28236
28264
|
}
|
|
28237
28265
|
const localSyncable = backendToSyncableFS(this.localFS, `local(${this.groupId})`);
|
|
28238
28266
|
this.fs = createChrootFS(this.localFS, "/");
|
|
28267
|
+
const { kept, removed } = dedupeAppDataBackends(this._backends);
|
|
28268
|
+
if (removed.length > 0) {
|
|
28269
|
+
log6.warn(
|
|
28270
|
+
`[AppDataGroup:${this.groupId}] dedupe: removing ${removed.length} duplicate backend(s) (${removed.map((b) => b.id).join(", ")}) \u2014 keeping ${kept.map((b) => b.id).join(", ")}`
|
|
28271
|
+
);
|
|
28272
|
+
this._backends = kept;
|
|
28273
|
+
}
|
|
28239
28274
|
for (const desc of this._backends) {
|
|
28240
28275
|
try {
|
|
28241
28276
|
const instance = await createBackend({ type: desc.type, options: desc.options });
|
|
@@ -28262,6 +28297,13 @@ var ZenFSConfig = (() => {
|
|
|
28262
28297
|
log6.warn(`[AppDataGroup:${this.groupId}] Initial sync failed:`, err2);
|
|
28263
28298
|
}
|
|
28264
28299
|
this.syncEngine.watchAll();
|
|
28300
|
+
if (removed.length > 0) {
|
|
28301
|
+
try {
|
|
28302
|
+
await this.parent.updateAppDataGroupDescriptor(this.groupId, this._backends);
|
|
28303
|
+
} catch (err2) {
|
|
28304
|
+
log6.warn(`[AppDataGroup:${this.groupId}] dedupe: failed to persist deduplicated backends:`, err2);
|
|
28305
|
+
}
|
|
28306
|
+
}
|
|
28265
28307
|
}
|
|
28266
28308
|
getSyncStatuses() {
|
|
28267
28309
|
return this.syncEngine.getStatusAll();
|
|
@@ -28289,7 +28331,7 @@ var ZenFSConfig = (() => {
|
|
|
28289
28331
|
},
|
|
28290
28332
|
"/"
|
|
28291
28333
|
);
|
|
28292
|
-
const desc = { id, type, options, description };
|
|
28334
|
+
const desc = { id, type, options, description, createdAt: Date.now() };
|
|
28293
28335
|
this.dataBackends.set(id, { instance, syncable, pairId: pair.pairId, desc });
|
|
28294
28336
|
log6.log(`[AppDataGroup:${this.groupId}] addBackend: ${id} (${type}) connected, pair=${pair.pairId}`);
|
|
28295
28337
|
try {
|