zen-fs-config 0.5.0 → 0.5.2
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 +60 -6
- package/dist/index.mjs +60 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -533,6 +533,22 @@ var LOCAL_IDB_BACKEND_ID = "local-idb";
|
|
|
533
533
|
function tombstoneFileName(filePath) {
|
|
534
534
|
return filePath.replace(/^\//, "").replace(/\//g, "__").replace(/\./g, "++") + ".json";
|
|
535
535
|
}
|
|
536
|
+
function stableOptionsKey(options) {
|
|
537
|
+
if (!options || typeof options !== "object") return "{}";
|
|
538
|
+
return JSON.stringify(sortKeysDeep(options));
|
|
539
|
+
}
|
|
540
|
+
function sortKeysDeep(obj) {
|
|
541
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
542
|
+
if (Array.isArray(obj)) return obj.map(sortKeysDeep);
|
|
543
|
+
const sorted = {};
|
|
544
|
+
for (const key of Object.keys(obj).sort()) {
|
|
545
|
+
sorted[key] = sortKeysDeep(obj[key]);
|
|
546
|
+
}
|
|
547
|
+
return sorted;
|
|
548
|
+
}
|
|
549
|
+
function backendDedupKey(desc) {
|
|
550
|
+
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
551
|
+
}
|
|
536
552
|
var ConfigRepo = class {
|
|
537
553
|
appId;
|
|
538
554
|
nodeId;
|
|
@@ -1163,7 +1179,7 @@ var ConfigRepo = class {
|
|
|
1163
1179
|
const seen = /* @__PURE__ */ new Map();
|
|
1164
1180
|
const duplicates = [];
|
|
1165
1181
|
for (const item of items) {
|
|
1166
|
-
const key =
|
|
1182
|
+
const key = backendDedupKey(item.desc);
|
|
1167
1183
|
const existing = seen.get(key);
|
|
1168
1184
|
if (existing) {
|
|
1169
1185
|
if (item.mtime < existing.mtime) {
|
|
@@ -1181,7 +1197,12 @@ var ConfigRepo = class {
|
|
|
1181
1197
|
`[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
|
|
1182
1198
|
);
|
|
1183
1199
|
for (const dupId of duplicates) {
|
|
1184
|
-
|
|
1200
|
+
const descPath = this.backendFilePath(dupId);
|
|
1201
|
+
try {
|
|
1202
|
+
await this.deleteFile(descPath);
|
|
1203
|
+
} catch {
|
|
1204
|
+
await this.removeBackendDescriptor(dupId);
|
|
1205
|
+
}
|
|
1185
1206
|
}
|
|
1186
1207
|
}
|
|
1187
1208
|
return Array.from(seen.values()).map((i) => i.desc);
|
|
@@ -1244,7 +1265,12 @@ var ConfigRepo = class {
|
|
|
1244
1265
|
const current = await this.readAllBackendDescriptors();
|
|
1245
1266
|
for (const desc of current) {
|
|
1246
1267
|
if (!keepIds.has(desc.id)) {
|
|
1247
|
-
|
|
1268
|
+
const descPath = this.backendFilePath(desc.id);
|
|
1269
|
+
try {
|
|
1270
|
+
await this.deleteFile(descPath);
|
|
1271
|
+
} catch {
|
|
1272
|
+
await this.removeBackendDescriptor(desc.id);
|
|
1273
|
+
}
|
|
1248
1274
|
}
|
|
1249
1275
|
}
|
|
1250
1276
|
}
|
|
@@ -1260,6 +1286,13 @@ var ConfigRepo = class {
|
|
|
1260
1286
|
if (existing.some((b) => b.id === id)) {
|
|
1261
1287
|
throw new Error(`Backend "${id}" already exists. Use removeBackend() first.`);
|
|
1262
1288
|
}
|
|
1289
|
+
const newKey = backendDedupKey({ id, type, options });
|
|
1290
|
+
const dup = existing.find((b) => backendDedupKey(b) === newKey);
|
|
1291
|
+
if (dup) {
|
|
1292
|
+
throw new Error(
|
|
1293
|
+
`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.`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1263
1296
|
console.log(`[ConfigRepo] addBackend: creating ${id} (${type})...`);
|
|
1264
1297
|
const instance = await createBackend({ type, options });
|
|
1265
1298
|
const syncable = backendToSyncableFS(instance, `${type}(${id})`);
|
|
@@ -1292,14 +1325,27 @@ var ConfigRepo = class {
|
|
|
1292
1325
|
if (!replica) {
|
|
1293
1326
|
throw new Error(`Backend "${id}" is not a registered replica`);
|
|
1294
1327
|
}
|
|
1328
|
+
const descPath = this.backendFilePath(id);
|
|
1329
|
+
try {
|
|
1330
|
+
await replica.instance.unlink(descPath);
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
try {
|
|
1334
|
+
await replica.instance.unlink(versionPathFor(descPath));
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
try {
|
|
1338
|
+
await this.deleteFile(descPath);
|
|
1339
|
+
} catch {
|
|
1340
|
+
await this.removeBackendDescriptor(id);
|
|
1341
|
+
}
|
|
1295
1342
|
this.syncEngine.removePair(replica.pairId);
|
|
1296
1343
|
console.log(`[ConfigRepo] removeBackend: sync pair ${replica.pairId} removed`);
|
|
1297
1344
|
this.replicaBackends.delete(id);
|
|
1298
1345
|
if (replica.instance?.dispose) {
|
|
1299
1346
|
await replica.instance.dispose();
|
|
1300
1347
|
}
|
|
1301
|
-
|
|
1302
|
-
console.log(`[ConfigRepo] removeBackend: ${id} removed`);
|
|
1348
|
+
console.log(`[ConfigRepo] removeBackend: ${id} removed (tombstone written, remote cleaned)`);
|
|
1303
1349
|
}
|
|
1304
1350
|
// -----------------------------------------------------------------------
|
|
1305
1351
|
// IConfigRepo — Group Type
|
|
@@ -1655,13 +1701,21 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
1655
1701
|
const replicaId = options.primaryBackendId || `${options.backendInfo.type}-replica`;
|
|
1656
1702
|
const allBackends2 = await tempRepo.readAllBackendDescriptors();
|
|
1657
1703
|
const hasReplica = allBackends2.some((b) => b.id === replicaId);
|
|
1658
|
-
|
|
1704
|
+
const newKey = backendDedupKey({
|
|
1705
|
+
id: replicaId,
|
|
1706
|
+
type: options.backendInfo.type,
|
|
1707
|
+
options: options.backendInfo.options
|
|
1708
|
+
});
|
|
1709
|
+
const dupConfig = allBackends2.find((b) => backendDedupKey(b) === newKey);
|
|
1710
|
+
if (!hasReplica && !dupConfig) {
|
|
1659
1711
|
await tempRepo.writeBackendDescriptor({
|
|
1660
1712
|
id: replicaId,
|
|
1661
1713
|
type: options.backendInfo.type,
|
|
1662
1714
|
options: options.backendInfo.options
|
|
1663
1715
|
});
|
|
1664
1716
|
console.log(`[createConfigRepo] Added replica backend: ${replicaId} (${options.backendInfo.type})`);
|
|
1717
|
+
} else if (dupConfig) {
|
|
1718
|
+
console.log(`[createConfigRepo] Replica with same config already registered as "${dupConfig.id}", skipping`);
|
|
1665
1719
|
} else {
|
|
1666
1720
|
console.log(`[createConfigRepo] Replica ${replicaId} already registered`);
|
|
1667
1721
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -476,6 +476,22 @@ var LOCAL_IDB_BACKEND_ID = "local-idb";
|
|
|
476
476
|
function tombstoneFileName(filePath) {
|
|
477
477
|
return filePath.replace(/^\//, "").replace(/\//g, "__").replace(/\./g, "++") + ".json";
|
|
478
478
|
}
|
|
479
|
+
function stableOptionsKey(options) {
|
|
480
|
+
if (!options || typeof options !== "object") return "{}";
|
|
481
|
+
return JSON.stringify(sortKeysDeep(options));
|
|
482
|
+
}
|
|
483
|
+
function sortKeysDeep(obj) {
|
|
484
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
485
|
+
if (Array.isArray(obj)) return obj.map(sortKeysDeep);
|
|
486
|
+
const sorted = {};
|
|
487
|
+
for (const key of Object.keys(obj).sort()) {
|
|
488
|
+
sorted[key] = sortKeysDeep(obj[key]);
|
|
489
|
+
}
|
|
490
|
+
return sorted;
|
|
491
|
+
}
|
|
492
|
+
function backendDedupKey(desc) {
|
|
493
|
+
return `${desc.type}:${stableOptionsKey(desc.options)}`;
|
|
494
|
+
}
|
|
479
495
|
var ConfigRepo = class {
|
|
480
496
|
appId;
|
|
481
497
|
nodeId;
|
|
@@ -1106,7 +1122,7 @@ var ConfigRepo = class {
|
|
|
1106
1122
|
const seen = /* @__PURE__ */ new Map();
|
|
1107
1123
|
const duplicates = [];
|
|
1108
1124
|
for (const item of items) {
|
|
1109
|
-
const key =
|
|
1125
|
+
const key = backendDedupKey(item.desc);
|
|
1110
1126
|
const existing = seen.get(key);
|
|
1111
1127
|
if (existing) {
|
|
1112
1128
|
if (item.mtime < existing.mtime) {
|
|
@@ -1124,7 +1140,12 @@ var ConfigRepo = class {
|
|
|
1124
1140
|
`[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
|
|
1125
1141
|
);
|
|
1126
1142
|
for (const dupId of duplicates) {
|
|
1127
|
-
|
|
1143
|
+
const descPath = this.backendFilePath(dupId);
|
|
1144
|
+
try {
|
|
1145
|
+
await this.deleteFile(descPath);
|
|
1146
|
+
} catch {
|
|
1147
|
+
await this.removeBackendDescriptor(dupId);
|
|
1148
|
+
}
|
|
1128
1149
|
}
|
|
1129
1150
|
}
|
|
1130
1151
|
return Array.from(seen.values()).map((i) => i.desc);
|
|
@@ -1187,7 +1208,12 @@ var ConfigRepo = class {
|
|
|
1187
1208
|
const current = await this.readAllBackendDescriptors();
|
|
1188
1209
|
for (const desc of current) {
|
|
1189
1210
|
if (!keepIds.has(desc.id)) {
|
|
1190
|
-
|
|
1211
|
+
const descPath = this.backendFilePath(desc.id);
|
|
1212
|
+
try {
|
|
1213
|
+
await this.deleteFile(descPath);
|
|
1214
|
+
} catch {
|
|
1215
|
+
await this.removeBackendDescriptor(desc.id);
|
|
1216
|
+
}
|
|
1191
1217
|
}
|
|
1192
1218
|
}
|
|
1193
1219
|
}
|
|
@@ -1203,6 +1229,13 @@ var ConfigRepo = class {
|
|
|
1203
1229
|
if (existing.some((b) => b.id === id)) {
|
|
1204
1230
|
throw new Error(`Backend "${id}" already exists. Use removeBackend() first.`);
|
|
1205
1231
|
}
|
|
1232
|
+
const newKey = backendDedupKey({ id, type, options });
|
|
1233
|
+
const dup = existing.find((b) => backendDedupKey(b) === newKey);
|
|
1234
|
+
if (dup) {
|
|
1235
|
+
throw new Error(
|
|
1236
|
+
`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.`
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1206
1239
|
console.log(`[ConfigRepo] addBackend: creating ${id} (${type})...`);
|
|
1207
1240
|
const instance = await createBackend({ type, options });
|
|
1208
1241
|
const syncable = backendToSyncableFS(instance, `${type}(${id})`);
|
|
@@ -1235,14 +1268,27 @@ var ConfigRepo = class {
|
|
|
1235
1268
|
if (!replica) {
|
|
1236
1269
|
throw new Error(`Backend "${id}" is not a registered replica`);
|
|
1237
1270
|
}
|
|
1271
|
+
const descPath = this.backendFilePath(id);
|
|
1272
|
+
try {
|
|
1273
|
+
await replica.instance.unlink(descPath);
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
try {
|
|
1277
|
+
await replica.instance.unlink(versionPathFor(descPath));
|
|
1278
|
+
} catch {
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
await this.deleteFile(descPath);
|
|
1282
|
+
} catch {
|
|
1283
|
+
await this.removeBackendDescriptor(id);
|
|
1284
|
+
}
|
|
1238
1285
|
this.syncEngine.removePair(replica.pairId);
|
|
1239
1286
|
console.log(`[ConfigRepo] removeBackend: sync pair ${replica.pairId} removed`);
|
|
1240
1287
|
this.replicaBackends.delete(id);
|
|
1241
1288
|
if (replica.instance?.dispose) {
|
|
1242
1289
|
await replica.instance.dispose();
|
|
1243
1290
|
}
|
|
1244
|
-
|
|
1245
|
-
console.log(`[ConfigRepo] removeBackend: ${id} removed`);
|
|
1291
|
+
console.log(`[ConfigRepo] removeBackend: ${id} removed (tombstone written, remote cleaned)`);
|
|
1246
1292
|
}
|
|
1247
1293
|
// -----------------------------------------------------------------------
|
|
1248
1294
|
// IConfigRepo — Group Type
|
|
@@ -1598,13 +1644,21 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
1598
1644
|
const replicaId = options.primaryBackendId || `${options.backendInfo.type}-replica`;
|
|
1599
1645
|
const allBackends2 = await tempRepo.readAllBackendDescriptors();
|
|
1600
1646
|
const hasReplica = allBackends2.some((b) => b.id === replicaId);
|
|
1601
|
-
|
|
1647
|
+
const newKey = backendDedupKey({
|
|
1648
|
+
id: replicaId,
|
|
1649
|
+
type: options.backendInfo.type,
|
|
1650
|
+
options: options.backendInfo.options
|
|
1651
|
+
});
|
|
1652
|
+
const dupConfig = allBackends2.find((b) => backendDedupKey(b) === newKey);
|
|
1653
|
+
if (!hasReplica && !dupConfig) {
|
|
1602
1654
|
await tempRepo.writeBackendDescriptor({
|
|
1603
1655
|
id: replicaId,
|
|
1604
1656
|
type: options.backendInfo.type,
|
|
1605
1657
|
options: options.backendInfo.options
|
|
1606
1658
|
});
|
|
1607
1659
|
console.log(`[createConfigRepo] Added replica backend: ${replicaId} (${options.backendInfo.type})`);
|
|
1660
|
+
} else if (dupConfig) {
|
|
1661
|
+
console.log(`[createConfigRepo] Replica with same config already registered as "${dupConfig.id}", skipping`);
|
|
1608
1662
|
} else {
|
|
1609
1663
|
console.log(`[createConfigRepo] Replica ${replicaId} already registered`);
|
|
1610
1664
|
}
|