zen-fs-config 0.3.18 → 0.3.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.d.mts CHANGED
@@ -170,6 +170,11 @@ interface IConfigRepo {
170
170
  getSyncRules(): Promise<SyncRulesMeta | null>;
171
171
  /** Write .meta/sync-rules.json. */
172
172
  updateSyncRules(meta: SyncRulesMeta): Promise<void>;
173
+ /**
174
+ * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
175
+ * Called automatically by createConfigRepo() after setupSync().
176
+ */
177
+ syncMetaToReplicas(): Promise<void>;
173
178
  /** Dispose: stop all sync, release cache FS and resources. */
174
179
  dispose(): Promise<void>;
175
180
  }
@@ -254,6 +259,16 @@ declare class ConfigRepo implements IConfigRepo {
254
259
  }): Promise<SyncResult>;
255
260
  peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
256
261
  flush(): Promise<SyncResult[]>;
262
+ /**
263
+ * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
264
+ *
265
+ * This ensures the backend topology is available on every replica, enabling
266
+ * any program that connects to any backend to discover the full topology.
267
+ *
268
+ * Called automatically by createConfigRepo() after setupSync().
269
+ * Can also be called manually after updateBackends() / updateSyncRules().
270
+ */
271
+ syncMetaToReplicas(): Promise<void>;
257
272
  getSyncStatuses(): Map<string, SyncPairStatus>;
258
273
  resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
259
274
  listConflicts(): Promise<ConflictArchive[]>;
package/dist/index.d.ts CHANGED
@@ -170,6 +170,11 @@ interface IConfigRepo {
170
170
  getSyncRules(): Promise<SyncRulesMeta | null>;
171
171
  /** Write .meta/sync-rules.json. */
172
172
  updateSyncRules(meta: SyncRulesMeta): Promise<void>;
173
+ /**
174
+ * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
175
+ * Called automatically by createConfigRepo() after setupSync().
176
+ */
177
+ syncMetaToReplicas(): Promise<void>;
173
178
  /** Dispose: stop all sync, release cache FS and resources. */
174
179
  dispose(): Promise<void>;
175
180
  }
@@ -254,6 +259,16 @@ declare class ConfigRepo implements IConfigRepo {
254
259
  }): Promise<SyncResult>;
255
260
  peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
256
261
  flush(): Promise<SyncResult[]>;
262
+ /**
263
+ * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
264
+ *
265
+ * This ensures the backend topology is available on every replica, enabling
266
+ * any program that connects to any backend to discover the full topology.
267
+ *
268
+ * Called automatically by createConfigRepo() after setupSync().
269
+ * Can also be called manually after updateBackends() / updateSyncRules().
270
+ */
271
+ syncMetaToReplicas(): Promise<void>;
257
272
  getSyncStatuses(): Map<string, SyncPairStatus>;
258
273
  resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
259
274
  listConflicts(): Promise<ConflictArchive[]>;
package/dist/index.js CHANGED
@@ -414,9 +414,12 @@ async function sha256(data) {
414
414
  const hex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
415
415
  return `sha256:${hex}`;
416
416
  }
417
- const nodeCrypto = await import("crypto");
418
- const hash = nodeCrypto.createHash("sha256").update(Buffer.from(buffer)).digest("hex");
419
- return `sha256:${hash}`;
417
+ if (typeof globalThis.window === "undefined") {
418
+ const nodeCrypto = await new Function("return import('node:crypto')")();
419
+ const hash = nodeCrypto.createHash("sha256").update(Buffer.from(buffer)).digest("hex");
420
+ return `sha256:${hash}`;
421
+ }
422
+ throw new Error("SHA-256 not available: neither Web Crypto nor Node.js crypto module found");
420
423
  }
421
424
  async function readVersion(fs, versionFilePath) {
422
425
  try {
@@ -662,6 +665,32 @@ var ConfigRepo = class {
662
665
  const resultsMap = await this.syncEngine.syncAll();
663
666
  return Array.from(resultsMap.values());
664
667
  }
668
+ /**
669
+ * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
670
+ *
671
+ * This ensures the backend topology is available on every replica, enabling
672
+ * any program that connects to any backend to discover the full topology.
673
+ *
674
+ * Called automatically by createConfigRepo() after setupSync().
675
+ * Can also be called manually after updateBackends() / updateSyncRules().
676
+ */
677
+ async syncMetaToReplicas() {
678
+ this.assertNotDisposed();
679
+ for (const metaFile of [BACKENDS_FILE, SYNC_RULES_FILE]) {
680
+ try {
681
+ const content = await this.cachedFS.readFile(metaFile);
682
+ for (const [id, replica] of this.replicaBackends) {
683
+ try {
684
+ await replica.syncable.writeFile(metaFile, content);
685
+ console.log(`[ConfigRepo] Synced ${metaFile} to replica ${id}`);
686
+ } catch (err) {
687
+ console.error(`[ConfigRepo] Failed to sync ${metaFile} to ${id}:`, err.message);
688
+ }
689
+ }
690
+ } catch {
691
+ }
692
+ }
693
+ }
665
694
  getSyncStatuses() {
666
695
  this.assertNotDisposed();
667
696
  return this.syncEngine.getStatusAll();
@@ -1049,7 +1078,12 @@ async function createConfigRepo(appId, options) {
1049
1078
  replicas: backendsMeta.backends.map((b) => b.id)
1050
1079
  },
1051
1080
  { prefix: `${NODES_DIR}/`, direction: "none" },
1052
- { prefix: `${META_DIR}/`, direction: "none" }
1081
+ {
1082
+ prefix: `${META_DIR}/`,
1083
+ direction: "one-way",
1084
+ conflictStrategy: "source-wins",
1085
+ replicas: backendsMeta.backends.map((b) => b.id)
1086
+ }
1053
1087
  ]
1054
1088
  };
1055
1089
  }
@@ -1091,6 +1125,7 @@ async function createConfigRepo(appId, options) {
1091
1125
  backendsMeta.backends,
1092
1126
  options.primaryBackendId
1093
1127
  );
1128
+ await repo.syncMetaToReplicas();
1094
1129
  await repo.load();
1095
1130
  return repo;
1096
1131
  }
package/dist/index.mjs CHANGED
@@ -365,9 +365,12 @@ async function sha256(data) {
365
365
  const hex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
366
366
  return `sha256:${hex}`;
367
367
  }
368
- const nodeCrypto = await import("crypto");
369
- const hash = nodeCrypto.createHash("sha256").update(Buffer.from(buffer)).digest("hex");
370
- return `sha256:${hash}`;
368
+ if (typeof globalThis.window === "undefined") {
369
+ const nodeCrypto = await new Function("return import('node:crypto')")();
370
+ const hash = nodeCrypto.createHash("sha256").update(Buffer.from(buffer)).digest("hex");
371
+ return `sha256:${hash}`;
372
+ }
373
+ throw new Error("SHA-256 not available: neither Web Crypto nor Node.js crypto module found");
371
374
  }
372
375
  async function readVersion(fs, versionFilePath) {
373
376
  try {
@@ -613,6 +616,32 @@ var ConfigRepo = class {
613
616
  const resultsMap = await this.syncEngine.syncAll();
614
617
  return Array.from(resultsMap.values());
615
618
  }
619
+ /**
620
+ * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
621
+ *
622
+ * This ensures the backend topology is available on every replica, enabling
623
+ * any program that connects to any backend to discover the full topology.
624
+ *
625
+ * Called automatically by createConfigRepo() after setupSync().
626
+ * Can also be called manually after updateBackends() / updateSyncRules().
627
+ */
628
+ async syncMetaToReplicas() {
629
+ this.assertNotDisposed();
630
+ for (const metaFile of [BACKENDS_FILE, SYNC_RULES_FILE]) {
631
+ try {
632
+ const content = await this.cachedFS.readFile(metaFile);
633
+ for (const [id, replica] of this.replicaBackends) {
634
+ try {
635
+ await replica.syncable.writeFile(metaFile, content);
636
+ console.log(`[ConfigRepo] Synced ${metaFile} to replica ${id}`);
637
+ } catch (err) {
638
+ console.error(`[ConfigRepo] Failed to sync ${metaFile} to ${id}:`, err.message);
639
+ }
640
+ }
641
+ } catch {
642
+ }
643
+ }
644
+ }
616
645
  getSyncStatuses() {
617
646
  this.assertNotDisposed();
618
647
  return this.syncEngine.getStatusAll();
@@ -1000,7 +1029,12 @@ async function createConfigRepo(appId, options) {
1000
1029
  replicas: backendsMeta.backends.map((b) => b.id)
1001
1030
  },
1002
1031
  { prefix: `${NODES_DIR}/`, direction: "none" },
1003
- { prefix: `${META_DIR}/`, direction: "none" }
1032
+ {
1033
+ prefix: `${META_DIR}/`,
1034
+ direction: "one-way",
1035
+ conflictStrategy: "source-wins",
1036
+ replicas: backendsMeta.backends.map((b) => b.id)
1037
+ }
1004
1038
  ]
1005
1039
  };
1006
1040
  }
@@ -1042,6 +1076,7 @@ async function createConfigRepo(appId, options) {
1042
1076
  backendsMeta.backends,
1043
1077
  options.primaryBackendId
1044
1078
  );
1079
+ await repo.syncMetaToReplicas();
1045
1080
  await repo.load();
1046
1081
  return repo;
1047
1082
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-fs-config",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
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",