zen-fs-config 0.5.0 → 0.5.1

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 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 = `${item.desc.type}:${JSON.stringify(item.desc.options ?? {})}`;
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
- await this.removeBackendDescriptor(dupId);
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);
@@ -1260,6 +1281,13 @@ var ConfigRepo = class {
1260
1281
  if (existing.some((b) => b.id === id)) {
1261
1282
  throw new Error(`Backend "${id}" already exists. Use removeBackend() first.`);
1262
1283
  }
1284
+ const newKey = backendDedupKey({ id, type, options });
1285
+ const dup = existing.find((b) => backendDedupKey(b) === newKey);
1286
+ if (dup) {
1287
+ throw new Error(
1288
+ `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.`
1289
+ );
1290
+ }
1263
1291
  console.log(`[ConfigRepo] addBackend: creating ${id} (${type})...`);
1264
1292
  const instance = await createBackend({ type, options });
1265
1293
  const syncable = backendToSyncableFS(instance, `${type}(${id})`);
@@ -1655,13 +1683,21 @@ async function createConfigRepo(appId, options = {}) {
1655
1683
  const replicaId = options.primaryBackendId || `${options.backendInfo.type}-replica`;
1656
1684
  const allBackends2 = await tempRepo.readAllBackendDescriptors();
1657
1685
  const hasReplica = allBackends2.some((b) => b.id === replicaId);
1658
- if (!hasReplica) {
1686
+ const newKey = backendDedupKey({
1687
+ id: replicaId,
1688
+ type: options.backendInfo.type,
1689
+ options: options.backendInfo.options
1690
+ });
1691
+ const dupConfig = allBackends2.find((b) => backendDedupKey(b) === newKey);
1692
+ if (!hasReplica && !dupConfig) {
1659
1693
  await tempRepo.writeBackendDescriptor({
1660
1694
  id: replicaId,
1661
1695
  type: options.backendInfo.type,
1662
1696
  options: options.backendInfo.options
1663
1697
  });
1664
1698
  console.log(`[createConfigRepo] Added replica backend: ${replicaId} (${options.backendInfo.type})`);
1699
+ } else if (dupConfig) {
1700
+ console.log(`[createConfigRepo] Replica with same config already registered as "${dupConfig.id}", skipping`);
1665
1701
  } else {
1666
1702
  console.log(`[createConfigRepo] Replica ${replicaId} already registered`);
1667
1703
  }
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 = `${item.desc.type}:${JSON.stringify(item.desc.options ?? {})}`;
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
- await this.removeBackendDescriptor(dupId);
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);
@@ -1203,6 +1224,13 @@ var ConfigRepo = class {
1203
1224
  if (existing.some((b) => b.id === id)) {
1204
1225
  throw new Error(`Backend "${id}" already exists. Use removeBackend() first.`);
1205
1226
  }
1227
+ const newKey = backendDedupKey({ id, type, options });
1228
+ const dup = existing.find((b) => backendDedupKey(b) === newKey);
1229
+ if (dup) {
1230
+ throw new Error(
1231
+ `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.`
1232
+ );
1233
+ }
1206
1234
  console.log(`[ConfigRepo] addBackend: creating ${id} (${type})...`);
1207
1235
  const instance = await createBackend({ type, options });
1208
1236
  const syncable = backendToSyncableFS(instance, `${type}(${id})`);
@@ -1598,13 +1626,21 @@ async function createConfigRepo(appId, options = {}) {
1598
1626
  const replicaId = options.primaryBackendId || `${options.backendInfo.type}-replica`;
1599
1627
  const allBackends2 = await tempRepo.readAllBackendDescriptors();
1600
1628
  const hasReplica = allBackends2.some((b) => b.id === replicaId);
1601
- if (!hasReplica) {
1629
+ const newKey = backendDedupKey({
1630
+ id: replicaId,
1631
+ type: options.backendInfo.type,
1632
+ options: options.backendInfo.options
1633
+ });
1634
+ const dupConfig = allBackends2.find((b) => backendDedupKey(b) === newKey);
1635
+ if (!hasReplica && !dupConfig) {
1602
1636
  await tempRepo.writeBackendDescriptor({
1603
1637
  id: replicaId,
1604
1638
  type: options.backendInfo.type,
1605
1639
  options: options.backendInfo.options
1606
1640
  });
1607
1641
  console.log(`[createConfigRepo] Added replica backend: ${replicaId} (${options.backendInfo.type})`);
1642
+ } else if (dupConfig) {
1643
+ console.log(`[createConfigRepo] Replica with same config already registered as "${dupConfig.id}", skipping`);
1608
1644
  } else {
1609
1645
  console.log(`[createConfigRepo] Replica ${replicaId} already registered`);
1610
1646
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-fs-config",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Distributed config management library built on ZenFS, zen-fs-cache, and zen-fs-sync",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",