zen-fs-config 0.5.18 → 0.5.20
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/index.js +106 -118
- package/dist/index.mjs +106 -118
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -485,6 +485,9 @@ registerBackend("IndexedDB", async (options) => {
|
|
|
485
485
|
defaultOptions: { storeName: "" }
|
|
486
486
|
});
|
|
487
487
|
|
|
488
|
+
// src/config-repo.ts
|
|
489
|
+
var import_localstorage_logger = require("@richard432/localstorage-logger");
|
|
490
|
+
|
|
488
491
|
// src/version.ts
|
|
489
492
|
function versionPathFor(configFilePath) {
|
|
490
493
|
const lastSlash = configFilePath.lastIndexOf("/");
|
|
@@ -573,6 +576,7 @@ async function verifyOrRepairVersion(fs, configFilePath, author) {
|
|
|
573
576
|
}
|
|
574
577
|
|
|
575
578
|
// src/config-repo.ts
|
|
579
|
+
var log = (0, import_localstorage_logger.createLogger)("zen-fs-config:config-repo");
|
|
576
580
|
var META_DIR = "/.meta";
|
|
577
581
|
var GROUP_TYPE_FILE = `${META_DIR}/group-type`;
|
|
578
582
|
var BACKENDS_FILE = `${META_DIR}/backends.json`;
|
|
@@ -688,7 +692,7 @@ var ConfigRepo = class {
|
|
|
688
692
|
const bytes = this.serializer.serialize(data, fullPath);
|
|
689
693
|
this.configCache.set(fullPath, data);
|
|
690
694
|
this.persistConfig(fullPath, bytes).catch((err) => {
|
|
691
|
-
|
|
695
|
+
log.error(`[zen-fs-config] Failed to persist ${fullPath}:`, err);
|
|
692
696
|
});
|
|
693
697
|
}
|
|
694
698
|
// -----------------------------------------------------------------------
|
|
@@ -834,7 +838,7 @@ var ConfigRepo = class {
|
|
|
834
838
|
} catch {
|
|
835
839
|
}
|
|
836
840
|
}
|
|
837
|
-
|
|
841
|
+
log.log(`[ConfigRepo] deleteFile: ${normalizedPath} (tombstone at ${tombstonePath})`);
|
|
838
842
|
this.invalidateTombstoneCache();
|
|
839
843
|
this.schedulePostDeleteSync();
|
|
840
844
|
}
|
|
@@ -852,7 +856,7 @@ var ConfigRepo = class {
|
|
|
852
856
|
this.postDeleteSyncTimer = void 0;
|
|
853
857
|
if (this.disposed) return;
|
|
854
858
|
this.syncEngine.syncAll().catch((err) => {
|
|
855
|
-
|
|
859
|
+
log.warn("[ConfigRepo] post-delete sync failed:", err);
|
|
856
860
|
});
|
|
857
861
|
}, 500);
|
|
858
862
|
}
|
|
@@ -904,11 +908,11 @@ var ConfigRepo = class {
|
|
|
904
908
|
let alreadyDeleted = 0;
|
|
905
909
|
for (const tombstone of tombstones) {
|
|
906
910
|
const tVersionPath = versionPathFor(tombstone.path);
|
|
907
|
-
|
|
911
|
+
log.log(`[TOMB-TRACE] processing tombstone: path=${tombstone.path} versionPath=${tVersionPath ?? "null"}`);
|
|
908
912
|
const existedOnPrimary = await this.safeExists(this.cachedFS, tombstone.path);
|
|
909
|
-
|
|
913
|
+
log.log(`[TOMB-TRACE] primary safeExists(${tombstone.path}) \u2192 ${existedOnPrimary}`);
|
|
910
914
|
if (existedOnPrimary) {
|
|
911
|
-
|
|
915
|
+
log.log(`[TOMB-TRACE] tombstone check: ${tombstone.path} on primary \u2192 EXISTS`);
|
|
912
916
|
}
|
|
913
917
|
if (existedOnPrimary) {
|
|
914
918
|
try {
|
|
@@ -919,7 +923,7 @@ var ConfigRepo = class {
|
|
|
919
923
|
}
|
|
920
924
|
if (tVersionPath) {
|
|
921
925
|
const vExistedOnPrimary = await this.safeExists(this.cachedFS, tVersionPath);
|
|
922
|
-
|
|
926
|
+
log.log(`[TOMB-TRACE] primary safeExists(${tVersionPath}) \u2192 ${vExistedOnPrimary}`);
|
|
923
927
|
if (vExistedOnPrimary) {
|
|
924
928
|
try {
|
|
925
929
|
await this.cachedFS.unlink(tVersionPath);
|
|
@@ -928,40 +932,40 @@ var ConfigRepo = class {
|
|
|
928
932
|
}
|
|
929
933
|
}
|
|
930
934
|
for (const [replicaId, replica] of this.replicaBackends) {
|
|
931
|
-
|
|
935
|
+
log.log(`[TOMB-TRACE] checking replica: ${replicaId} for ${tombstone.path}`);
|
|
932
936
|
const existed = await this.safeExists(replica.instance, tombstone.path);
|
|
933
|
-
|
|
937
|
+
log.log(`[TOMB-TRACE] replica ${replicaId} safeExists(${tombstone.path}) \u2192 ${existed}`);
|
|
934
938
|
if (existed) {
|
|
935
|
-
|
|
939
|
+
log.log(`[TOMB-TRACE] tombstone check: ${tombstone.path} on ${replicaId} \u2192 EXISTS`);
|
|
936
940
|
try {
|
|
937
|
-
|
|
941
|
+
log.log(`[TOMB-TRACE] calling unlink(${tombstone.path}) on ${replicaId}`);
|
|
938
942
|
await replica.instance.unlink(tombstone.path);
|
|
939
|
-
|
|
943
|
+
log.log(`[TOMB-TRACE] tombstone ${tombstone.path}: deleted on ${replicaId}`);
|
|
940
944
|
processed++;
|
|
941
945
|
} catch (err) {
|
|
942
|
-
|
|
946
|
+
log.log(`[TOMB-TRACE] unlink(${tombstone.path}) on ${replicaId} FAILED: ${err}`);
|
|
943
947
|
alreadyDeleted++;
|
|
944
948
|
}
|
|
945
949
|
} else {
|
|
946
|
-
|
|
950
|
+
log.log(`[TOMB-TRACE] replica ${replicaId}: already gone, skipping unlink`);
|
|
947
951
|
alreadyDeleted++;
|
|
948
952
|
}
|
|
949
953
|
if (tVersionPath) {
|
|
950
954
|
const vExisted = await this.safeExists(replica.instance, tVersionPath);
|
|
951
|
-
|
|
955
|
+
log.log(`[TOMB-TRACE] replica ${replicaId} safeExists(${tVersionPath}) \u2192 ${vExisted}`);
|
|
952
956
|
if (vExisted) {
|
|
953
957
|
try {
|
|
954
|
-
|
|
958
|
+
log.log(`[TOMB-TRACE] calling unlink(${tVersionPath}) on ${replicaId}`);
|
|
955
959
|
await replica.instance.unlink(tVersionPath);
|
|
956
960
|
} catch (err) {
|
|
957
|
-
|
|
961
|
+
log.log(`[TOMB-TRACE] unlink(${tVersionPath}) on ${replicaId} FAILED: ${err}`);
|
|
958
962
|
}
|
|
959
963
|
}
|
|
960
964
|
}
|
|
961
965
|
}
|
|
962
966
|
}
|
|
963
967
|
if (processed > 0 || alreadyDeleted > 0) {
|
|
964
|
-
|
|
968
|
+
log.log(`[TOMB-TRACE] processTombstones: ${tombstones.length} tombstone(s), ${processed} deleted, ${alreadyDeleted} already gone`);
|
|
965
969
|
}
|
|
966
970
|
}
|
|
967
971
|
/**
|
|
@@ -977,21 +981,21 @@ var ConfigRepo = class {
|
|
|
977
981
|
const innerBackendName = fs?.inner?.backendName;
|
|
978
982
|
const directBackendName = fs?.backendName;
|
|
979
983
|
const backendLabel = innerBackendName ?? directBackendName ?? innerBackend ?? ctorName;
|
|
980
|
-
|
|
984
|
+
log.log(`[TOMB-TRACE] safeExists(${path}): fs type=${ctorName} backend=${backendLabel} hasExists=${hasExists} hasStat=${hasStat} inner=${innerBackend ?? "N/A"}`);
|
|
981
985
|
if (hasExists) {
|
|
982
|
-
|
|
986
|
+
log.log(`[TOMB-TRACE] safeExists(${path}): \u2192 calling fs.exists() [backend=${backendLabel}]`);
|
|
983
987
|
const result = await fs.exists(path);
|
|
984
|
-
|
|
988
|
+
log.log(`[TOMB-TRACE] safeExists(${path}): \u2190 fs.exists() [backend=${backendLabel}] \u2192 ${result}`);
|
|
985
989
|
return result;
|
|
986
990
|
}
|
|
987
|
-
|
|
991
|
+
log.log(`[TOMB-TRACE] safeExists(${path}): no exists(), calling fs.stat() [backend=${backendLabel}]`);
|
|
988
992
|
await fs.stat(path);
|
|
989
|
-
|
|
993
|
+
log.log(`[TOMB-TRACE] safeExists(${path}): \u2190 fs.stat() [backend=${backendLabel}] \u2192 OK (exists)`);
|
|
990
994
|
return true;
|
|
991
995
|
} catch (err) {
|
|
992
996
|
const errCode = err?.code ?? err?.status ?? "";
|
|
993
997
|
const errName = err?.constructor?.name ?? "";
|
|
994
|
-
|
|
998
|
+
log.log(`[TOMB-TRACE] safeExists(${path}): \u2190 ERROR ${errName} ${errCode} ${err?.message ?? err}`);
|
|
995
999
|
return false;
|
|
996
1000
|
}
|
|
997
1001
|
}
|
|
@@ -1018,9 +1022,9 @@ var ConfigRepo = class {
|
|
|
1018
1022
|
*/
|
|
1019
1023
|
startBackgroundSync() {
|
|
1020
1024
|
this.initialSyncPromise = this.initialSyncAndDedup().then(() => {
|
|
1021
|
-
|
|
1025
|
+
log.log("[ConfigRepo] Background initial sync complete");
|
|
1022
1026
|
}).catch((err) => {
|
|
1023
|
-
|
|
1027
|
+
log.error("[ConfigRepo] Background initial sync failed:", err);
|
|
1024
1028
|
}).finally(() => {
|
|
1025
1029
|
this.initialSyncPromise = null;
|
|
1026
1030
|
});
|
|
@@ -1048,7 +1052,7 @@ var ConfigRepo = class {
|
|
|
1048
1052
|
} catch {
|
|
1049
1053
|
}
|
|
1050
1054
|
}
|
|
1051
|
-
|
|
1055
|
+
log.log(`[ConfigRepo] updateTombstoneConfirmations: ${tombstones.length} tombstone(s) updated`);
|
|
1052
1056
|
this.invalidateTombstoneCache();
|
|
1053
1057
|
}
|
|
1054
1058
|
/**
|
|
@@ -1080,7 +1084,7 @@ var ConfigRepo = class {
|
|
|
1080
1084
|
}
|
|
1081
1085
|
}
|
|
1082
1086
|
}
|
|
1083
|
-
|
|
1087
|
+
log.log(`[ConfigRepo] gcTombstones: removed ${tombstonePath} (all ${allBackendIds.length} backends confirmed)`);
|
|
1084
1088
|
}
|
|
1085
1089
|
}
|
|
1086
1090
|
}
|
|
@@ -1096,7 +1100,7 @@ var ConfigRepo = class {
|
|
|
1096
1100
|
this.assertNotDisposed();
|
|
1097
1101
|
const results = await this.flush();
|
|
1098
1102
|
for (const result of results) {
|
|
1099
|
-
|
|
1103
|
+
log.log(
|
|
1100
1104
|
`[ConfigRepo] syncMetaToReplicas: ${result.pairId} +${result.filesCreated}/~${result.filesUpdated}/-${result.filesDeleted} skip:${result.filesSkipped} ${result.durationMs}ms`
|
|
1101
1105
|
);
|
|
1102
1106
|
}
|
|
@@ -1198,21 +1202,21 @@ var ConfigRepo = class {
|
|
|
1198
1202
|
// Internal — Setup
|
|
1199
1203
|
// -----------------------------------------------------------------------
|
|
1200
1204
|
async setupSync(backends, primaryBackendId, pollIntervalMs) {
|
|
1201
|
-
|
|
1205
|
+
log.log(`[ConfigRepo] setupSync: ${backends.length} backends, primary=${primaryBackendId} pollInterval=${pollIntervalMs ?? "default"}ms`);
|
|
1202
1206
|
for (const desc of backends) {
|
|
1203
1207
|
if (desc.id === primaryBackendId) continue;
|
|
1204
1208
|
if (desc.enabled === false) {
|
|
1205
|
-
|
|
1209
|
+
log.log(`[ConfigRepo] Skipping disabled replica: ${desc.id}`);
|
|
1206
1210
|
continue;
|
|
1207
1211
|
}
|
|
1208
|
-
|
|
1212
|
+
log.log(`[ConfigRepo] Creating replica backend: id=${desc.id}, type=${desc.type}`);
|
|
1209
1213
|
try {
|
|
1210
1214
|
const instance = await createBackend(desc);
|
|
1211
1215
|
let fsInstance = instance;
|
|
1212
1216
|
if (this.cacheOptions) {
|
|
1213
1217
|
const { wrapWithCache: wrapWithCache2 } = await Promise.resolve().then(() => (init_cache_wrapper(), cache_wrapper_exports));
|
|
1214
1218
|
fsInstance = wrapWithCache2(instance, desc.id, this.cacheOptions);
|
|
1215
|
-
|
|
1219
|
+
log.log(`[ConfigRepo] Replica ${desc.id} wrapped with CachedFileSystem (store=${this.cacheOptions.storeType ?? "IdbCacheStore"})`);
|
|
1216
1220
|
}
|
|
1217
1221
|
const syncable = backendToSyncableFS(fsInstance, `${desc.type}(${desc.id})`);
|
|
1218
1222
|
const pair = this.syncEngine.addPair(
|
|
@@ -1227,7 +1231,7 @@ var ConfigRepo = class {
|
|
|
1227
1231
|
this.invalidateTombstoneCache();
|
|
1228
1232
|
await this.processTombstones();
|
|
1229
1233
|
} catch (err) {
|
|
1230
|
-
|
|
1234
|
+
log.warn("[ConfigRepo] preSyncHook processTombstones failed:", err);
|
|
1231
1235
|
}
|
|
1232
1236
|
},
|
|
1233
1237
|
postSyncHook: async () => {
|
|
@@ -1236,7 +1240,7 @@ var ConfigRepo = class {
|
|
|
1236
1240
|
await this.processTombstones();
|
|
1237
1241
|
await this.updateTombstoneConfirmations();
|
|
1238
1242
|
} catch (err) {
|
|
1239
|
-
|
|
1243
|
+
log.warn("[ConfigRepo] postSyncHook tombstone processing failed:", err);
|
|
1240
1244
|
}
|
|
1241
1245
|
}
|
|
1242
1246
|
},
|
|
@@ -1247,13 +1251,13 @@ var ConfigRepo = class {
|
|
|
1247
1251
|
this.handleConflict(event);
|
|
1248
1252
|
};
|
|
1249
1253
|
this.syncEngine.on(pair.pairId, "conflict", conflictHandler);
|
|
1250
|
-
|
|
1254
|
+
log.log(`[ConfigRepo] Replica ${desc.id} created, sync pair=${pair.pairId}`);
|
|
1251
1255
|
} catch (err) {
|
|
1252
|
-
|
|
1256
|
+
log.error(`[ConfigRepo] Failed to create replica ${desc.id} (${desc.type}):`, err);
|
|
1253
1257
|
}
|
|
1254
1258
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1259
|
+
log.log(`[ConfigRepo] setupSync complete. Replicas:`, Array.from(this.replicaBackends.keys()));
|
|
1260
|
+
log.log(`[ConfigRepo] Sync statuses:`, this.getSyncStatuses());
|
|
1257
1261
|
}
|
|
1258
1262
|
// -----------------------------------------------------------------------
|
|
1259
1263
|
// Internal — Persistence
|
|
@@ -1365,7 +1369,7 @@ var ConfigRepo = class {
|
|
|
1365
1369
|
await this.resolveConflict(`${conflictId}/meta.json`, customMerge);
|
|
1366
1370
|
}
|
|
1367
1371
|
} catch (err) {
|
|
1368
|
-
|
|
1372
|
+
log.error("[zen-fs-config] Conflict handler error:", err);
|
|
1369
1373
|
}
|
|
1370
1374
|
}
|
|
1371
1375
|
}
|
|
@@ -1464,11 +1468,11 @@ var ConfigRepo = class {
|
|
|
1464
1468
|
}
|
|
1465
1469
|
return { kind: "ok", desc, mtime };
|
|
1466
1470
|
} else {
|
|
1467
|
-
|
|
1471
|
+
log.warn(`[ConfigRepo] Backend descriptor ${entry} is missing id/type fields, marking for cleanup`);
|
|
1468
1472
|
return { kind: "corrupt", filePath };
|
|
1469
1473
|
}
|
|
1470
1474
|
} catch (parseErr) {
|
|
1471
|
-
|
|
1475
|
+
log.warn(`[ConfigRepo] Backend descriptor ${entry} has corrupted JSON: ${parseErr}. Marking for cleanup.`);
|
|
1472
1476
|
return { kind: "corrupt", filePath };
|
|
1473
1477
|
}
|
|
1474
1478
|
})
|
|
@@ -1517,7 +1521,7 @@ var ConfigRepo = class {
|
|
|
1517
1521
|
}
|
|
1518
1522
|
}
|
|
1519
1523
|
if (duplicates.length > 0) {
|
|
1520
|
-
|
|
1524
|
+
log.log(
|
|
1521
1525
|
`[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
|
|
1522
1526
|
);
|
|
1523
1527
|
for (const dupId of duplicates) {
|
|
@@ -1620,7 +1624,7 @@ var ConfigRepo = class {
|
|
|
1620
1624
|
`Backend "${id}" has the same configuration as existing backend "${dup.id}" (type=${type}). Use removeBackend("${dup.id}") first, or connect with the existing backend's ID.`
|
|
1621
1625
|
);
|
|
1622
1626
|
}
|
|
1623
|
-
|
|
1627
|
+
log.log(`[ConfigRepo] addBackend: creating ${id} (${type})...`);
|
|
1624
1628
|
const instance = await createBackend({ type, options });
|
|
1625
1629
|
const syncable = backendToSyncableFS(instance, `${type}(${id})`);
|
|
1626
1630
|
const desc = { id, type, options, description };
|
|
@@ -1636,7 +1640,7 @@ var ConfigRepo = class {
|
|
|
1636
1640
|
this.invalidateTombstoneCache();
|
|
1637
1641
|
await this.processTombstones();
|
|
1638
1642
|
} catch (err) {
|
|
1639
|
-
|
|
1643
|
+
log.warn("[ConfigRepo] preSyncHook processTombstones failed:", err);
|
|
1640
1644
|
}
|
|
1641
1645
|
},
|
|
1642
1646
|
postSyncHook: async () => {
|
|
@@ -1645,7 +1649,7 @@ var ConfigRepo = class {
|
|
|
1645
1649
|
await this.processTombstones();
|
|
1646
1650
|
await this.updateTombstoneConfirmations();
|
|
1647
1651
|
} catch (err) {
|
|
1648
|
-
|
|
1652
|
+
log.warn("[ConfigRepo] postSyncHook tombstone processing failed:", err);
|
|
1649
1653
|
}
|
|
1650
1654
|
}
|
|
1651
1655
|
},
|
|
@@ -1656,7 +1660,7 @@ var ConfigRepo = class {
|
|
|
1656
1660
|
this.handleConflict(event);
|
|
1657
1661
|
};
|
|
1658
1662
|
this.syncEngine.on(pair.pairId, "conflict", conflictHandler);
|
|
1659
|
-
|
|
1663
|
+
log.log(`[ConfigRepo] addBackend: ${id} (${type}) added, sync pair=${pair.pairId}`);
|
|
1660
1664
|
await this.syncMetaToReplicas();
|
|
1661
1665
|
this.syncEngine.watch(pair.pairId);
|
|
1662
1666
|
}
|
|
@@ -1679,13 +1683,13 @@ var ConfigRepo = class {
|
|
|
1679
1683
|
await this.removeBackendDescriptor(id);
|
|
1680
1684
|
}
|
|
1681
1685
|
this.syncEngine.removePair(replica.pairId);
|
|
1682
|
-
|
|
1686
|
+
log.log(`[ConfigRepo] removeBackend: sync pair ${replica.pairId} removed`);
|
|
1683
1687
|
this.replicaBackends.delete(id);
|
|
1684
1688
|
if (replica.instance?.dispose) {
|
|
1685
1689
|
await replica.instance.dispose();
|
|
1686
1690
|
}
|
|
1687
1691
|
} else {
|
|
1688
|
-
|
|
1692
|
+
log.log(`[ConfigRepo] removeBackend: "${id}" not in replicaBackends, cleaning up descriptor only`);
|
|
1689
1693
|
try {
|
|
1690
1694
|
await this.deleteFile(descPath);
|
|
1691
1695
|
} catch {
|
|
@@ -1693,7 +1697,7 @@ var ConfigRepo = class {
|
|
|
1693
1697
|
}
|
|
1694
1698
|
}
|
|
1695
1699
|
this.schedulePostDeleteSync();
|
|
1696
|
-
|
|
1700
|
+
log.log(`[ConfigRepo] removeBackend: ${id} removed (tombstone written, remote cleaned)`);
|
|
1697
1701
|
}
|
|
1698
1702
|
// -----------------------------------------------------------------------
|
|
1699
1703
|
// IConfigRepo — Group Type
|
|
@@ -1705,14 +1709,14 @@ var ConfigRepo = class {
|
|
|
1705
1709
|
const existing = await this.cachedFS.readFile(GROUP_TYPE_FILE, "utf-8");
|
|
1706
1710
|
const current = existing.trim();
|
|
1707
1711
|
if (current && current !== type) {
|
|
1708
|
-
|
|
1712
|
+
log.warn(`[ConfigRepo] group-type already set to "${current}", ignoring request to set "${type}"`);
|
|
1709
1713
|
return;
|
|
1710
1714
|
}
|
|
1711
1715
|
} catch {
|
|
1712
1716
|
}
|
|
1713
1717
|
await this.ensureDir(GROUP_TYPE_FILE);
|
|
1714
1718
|
await this.cachedFS.writeFile(GROUP_TYPE_FILE, new TextEncoder().encode(type));
|
|
1715
|
-
|
|
1719
|
+
log.log(`[ConfigRepo] group-type set to "${type}"`);
|
|
1716
1720
|
}
|
|
1717
1721
|
/** Read the group-type marker. Returns null if not set. */
|
|
1718
1722
|
async getGroupType() {
|
|
@@ -1753,7 +1757,7 @@ var ConfigRepo = class {
|
|
|
1753
1757
|
if (this.appDataGroups.has(id)) {
|
|
1754
1758
|
throw new Error(`App data group "${id}" already exists. Use removeAppDataGroup() first.`);
|
|
1755
1759
|
}
|
|
1756
|
-
|
|
1760
|
+
log.log(`[ConfigRepo] createAppDataGroup: creating "${id}" with ${backends.length} backend(s)`);
|
|
1757
1761
|
const resolvedBackends = [];
|
|
1758
1762
|
for (const desc of backends) {
|
|
1759
1763
|
const mergedOptions = await this.resolveAppDataBackendOptions(desc);
|
|
@@ -1779,7 +1783,7 @@ var ConfigRepo = class {
|
|
|
1779
1783
|
const version = await incrementVersion(this.fullFS, descPath, bytes, author);
|
|
1780
1784
|
await this.writeVersionSidecar(descPath, version);
|
|
1781
1785
|
this.appDataGroups.set(id, group);
|
|
1782
|
-
|
|
1786
|
+
log.log(`[ConfigRepo] createAppDataGroup: "${id}" created`);
|
|
1783
1787
|
return group;
|
|
1784
1788
|
}
|
|
1785
1789
|
async getAppDataGroup(id) {
|
|
@@ -1838,7 +1842,7 @@ var ConfigRepo = class {
|
|
|
1838
1842
|
} catch {
|
|
1839
1843
|
}
|
|
1840
1844
|
await this.unlinkVersionSidecar(this.cachedFS, descPath);
|
|
1841
|
-
|
|
1845
|
+
log.log(`[ConfigRepo] removeAppDataGroup: "${id}" removed`);
|
|
1842
1846
|
}
|
|
1843
1847
|
async listAccountBackends() {
|
|
1844
1848
|
this.assertNotDisposed();
|
|
@@ -1903,15 +1907,15 @@ var AppDataGroupImpl = class {
|
|
|
1903
1907
|
"/"
|
|
1904
1908
|
);
|
|
1905
1909
|
this.dataBackends.set(desc.id, { instance, syncable, pairId: pair.pairId, desc });
|
|
1906
|
-
|
|
1910
|
+
log.log(`[AppDataGroup:${this.groupId}] backend ${desc.id} (${desc.type}) connected, pair=${pair.pairId}`);
|
|
1907
1911
|
} catch (err) {
|
|
1908
|
-
|
|
1912
|
+
log.error(`[AppDataGroup:${this.groupId}] Failed to create backend ${desc.id} (${desc.type}):`, err);
|
|
1909
1913
|
}
|
|
1910
1914
|
}
|
|
1911
1915
|
try {
|
|
1912
1916
|
await this.syncEngine.syncAll();
|
|
1913
1917
|
} catch (err) {
|
|
1914
|
-
|
|
1918
|
+
log.warn(`[AppDataGroup:${this.groupId}] Initial sync failed:`, err);
|
|
1915
1919
|
}
|
|
1916
1920
|
this.syncEngine.watchAll();
|
|
1917
1921
|
}
|
|
@@ -1927,7 +1931,7 @@ var AppDataGroupImpl = class {
|
|
|
1927
1931
|
if (this.dataBackends.has(id)) {
|
|
1928
1932
|
throw new Error(`Backend "${id}" already exists in data group "${this.groupId}"`);
|
|
1929
1933
|
}
|
|
1930
|
-
|
|
1934
|
+
log.log(`[AppDataGroup:${this.groupId}] addBackend: creating ${id} (${type})...`);
|
|
1931
1935
|
const instance = await createBackend({ type, options });
|
|
1932
1936
|
const syncable = backendToSyncableFS(instance, `${type}(${id})`);
|
|
1933
1937
|
const localSyncable = backendToSyncableFS(this.localFS, `local(${this.groupId})`);
|
|
@@ -1943,11 +1947,11 @@ var AppDataGroupImpl = class {
|
|
|
1943
1947
|
);
|
|
1944
1948
|
const desc = { id, type, options, description };
|
|
1945
1949
|
this.dataBackends.set(id, { instance, syncable, pairId: pair.pairId, desc });
|
|
1946
|
-
|
|
1950
|
+
log.log(`[AppDataGroup:${this.groupId}] addBackend: ${id} (${type}) connected, pair=${pair.pairId}`);
|
|
1947
1951
|
try {
|
|
1948
1952
|
await this.syncEngine.sync(pair.pairId);
|
|
1949
1953
|
} catch (err) {
|
|
1950
|
-
|
|
1954
|
+
log.warn(`[AppDataGroup:${this.groupId}] addBackend: initial sync failed for ${id}:`, err);
|
|
1951
1955
|
}
|
|
1952
1956
|
this.syncEngine.watch(pair.pairId);
|
|
1953
1957
|
}
|
|
@@ -1962,7 +1966,7 @@ var AppDataGroupImpl = class {
|
|
|
1962
1966
|
if (backend.instance?.dispose) {
|
|
1963
1967
|
await backend.instance.dispose();
|
|
1964
1968
|
}
|
|
1965
|
-
|
|
1969
|
+
log.log(`[AppDataGroup:${this.groupId}] removeBackend: ${id} removed`);
|
|
1966
1970
|
}
|
|
1967
1971
|
listBackends() {
|
|
1968
1972
|
return Array.from(this.dataBackends.values()).map((b) => b.desc);
|
|
@@ -1988,7 +1992,7 @@ function toUint8Array(raw) {
|
|
|
1988
1992
|
}
|
|
1989
1993
|
async function createConfigRepo(appId, options = {}) {
|
|
1990
1994
|
const idbStoreName = options.idbStoreName || `zen-fs-config-${appId}`;
|
|
1991
|
-
|
|
1995
|
+
log.log(`[createConfigRepo] Creating IndexedDB primary (store: ${idbStoreName})...`);
|
|
1992
1996
|
const primaryInstance = await createBackend({
|
|
1993
1997
|
type: "IndexedDB",
|
|
1994
1998
|
options: { storeName: idbStoreName }
|
|
@@ -1997,12 +2001,12 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
1997
2001
|
const cacheOptions = options.cache === false ? void 0 : options.cache ?? {};
|
|
1998
2002
|
try {
|
|
1999
2003
|
await primaryInstance.mkdir(META_DIR);
|
|
2000
|
-
|
|
2004
|
+
log.log(`[createConfigRepo] /.meta/ ready`);
|
|
2001
2005
|
} catch (err) {
|
|
2002
2006
|
const msg = err.message || "";
|
|
2003
2007
|
if (msg.includes("File exists") || msg.includes("EEXIST") || err.code === "EEXIST") {
|
|
2004
2008
|
} else {
|
|
2005
|
-
|
|
2009
|
+
log.error(`[createConfigRepo] Failed to ensure /.meta/:`, err.message);
|
|
2006
2010
|
}
|
|
2007
2011
|
}
|
|
2008
2012
|
try {
|
|
@@ -2011,10 +2015,10 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
2011
2015
|
await primaryInstance.readFile(`${META_DIR}/group-type`);
|
|
2012
2016
|
} catch {
|
|
2013
2017
|
await primaryInstance.writeFile(`${META_DIR}/group-type`, groupTypeBytes);
|
|
2014
|
-
|
|
2018
|
+
log.log(`[createConfigRepo] group-type set to "config-sync"`);
|
|
2015
2019
|
}
|
|
2016
2020
|
} catch (err) {
|
|
2017
|
-
|
|
2021
|
+
log.warn(`[createConfigRepo] Failed to write group-type:`, err.message);
|
|
2018
2022
|
}
|
|
2019
2023
|
const tempRepo = new ConfigRepo(
|
|
2020
2024
|
appId,
|
|
@@ -2028,11 +2032,11 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
2028
2032
|
);
|
|
2029
2033
|
const oldBackendsMeta = await tempRepo.readMetaFile(BACKENDS_FILE);
|
|
2030
2034
|
if (oldBackendsMeta && oldBackendsMeta.backends?.length > 0) {
|
|
2031
|
-
|
|
2035
|
+
log.log(`[createConfigRepo] Migrating ${oldBackendsMeta.backends.length} backend(s) from backends.json to individual files...`);
|
|
2032
2036
|
await tempRepo.ensureDir(`${BACKENDS_DIR}/.keep`);
|
|
2033
2037
|
for (const desc of oldBackendsMeta.backends) {
|
|
2034
2038
|
if (desc.id === LOCAL_IDB_BACKEND_ID || desc.type === "IndexedDB") {
|
|
2035
|
-
|
|
2039
|
+
log.log(`[createConfigRepo] Skipping local backend ${desc.id} during migration`);
|
|
2036
2040
|
continue;
|
|
2037
2041
|
}
|
|
2038
2042
|
await tempRepo.writeBackendDescriptor(desc);
|
|
@@ -2050,7 +2054,7 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
2050
2054
|
}
|
|
2051
2055
|
}
|
|
2052
2056
|
}
|
|
2053
|
-
|
|
2057
|
+
log.log(`[createConfigRepo] Migration complete`);
|
|
2054
2058
|
}
|
|
2055
2059
|
let allBackends = await tempRepo.readAllBackendDescriptors();
|
|
2056
2060
|
if (options.backendInfo) {
|
|
@@ -2068,19 +2072,19 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
2068
2072
|
type: options.backendInfo.type,
|
|
2069
2073
|
options: options.backendInfo.options
|
|
2070
2074
|
});
|
|
2071
|
-
|
|
2075
|
+
log.log(`[createConfigRepo] Added replica backend: ${replicaId} (${options.backendInfo.type})`);
|
|
2072
2076
|
allBackends = [...allBackends, { id: replicaId, type: options.backendInfo.type, options: options.backendInfo.options }];
|
|
2073
2077
|
} else if (dupConfig) {
|
|
2074
|
-
|
|
2078
|
+
log.log(`[createConfigRepo] Replica with same config already registered as "${dupConfig.id}", skipping`);
|
|
2075
2079
|
} else {
|
|
2076
|
-
|
|
2080
|
+
log.log(`[createConfigRepo] Replica ${replicaId} already registered`);
|
|
2077
2081
|
}
|
|
2078
2082
|
}
|
|
2079
|
-
|
|
2083
|
+
log.log(`[createConfigRepo] Replica backends: ${allBackends.map((b) => b.id).join(", ") || "(none)"}`);
|
|
2080
2084
|
let nodeId = options.nodeId;
|
|
2081
2085
|
if (!nodeId) {
|
|
2082
2086
|
nodeId = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2083
|
-
|
|
2087
|
+
log.log(`[createConfigRepo] Generated nodeId: ${nodeId}`);
|
|
2084
2088
|
}
|
|
2085
2089
|
const serializer = createSerializerChain(options.serializer);
|
|
2086
2090
|
const repo = new ConfigRepo(
|
|
@@ -2096,11 +2100,11 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
2096
2100
|
await repo.setupSync(allBackends, LOCAL_IDB_BACKEND_ID, options.syncPollIntervalMs);
|
|
2097
2101
|
await repo.load();
|
|
2098
2102
|
if (repo.replicaCount > 0) {
|
|
2099
|
-
|
|
2103
|
+
log.log("[createConfigRepo] Starting background initial sync + dedup...");
|
|
2100
2104
|
repo.startBackgroundSync();
|
|
2101
2105
|
}
|
|
2102
2106
|
repo.syncMetaToReplicas().catch((err) => {
|
|
2103
|
-
|
|
2107
|
+
log.error("[createConfigRepo] background syncMetaToReplicas failed:", err);
|
|
2104
2108
|
});
|
|
2105
2109
|
return repo;
|
|
2106
2110
|
}
|
|
@@ -2109,31 +2113,15 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
2109
2113
|
var import_zen_fs_sync2 = require("zen-fs-sync");
|
|
2110
2114
|
|
|
2111
2115
|
// src/logger.ts
|
|
2112
|
-
var
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
try {
|
|
2118
|
-
return localStorage.getItem("ZEN_FS_CONFIG_DEBUG") === "1";
|
|
2119
|
-
} catch {
|
|
2120
|
-
return false;
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
return false;
|
|
2124
|
-
})();
|
|
2125
|
-
function createLogger(prefix) {
|
|
2126
|
-
if (!isDebug) {
|
|
2127
|
-
return () => {
|
|
2128
|
-
};
|
|
2129
|
-
}
|
|
2130
|
-
return (...args) => {
|
|
2131
|
-
console.log(`[${prefix}]`, ...args);
|
|
2132
|
-
};
|
|
2116
|
+
var import_localstorage_logger2 = require("@richard432/localstorage-logger");
|
|
2117
|
+
var MODULE_PREFIX = "zen-fs-config";
|
|
2118
|
+
function createLogger2(prefix) {
|
|
2119
|
+
const logger = (0, import_localstorage_logger2.createLogger)(`${MODULE_PREFIX}:${prefix}`);
|
|
2120
|
+
return (...args) => logger.log(...args);
|
|
2133
2121
|
}
|
|
2134
2122
|
|
|
2135
2123
|
// src/data-sync-group.ts
|
|
2136
|
-
var
|
|
2124
|
+
var log2 = createLogger2("data-sync-group");
|
|
2137
2125
|
var META_DIR2 = "/.meta";
|
|
2138
2126
|
var GROUP_TYPE_FILE2 = `${META_DIR2}/group-type`;
|
|
2139
2127
|
var BACKENDS_DIR2 = `${META_DIR2}/backends`;
|
|
@@ -2160,7 +2148,7 @@ var DataSyncGroup = class {
|
|
|
2160
2148
|
if (this.dataBackends.has(id)) {
|
|
2161
2149
|
throw new Error(`Backend "${id}" already exists in data group "${this.groupId}"`);
|
|
2162
2150
|
}
|
|
2163
|
-
|
|
2151
|
+
log2(`addBackend: creating ${id} (${type})...`);
|
|
2164
2152
|
const instance = await createBackend({ type, options });
|
|
2165
2153
|
const syncable = backendToSyncableFS(instance, `${type}(${id})`);
|
|
2166
2154
|
const localSyncable = backendToSyncableFS(this.localFS, `local(${this.groupId})`);
|
|
@@ -2175,12 +2163,12 @@ var DataSyncGroup = class {
|
|
|
2175
2163
|
);
|
|
2176
2164
|
this.dataBackends.set(id, { instance, syncable, pairId: pair.pairId, desc: { id, type, options, description } });
|
|
2177
2165
|
this.syncEngine.watch(pair.pairId);
|
|
2178
|
-
|
|
2166
|
+
log2(`addBackend: ${id} (${type}) connected, pair=${pair.pairId}`);
|
|
2179
2167
|
await this.saveBackendDescriptor(id, type, options, description);
|
|
2180
2168
|
try {
|
|
2181
2169
|
await this.syncEngine.sync(pair.pairId);
|
|
2182
2170
|
} catch (err) {
|
|
2183
|
-
|
|
2171
|
+
log2(`addBackend: initial sync failed for ${id}:`, err);
|
|
2184
2172
|
}
|
|
2185
2173
|
}
|
|
2186
2174
|
/**
|
|
@@ -2201,7 +2189,7 @@ var DataSyncGroup = class {
|
|
|
2201
2189
|
await this.localFS.unlink(`${BACKENDS_DIR2}/${id}.json`);
|
|
2202
2190
|
} catch {
|
|
2203
2191
|
}
|
|
2204
|
-
|
|
2192
|
+
log2(`removeBackend: ${id} removed`);
|
|
2205
2193
|
}
|
|
2206
2194
|
getSyncStatuses() {
|
|
2207
2195
|
return this.syncEngine.getStatusAll();
|
|
@@ -2223,7 +2211,7 @@ var DataSyncGroup = class {
|
|
|
2223
2211
|
}
|
|
2224
2212
|
}
|
|
2225
2213
|
this.dataBackends.clear();
|
|
2226
|
-
|
|
2214
|
+
log2(`disposed`);
|
|
2227
2215
|
}
|
|
2228
2216
|
// -----------------------------------------------------------------------
|
|
2229
2217
|
// Internal — used by the factory function
|
|
@@ -2281,7 +2269,7 @@ var DataSyncGroup = class {
|
|
|
2281
2269
|
};
|
|
2282
2270
|
async function createDataSyncGroup(appId, options = {}) {
|
|
2283
2271
|
const groupId = `data-${appId}-${Date.now().toString(36)}`;
|
|
2284
|
-
|
|
2272
|
+
log2(`createDataSyncGroup: appId=${appId} groupId=${groupId}`);
|
|
2285
2273
|
const localFS = await createBackend({
|
|
2286
2274
|
type: "InMemory",
|
|
2287
2275
|
options: { label: `data-sync-${appId}-${Date.now()}` }
|
|
@@ -2311,7 +2299,7 @@ async function createDataSyncGroup(appId, options = {}) {
|
|
|
2311
2299
|
new TextEncoder().encode("data-sync")
|
|
2312
2300
|
);
|
|
2313
2301
|
} catch (writeErr) {
|
|
2314
|
-
|
|
2302
|
+
log2(`Could not write group-type on remote: ${writeErr.message}`);
|
|
2315
2303
|
}
|
|
2316
2304
|
}
|
|
2317
2305
|
let remoteBackendDescs = [];
|
|
@@ -2356,15 +2344,15 @@ async function createDataSyncGroup(appId, options = {}) {
|
|
|
2356
2344
|
const syncable = backendToSyncableFS(instance, `${rdesc.type}(${rdesc.id})`);
|
|
2357
2345
|
group2._registerBackend(rdesc.id, instance, syncable, options.pollIntervalMs);
|
|
2358
2346
|
} catch (err) {
|
|
2359
|
-
|
|
2347
|
+
log2(`Failed to add remote backend ${rdesc.id}:`, err);
|
|
2360
2348
|
}
|
|
2361
2349
|
}
|
|
2362
2350
|
try {
|
|
2363
2351
|
await group2._syncAll();
|
|
2364
2352
|
} catch (err) {
|
|
2365
|
-
|
|
2353
|
+
log2(`Initial sync failed:`, err);
|
|
2366
2354
|
}
|
|
2367
|
-
|
|
2355
|
+
log2(`createDataSyncGroup: ready with ${group2._backendCount} backend(s)`);
|
|
2368
2356
|
return group2;
|
|
2369
2357
|
}
|
|
2370
2358
|
const group = new DataSyncGroup(appId, groupId, localFS);
|
|
@@ -2372,28 +2360,28 @@ async function createDataSyncGroup(appId, options = {}) {
|
|
|
2372
2360
|
await localFS.writeFile(GROUP_TYPE_FILE2, new TextEncoder().encode("data-sync"));
|
|
2373
2361
|
} catch {
|
|
2374
2362
|
}
|
|
2375
|
-
|
|
2363
|
+
log2(`createDataSyncGroup: local-only group created`);
|
|
2376
2364
|
return group;
|
|
2377
2365
|
}
|
|
2378
2366
|
|
|
2379
2367
|
// src/connect.ts
|
|
2380
|
-
var
|
|
2368
|
+
var log3 = createLogger2("connect");
|
|
2381
2369
|
var META_DIR3 = "/.meta";
|
|
2382
2370
|
var GROUP_TYPE_FILE3 = `${META_DIR3}/group-type`;
|
|
2383
2371
|
async function detectGroupType(type, options) {
|
|
2384
|
-
|
|
2372
|
+
log3(`detectGroupType: connecting to ${type}...`);
|
|
2385
2373
|
const tempBackend = await createBackend({ type, options });
|
|
2386
2374
|
try {
|
|
2387
2375
|
const raw = await tempBackend.readFile(GROUP_TYPE_FILE3, "utf-8");
|
|
2388
2376
|
const groupType = raw.trim();
|
|
2389
2377
|
if (groupType === "config-sync" || groupType === "data-sync") {
|
|
2390
|
-
|
|
2378
|
+
log3(`detectGroupType: detected "${groupType}"`);
|
|
2391
2379
|
return groupType;
|
|
2392
2380
|
}
|
|
2393
|
-
|
|
2381
|
+
log3(`detectGroupType: unknown group-type value "${groupType}", treating as null`);
|
|
2394
2382
|
return null;
|
|
2395
2383
|
} catch {
|
|
2396
|
-
|
|
2384
|
+
log3(`detectGroupType: no group-type file found (new backend)`);
|
|
2397
2385
|
return null;
|
|
2398
2386
|
} finally {
|
|
2399
2387
|
if (tempBackend?.dispose) {
|
|
@@ -2402,10 +2390,10 @@ async function detectGroupType(type, options) {
|
|
|
2402
2390
|
}
|
|
2403
2391
|
}
|
|
2404
2392
|
async function connect(appId, options = {}) {
|
|
2405
|
-
|
|
2393
|
+
log3(`connect: appId=${appId}`);
|
|
2406
2394
|
if (!options.backendInfo) {
|
|
2407
2395
|
const groupType2 = options.groupType ?? "config-sync";
|
|
2408
|
-
|
|
2396
|
+
log3(`connect: no backendInfo, using groupType="${groupType2}"`);
|
|
2409
2397
|
if (groupType2 === "data-sync") {
|
|
2410
2398
|
const dataGroup = await createDataSyncGroup(appId, {});
|
|
2411
2399
|
return { groupType: "data-sync", dataGroup };
|
|
@@ -2429,7 +2417,7 @@ async function connect(appId, options = {}) {
|
|
|
2429
2417
|
groupType = detectedType;
|
|
2430
2418
|
} else {
|
|
2431
2419
|
groupType = options.groupType ?? "config-sync";
|
|
2432
|
-
|
|
2420
|
+
log3(`connect: new backend, using groupType="${groupType}"`);
|
|
2433
2421
|
}
|
|
2434
2422
|
if (groupType === "data-sync") {
|
|
2435
2423
|
const dataGroup = await createDataSyncGroup(appId, {
|