zen-fs-config 0.4.0 → 0.4.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.d.mts CHANGED
@@ -399,7 +399,13 @@ declare class ConfigRepo implements IConfigRepo {
399
399
  readMetaFile<T>(path: string): Promise<T | null>;
400
400
  /** Path for a single backend descriptor: .meta/backends/{id}.json */
401
401
  backendFilePath(id: string): string;
402
- /** Read all backend descriptors from .meta/backends/*.json */
402
+ /**
403
+ * Read all backend descriptors from .meta/backends/*.json.
404
+ *
405
+ * If duplicate backends are detected (same type + options but different id),
406
+ * only the first one (sorted by id) is kept and the rest are removed
407
+ * (including their version sidecar files).
408
+ */
403
409
  readAllBackendDescriptors(): Promise<BackendDescriptor[]>;
404
410
  /** Write a single backend descriptor as .meta/backends/{id}.json */
405
411
  writeBackendDescriptor(desc: BackendDescriptor): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -399,7 +399,13 @@ declare class ConfigRepo implements IConfigRepo {
399
399
  readMetaFile<T>(path: string): Promise<T | null>;
400
400
  /** Path for a single backend descriptor: .meta/backends/{id}.json */
401
401
  backendFilePath(id: string): string;
402
- /** Read all backend descriptors from .meta/backends/*.json */
402
+ /**
403
+ * Read all backend descriptors from .meta/backends/*.json.
404
+ *
405
+ * If duplicate backends are detected (same type + options but different id),
406
+ * only the first one (sorted by id) is kept and the rest are removed
407
+ * (including their version sidecar files).
408
+ */
403
409
  readAllBackendDescriptors(): Promise<BackendDescriptor[]>;
404
410
  /** Write a single backend descriptor as .meta/backends/{id}.json */
405
411
  writeBackendDescriptor(desc: BackendDescriptor): Promise<void>;
package/dist/index.js CHANGED
@@ -485,7 +485,6 @@ var BACKENDS_DIR = `${META_DIR}/backends`;
485
485
  var CONFLICTS_DIR = `${META_DIR}/.conflicts`;
486
486
  var DELETIONS_DIR = `${META_DIR}/.deleted`;
487
487
  var NODES_DIR = "/nodes";
488
- var NODE_ID_FILE = `${NODES_DIR}/.node-id`;
489
488
  var LOCAL_IDB_BACKEND_ID = "local-idb";
490
489
  function tombstoneFileName(filePath) {
491
490
  return filePath.replace(/^\//, "").replace(/\//g, "__").replace(/\./g, "++") + ".json";
@@ -1080,7 +1079,13 @@ var ConfigRepo = class {
1080
1079
  backendFilePath(id) {
1081
1080
  return `${BACKENDS_DIR}/${id}.json`;
1082
1081
  }
1083
- /** Read all backend descriptors from .meta/backends/*.json */
1082
+ /**
1083
+ * Read all backend descriptors from .meta/backends/*.json.
1084
+ *
1085
+ * If duplicate backends are detected (same type + options but different id),
1086
+ * only the first one (sorted by id) is kept and the rest are removed
1087
+ * (including their version sidecar files).
1088
+ */
1084
1089
  async readAllBackendDescriptors() {
1085
1090
  try {
1086
1091
  const entries = await this.cachedFS.readdir(BACKENDS_DIR);
@@ -1089,11 +1094,33 @@ var ConfigRepo = class {
1089
1094
  if (!entry.endsWith(".json")) continue;
1090
1095
  try {
1091
1096
  const raw = await this.cachedFS.readFile(`${BACKENDS_DIR}/${entry}`);
1092
- descriptors.push(JSON.parse(new TextDecoder().decode(toUint8Array(raw))));
1097
+ const desc = JSON.parse(new TextDecoder().decode(toUint8Array(raw)));
1098
+ if (desc.id && desc.type) {
1099
+ descriptors.push(desc);
1100
+ }
1093
1101
  } catch {
1094
1102
  }
1095
1103
  }
1096
- return descriptors;
1104
+ const seen = /* @__PURE__ */ new Map();
1105
+ const duplicates = [];
1106
+ descriptors.sort((a, b) => a.id.localeCompare(b.id));
1107
+ for (const desc of descriptors) {
1108
+ const key = `${desc.type}:${JSON.stringify(desc.options ?? {})}`;
1109
+ if (seen.has(key)) {
1110
+ duplicates.push(desc.id);
1111
+ } else {
1112
+ seen.set(key, desc.id);
1113
+ }
1114
+ }
1115
+ if (duplicates.length > 0) {
1116
+ console.log(
1117
+ `[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
1118
+ );
1119
+ for (const dupId of duplicates) {
1120
+ await this.removeBackendDescriptor(dupId);
1121
+ }
1122
+ }
1123
+ return descriptors.filter((d) => !duplicates.includes(d.id));
1097
1124
  } catch {
1098
1125
  return [];
1099
1126
  }
@@ -1291,22 +1318,9 @@ async function createConfigRepo(appId, options = {}) {
1291
1318
  const allBackends = await tempRepo.readAllBackendDescriptors();
1292
1319
  console.log(`[createConfigRepo] Replica backends: ${allBackends.map((b) => b.id).join(", ") || "(none)"}`);
1293
1320
  let nodeId = options.nodeId;
1294
- if (!nodeId && typeof process !== "undefined" && process.env?.NODE_ID) {
1295
- nodeId = process.env.NODE_ID;
1296
- }
1297
- if (!nodeId) {
1298
- try {
1299
- const raw = await cachedFS.readFile(NODE_ID_FILE);
1300
- nodeId = new TextDecoder().decode(toUint8Array(raw)).trim();
1301
- } catch {
1302
- }
1303
- }
1304
1321
  if (!nodeId) {
1305
1322
  nodeId = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1306
- try {
1307
- await cachedFS.writeFile(NODE_ID_FILE, new TextEncoder().encode(nodeId));
1308
- } catch {
1309
- }
1323
+ console.log(`[createConfigRepo] Generated nodeId: ${nodeId}`);
1310
1324
  }
1311
1325
  const serializer = createSerializerChain(options.serializer);
1312
1326
  const repo = new ConfigRepo(
package/dist/index.mjs CHANGED
@@ -433,7 +433,6 @@ var BACKENDS_DIR = `${META_DIR}/backends`;
433
433
  var CONFLICTS_DIR = `${META_DIR}/.conflicts`;
434
434
  var DELETIONS_DIR = `${META_DIR}/.deleted`;
435
435
  var NODES_DIR = "/nodes";
436
- var NODE_ID_FILE = `${NODES_DIR}/.node-id`;
437
436
  var LOCAL_IDB_BACKEND_ID = "local-idb";
438
437
  function tombstoneFileName(filePath) {
439
438
  return filePath.replace(/^\//, "").replace(/\//g, "__").replace(/\./g, "++") + ".json";
@@ -1028,7 +1027,13 @@ var ConfigRepo = class {
1028
1027
  backendFilePath(id) {
1029
1028
  return `${BACKENDS_DIR}/${id}.json`;
1030
1029
  }
1031
- /** Read all backend descriptors from .meta/backends/*.json */
1030
+ /**
1031
+ * Read all backend descriptors from .meta/backends/*.json.
1032
+ *
1033
+ * If duplicate backends are detected (same type + options but different id),
1034
+ * only the first one (sorted by id) is kept and the rest are removed
1035
+ * (including their version sidecar files).
1036
+ */
1032
1037
  async readAllBackendDescriptors() {
1033
1038
  try {
1034
1039
  const entries = await this.cachedFS.readdir(BACKENDS_DIR);
@@ -1037,11 +1042,33 @@ var ConfigRepo = class {
1037
1042
  if (!entry.endsWith(".json")) continue;
1038
1043
  try {
1039
1044
  const raw = await this.cachedFS.readFile(`${BACKENDS_DIR}/${entry}`);
1040
- descriptors.push(JSON.parse(new TextDecoder().decode(toUint8Array(raw))));
1045
+ const desc = JSON.parse(new TextDecoder().decode(toUint8Array(raw)));
1046
+ if (desc.id && desc.type) {
1047
+ descriptors.push(desc);
1048
+ }
1041
1049
  } catch {
1042
1050
  }
1043
1051
  }
1044
- return descriptors;
1052
+ const seen = /* @__PURE__ */ new Map();
1053
+ const duplicates = [];
1054
+ descriptors.sort((a, b) => a.id.localeCompare(b.id));
1055
+ for (const desc of descriptors) {
1056
+ const key = `${desc.type}:${JSON.stringify(desc.options ?? {})}`;
1057
+ if (seen.has(key)) {
1058
+ duplicates.push(desc.id);
1059
+ } else {
1060
+ seen.set(key, desc.id);
1061
+ }
1062
+ }
1063
+ if (duplicates.length > 0) {
1064
+ console.log(
1065
+ `[ConfigRepo] readAllBackendDescriptors: removing ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`
1066
+ );
1067
+ for (const dupId of duplicates) {
1068
+ await this.removeBackendDescriptor(dupId);
1069
+ }
1070
+ }
1071
+ return descriptors.filter((d) => !duplicates.includes(d.id));
1045
1072
  } catch {
1046
1073
  return [];
1047
1074
  }
@@ -1239,22 +1266,9 @@ async function createConfigRepo(appId, options = {}) {
1239
1266
  const allBackends = await tempRepo.readAllBackendDescriptors();
1240
1267
  console.log(`[createConfigRepo] Replica backends: ${allBackends.map((b) => b.id).join(", ") || "(none)"}`);
1241
1268
  let nodeId = options.nodeId;
1242
- if (!nodeId && typeof process !== "undefined" && process.env?.NODE_ID) {
1243
- nodeId = process.env.NODE_ID;
1244
- }
1245
- if (!nodeId) {
1246
- try {
1247
- const raw = await cachedFS.readFile(NODE_ID_FILE);
1248
- nodeId = new TextDecoder().decode(toUint8Array(raw)).trim();
1249
- } catch {
1250
- }
1251
- }
1252
1269
  if (!nodeId) {
1253
1270
  nodeId = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1254
- try {
1255
- await cachedFS.writeFile(NODE_ID_FILE, new TextEncoder().encode(nodeId));
1256
- } catch {
1257
- }
1271
+ console.log(`[createConfigRepo] Generated nodeId: ${nodeId}`);
1258
1272
  }
1259
1273
  const serializer = createSerializerChain(options.serializer);
1260
1274
  const repo = new ConfigRepo(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-fs-config",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",