zen-fs-config 0.3.21 → 0.3.23

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
@@ -1,4 +1,4 @@
1
- import { ConflictStrategy, SyncResult, SyncPairStatus, SyncableFS } from 'zen-fs-sync';
1
+ import { SyncResult, SyncPairStatus, ConflictStrategy, SyncableFS } from 'zen-fs-sync';
2
2
  export { SyncPairStatus, SyncResult } from 'zen-fs-sync';
3
3
  import * as node_fs from 'node:fs';
4
4
 
@@ -18,24 +18,6 @@ interface BackendsMeta {
18
18
  version: 1;
19
19
  backends: BackendDescriptor[];
20
20
  }
21
- /** Sync direction for a path prefix. */
22
- type SyncDirection = 'one-way' | 'bi-directional' | 'none';
23
- /** A single sync rule. */
24
- interface SyncRule {
25
- /** Path prefix this rule applies to (e.g., "/app-a/"). */
26
- prefix: string;
27
- /** Sync direction. */
28
- direction: SyncDirection;
29
- /** Conflict resolution strategy (only relevant for bi-directional). */
30
- conflictStrategy?: ConflictStrategy;
31
- /** IDs of replica backends to sync with (from .meta/backends.json). */
32
- replicas?: string[];
33
- }
34
- /** Content of `.meta/sync-rules.json`. */
35
- interface SyncRulesMeta {
36
- version: 1;
37
- rules: SyncRule[];
38
- }
39
21
  /** Content of a sidecar `.version` file. */
40
22
  interface VersionMeta {
41
23
  /** Monotonically increasing version number. */
@@ -57,18 +39,18 @@ interface ConflictArchive {
57
39
  sourceAuthor: string;
58
40
  /** Author of the target side. */
59
41
  targetAuthor: string;
60
- /** Source side content. */
61
- sourceContent: unknown;
62
- /** Target side content. */
63
- targetContent: unknown;
64
42
  /** Source side version. */
65
43
  sourceVersion: number;
66
44
  /** Target side version. */
67
45
  targetVersion: number;
68
46
  /** Strategy that was used to auto-resolve (if any). */
69
47
  resolvedStrategy?: ConflictStrategy;
70
- /** The content that was written as the resolved result (if auto-resolved). */
71
- resolvedContent?: unknown;
48
+ /** Path to the source-side backup file. */
49
+ sourceBackupPath: string;
50
+ /** Path to the target-side backup file. */
51
+ targetBackupPath: string;
52
+ /** Path to the resolved file (present after resolution). */
53
+ resolvedBackupPath?: string;
72
54
  }
73
55
  /** Information passed to conflict event handlers. */
74
56
  interface ConflictInfo {
@@ -103,11 +85,6 @@ interface CacheOptions {
103
85
  /** TTL in milliseconds for cache hits without revalidation. Default: 0 (always revalidate). */
104
86
  ttlMs?: number;
105
87
  }
106
- /** Bootstrap data, written to .meta/ only on first initialization. */
107
- interface BootstrapData {
108
- backends: Omit<BackendDescriptor, 'description'>[];
109
- syncRules: SyncRule[];
110
- }
111
88
  /** Options for creating a ConfigRepo. */
112
89
  interface ConfigRepoOptions {
113
90
  /** The backend ID (from .meta/backends.json) to use as this instance's primary. */
@@ -121,8 +98,6 @@ interface ConfigRepoOptions {
121
98
  nodeId?: string;
122
99
  /** Cache configuration. */
123
100
  cache?: CacheOptions;
124
- /** Bootstrap data (only used when .meta/backends.json doesn't exist). */
125
- bootstrap?: BootstrapData;
126
101
  /** Custom serializer. */
127
102
  serializer?: ConfigSerializer;
128
103
  /** Custom conflict handler. Called before auto-resolution. */
@@ -162,16 +137,17 @@ interface IConfigRepo {
162
137
  resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
163
138
  /** List all conflict archives. */
164
139
  listConflicts(): Promise<ConflictArchive[]>;
140
+ /** Read the raw content of a conflict backup file (source/target/resolved).
141
+ * @param conflictId The meta.json path (e.g., "12345_path.conflict/meta.json")
142
+ * @param fileType One of "source", "target", or "resolved"
143
+ */
144
+ readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
165
145
  /** Read .meta/backends.json. */
166
146
  getBackends(): Promise<BackendsMeta | null>;
167
147
  /** Write .meta/backends.json. */
168
148
  updateBackends(meta: BackendsMeta): Promise<void>;
169
- /** Read .meta/sync-rules.json. */
170
- getSyncRules(): Promise<SyncRulesMeta | null>;
171
- /** Write .meta/sync-rules.json. */
172
- updateSyncRules(meta: SyncRulesMeta): Promise<void>;
173
149
  /**
174
- * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
150
+ * Sync .meta/ files (backends.json) to all replica backends.
175
151
  * Called automatically by createConfigRepo() after setupSync().
176
152
  */
177
153
  syncMetaToReplicas(): Promise<void>;
@@ -260,18 +236,21 @@ declare class ConfigRepo implements IConfigRepo {
260
236
  peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
261
237
  flush(): Promise<SyncResult[]>;
262
238
  /**
263
- * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
239
+ * Sync .meta/ files (backends.json) to all replica backends.
264
240
  *
265
241
  * This ensures the backend topology is available on every replica, enabling
266
242
  * any program that connects to any backend to discover the full topology.
267
243
  *
268
244
  * Called automatically by createConfigRepo() after setupSync().
269
- * Can also be called manually after updateBackends() / updateSyncRules().
270
245
  */
271
246
  syncMetaToReplicas(): Promise<void>;
247
+ /** Compare two file contents (handles Uint8Array, ArrayBuffer, string, Buffer). */
248
+ private bufferEqual;
249
+ private toUint8Array;
272
250
  getSyncStatuses(): Map<string, SyncPairStatus>;
273
251
  resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
274
252
  listConflicts(): Promise<ConflictArchive[]>;
253
+ readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
275
254
  dispose(): Promise<void>;
276
255
  setupSync(backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
277
256
  private persistConfig;
@@ -279,12 +258,10 @@ declare class ConfigRepo implements IConfigRepo {
279
258
  private handleConflict;
280
259
  private ensureDir;
281
260
  private walkDir;
282
- writeMetaFile(path: string, data: BackendsMeta | SyncRulesMeta): Promise<void>;
261
+ writeMetaFile(path: string, data: BackendsMeta): Promise<void>;
283
262
  readMetaFile<T>(path: string): Promise<T | null>;
284
263
  getBackends(): Promise<BackendsMeta | null>;
285
264
  updateBackends(meta: BackendsMeta): Promise<void>;
286
- getSyncRules(): Promise<SyncRulesMeta | null>;
287
- updateSyncRules(meta: SyncRulesMeta): Promise<void>;
288
265
  private tryParse;
289
266
  private assertNotDisposed;
290
267
  }
@@ -374,4 +351,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
374
351
  */
375
352
  declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
376
353
 
377
- export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
354
+ export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConflictStrategy, SyncResult, SyncPairStatus, SyncableFS } from 'zen-fs-sync';
1
+ import { SyncResult, SyncPairStatus, ConflictStrategy, SyncableFS } from 'zen-fs-sync';
2
2
  export { SyncPairStatus, SyncResult } from 'zen-fs-sync';
3
3
  import * as node_fs from 'node:fs';
4
4
 
@@ -18,24 +18,6 @@ interface BackendsMeta {
18
18
  version: 1;
19
19
  backends: BackendDescriptor[];
20
20
  }
21
- /** Sync direction for a path prefix. */
22
- type SyncDirection = 'one-way' | 'bi-directional' | 'none';
23
- /** A single sync rule. */
24
- interface SyncRule {
25
- /** Path prefix this rule applies to (e.g., "/app-a/"). */
26
- prefix: string;
27
- /** Sync direction. */
28
- direction: SyncDirection;
29
- /** Conflict resolution strategy (only relevant for bi-directional). */
30
- conflictStrategy?: ConflictStrategy;
31
- /** IDs of replica backends to sync with (from .meta/backends.json). */
32
- replicas?: string[];
33
- }
34
- /** Content of `.meta/sync-rules.json`. */
35
- interface SyncRulesMeta {
36
- version: 1;
37
- rules: SyncRule[];
38
- }
39
21
  /** Content of a sidecar `.version` file. */
40
22
  interface VersionMeta {
41
23
  /** Monotonically increasing version number. */
@@ -57,18 +39,18 @@ interface ConflictArchive {
57
39
  sourceAuthor: string;
58
40
  /** Author of the target side. */
59
41
  targetAuthor: string;
60
- /** Source side content. */
61
- sourceContent: unknown;
62
- /** Target side content. */
63
- targetContent: unknown;
64
42
  /** Source side version. */
65
43
  sourceVersion: number;
66
44
  /** Target side version. */
67
45
  targetVersion: number;
68
46
  /** Strategy that was used to auto-resolve (if any). */
69
47
  resolvedStrategy?: ConflictStrategy;
70
- /** The content that was written as the resolved result (if auto-resolved). */
71
- resolvedContent?: unknown;
48
+ /** Path to the source-side backup file. */
49
+ sourceBackupPath: string;
50
+ /** Path to the target-side backup file. */
51
+ targetBackupPath: string;
52
+ /** Path to the resolved file (present after resolution). */
53
+ resolvedBackupPath?: string;
72
54
  }
73
55
  /** Information passed to conflict event handlers. */
74
56
  interface ConflictInfo {
@@ -103,11 +85,6 @@ interface CacheOptions {
103
85
  /** TTL in milliseconds for cache hits without revalidation. Default: 0 (always revalidate). */
104
86
  ttlMs?: number;
105
87
  }
106
- /** Bootstrap data, written to .meta/ only on first initialization. */
107
- interface BootstrapData {
108
- backends: Omit<BackendDescriptor, 'description'>[];
109
- syncRules: SyncRule[];
110
- }
111
88
  /** Options for creating a ConfigRepo. */
112
89
  interface ConfigRepoOptions {
113
90
  /** The backend ID (from .meta/backends.json) to use as this instance's primary. */
@@ -121,8 +98,6 @@ interface ConfigRepoOptions {
121
98
  nodeId?: string;
122
99
  /** Cache configuration. */
123
100
  cache?: CacheOptions;
124
- /** Bootstrap data (only used when .meta/backends.json doesn't exist). */
125
- bootstrap?: BootstrapData;
126
101
  /** Custom serializer. */
127
102
  serializer?: ConfigSerializer;
128
103
  /** Custom conflict handler. Called before auto-resolution. */
@@ -162,16 +137,17 @@ interface IConfigRepo {
162
137
  resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
163
138
  /** List all conflict archives. */
164
139
  listConflicts(): Promise<ConflictArchive[]>;
140
+ /** Read the raw content of a conflict backup file (source/target/resolved).
141
+ * @param conflictId The meta.json path (e.g., "12345_path.conflict/meta.json")
142
+ * @param fileType One of "source", "target", or "resolved"
143
+ */
144
+ readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
165
145
  /** Read .meta/backends.json. */
166
146
  getBackends(): Promise<BackendsMeta | null>;
167
147
  /** Write .meta/backends.json. */
168
148
  updateBackends(meta: BackendsMeta): Promise<void>;
169
- /** Read .meta/sync-rules.json. */
170
- getSyncRules(): Promise<SyncRulesMeta | null>;
171
- /** Write .meta/sync-rules.json. */
172
- updateSyncRules(meta: SyncRulesMeta): Promise<void>;
173
149
  /**
174
- * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
150
+ * Sync .meta/ files (backends.json) to all replica backends.
175
151
  * Called automatically by createConfigRepo() after setupSync().
176
152
  */
177
153
  syncMetaToReplicas(): Promise<void>;
@@ -260,18 +236,21 @@ declare class ConfigRepo implements IConfigRepo {
260
236
  peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
261
237
  flush(): Promise<SyncResult[]>;
262
238
  /**
263
- * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
239
+ * Sync .meta/ files (backends.json) to all replica backends.
264
240
  *
265
241
  * This ensures the backend topology is available on every replica, enabling
266
242
  * any program that connects to any backend to discover the full topology.
267
243
  *
268
244
  * Called automatically by createConfigRepo() after setupSync().
269
- * Can also be called manually after updateBackends() / updateSyncRules().
270
245
  */
271
246
  syncMetaToReplicas(): Promise<void>;
247
+ /** Compare two file contents (handles Uint8Array, ArrayBuffer, string, Buffer). */
248
+ private bufferEqual;
249
+ private toUint8Array;
272
250
  getSyncStatuses(): Map<string, SyncPairStatus>;
273
251
  resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
274
252
  listConflicts(): Promise<ConflictArchive[]>;
253
+ readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
275
254
  dispose(): Promise<void>;
276
255
  setupSync(backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
277
256
  private persistConfig;
@@ -279,12 +258,10 @@ declare class ConfigRepo implements IConfigRepo {
279
258
  private handleConflict;
280
259
  private ensureDir;
281
260
  private walkDir;
282
- writeMetaFile(path: string, data: BackendsMeta | SyncRulesMeta): Promise<void>;
261
+ writeMetaFile(path: string, data: BackendsMeta): Promise<void>;
283
262
  readMetaFile<T>(path: string): Promise<T | null>;
284
263
  getBackends(): Promise<BackendsMeta | null>;
285
264
  updateBackends(meta: BackendsMeta): Promise<void>;
286
- getSyncRules(): Promise<SyncRulesMeta | null>;
287
- updateSyncRules(meta: SyncRulesMeta): Promise<void>;
288
265
  private tryParse;
289
266
  private assertNotDisposed;
290
267
  }
@@ -374,4 +351,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
374
351
  */
375
352
  declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
376
353
 
377
- export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
354
+ export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
package/dist/index.js CHANGED
@@ -484,10 +484,8 @@ async function verifyOrRepairVersion(fs, configFilePath, author) {
484
484
  // src/config-repo.ts
485
485
  var META_DIR = "/.meta";
486
486
  var BACKENDS_FILE = `${META_DIR}/backends.json`;
487
- var SYNC_RULES_FILE = `${META_DIR}/sync-rules.json`;
488
487
  var CONFLICTS_DIR = `${META_DIR}/.conflicts`;
489
488
  var NODES_DIR = "/nodes";
490
- var SHARED_DIR = "/shared";
491
489
  var NODE_ID_FILE = `${NODES_DIR}/.node-id`;
492
490
  var ConfigRepo = class {
493
491
  appId;
@@ -533,12 +531,6 @@ var ConfigRepo = class {
533
531
  backends: data.backends
534
532
  });
535
533
  }
536
- if (data.syncRules) {
537
- await this.writeMetaFile(SYNC_RULES_FILE, {
538
- version: 1,
539
- rules: data.syncRules
540
- });
541
- }
542
534
  }
543
535
  await this.reloadConfigCache();
544
536
  }
@@ -666,33 +658,68 @@ var ConfigRepo = class {
666
658
  return Array.from(resultsMap.values());
667
659
  }
668
660
  /**
669
- * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
661
+ * Sync .meta/ files (backends.json) to all replica backends.
670
662
  *
671
663
  * This ensures the backend topology is available on every replica, enabling
672
664
  * any program that connects to any backend to discover the full topology.
673
665
  *
674
666
  * Called automatically by createConfigRepo() after setupSync().
675
- * Can also be called manually after updateBackends() / updateSyncRules().
676
667
  */
677
668
  async syncMetaToReplicas() {
678
669
  this.assertNotDisposed();
679
- for (const metaFile of [BACKENDS_FILE, SYNC_RULES_FILE]) {
670
+ try {
671
+ const content = await this.cachedFS.readFile(BACKENDS_FILE);
672
+ const vPath = versionPathFor(BACKENDS_FILE);
673
+ let vContent;
680
674
  try {
681
- const content = await this.cachedFS.readFile(metaFile);
682
- const vPath = versionPathFor(metaFile);
683
- const vContent = await this.cachedFS.readFile(vPath);
684
- for (const [id, replica] of this.replicaBackends) {
675
+ vContent = await this.cachedFS.readFile(vPath);
676
+ } catch {
677
+ }
678
+ for (const [id, replica] of this.replicaBackends) {
679
+ try {
680
+ let needWrite = true;
685
681
  try {
686
- await replica.syncable.writeFile(metaFile, content);
687
- await replica.syncable.writeFile(vPath, vContent);
688
- console.log(`[ConfigRepo] Synced ${metaFile} + .version to replica ${id}`);
689
- } catch (err) {
690
- console.error(`[ConfigRepo] Failed to sync ${metaFile} to ${id}:`, err.message);
682
+ const existing = await replica.syncable.readFile(BACKENDS_FILE);
683
+ if (this.bufferEqual(content, existing)) {
684
+ needWrite = false;
685
+ console.log(`[ConfigRepo] ${BACKENDS_FILE} already up-to-date on ${id}, skipping`);
686
+ }
687
+ } catch {
688
+ }
689
+ if (needWrite) {
690
+ await replica.syncable.writeFile(BACKENDS_FILE, content);
691
+ console.log(`[ConfigRepo] Synced ${BACKENDS_FILE} to replica ${id}`);
691
692
  }
693
+ if (vContent) {
694
+ try {
695
+ await replica.syncable.writeFile(vPath, vContent);
696
+ } catch (err) {
697
+ console.error(`[ConfigRepo] Failed to sync ${vPath} to ${id}:`, err.message);
698
+ }
699
+ }
700
+ } catch (err) {
701
+ console.error(`[ConfigRepo] Failed to sync ${BACKENDS_FILE} to ${id}:`, err.message);
692
702
  }
693
- } catch {
694
703
  }
704
+ } catch {
705
+ }
706
+ }
707
+ /** Compare two file contents (handles Uint8Array, ArrayBuffer, string, Buffer). */
708
+ bufferEqual(a, b) {
709
+ const ua = this.toUint8Array(a);
710
+ const ub = this.toUint8Array(b);
711
+ if (ua.length !== ub.length) return false;
712
+ for (let i = 0; i < ua.length; i++) {
713
+ if (ua[i] !== ub[i]) return false;
695
714
  }
715
+ return true;
716
+ }
717
+ toUint8Array(raw) {
718
+ if (raw instanceof ArrayBuffer) return new Uint8Array(raw);
719
+ if (raw instanceof Uint8Array) return raw;
720
+ if (typeof raw === "string") return new TextEncoder().encode(raw);
721
+ if (Buffer.isBuffer(raw)) return new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
722
+ return new Uint8Array(raw);
696
723
  }
697
724
  getSyncStatuses() {
698
725
  this.assertNotDisposed();
@@ -703,9 +730,9 @@ var ConfigRepo = class {
703
730
  // -----------------------------------------------------------------------
704
731
  async resolveConflict(conflictId, mergedContent) {
705
732
  this.assertNotDisposed();
706
- const archivePath = `${CONFLICTS_DIR}/${conflictId}`;
733
+ const metaPath = `${CONFLICTS_DIR}/${conflictId}`;
707
734
  try {
708
- const raw = await this.cachedFS.readFile(archivePath);
735
+ const raw = await this.cachedFS.readFile(metaPath);
709
736
  const archive = JSON.parse(
710
737
  new TextDecoder().decode(toUint8Array(raw))
711
738
  );
@@ -720,9 +747,13 @@ var ConfigRepo = class {
720
747
  author
721
748
  );
722
749
  await writeVersion(this.fullFS, versionPathFor(configPath), version);
723
- archive.resolvedContent = mergedContent;
750
+ const conflictDir = metaPath.substring(0, metaPath.lastIndexOf("/"));
751
+ const resolvedBackupPath = `${conflictDir}/resolved`;
752
+ const resolvedBytes = typeof mergedContent === "string" ? new TextEncoder().encode(mergedContent) : new TextEncoder().encode(JSON.stringify(mergedContent, null, 2));
753
+ await this.cachedFS.writeFile(resolvedBackupPath, resolvedBytes);
754
+ archive.resolvedBackupPath = `./resolved`;
724
755
  await this.cachedFS.writeFile(
725
- archivePath,
756
+ metaPath,
726
757
  new TextEncoder().encode(JSON.stringify(archive, null, 2))
727
758
  );
728
759
  } catch (err) {
@@ -735,9 +766,9 @@ var ConfigRepo = class {
735
766
  try {
736
767
  const entries = await this.cachedFS.readdir(CONFLICTS_DIR);
737
768
  for (const entry of entries) {
738
- if (!entry.endsWith(".json")) continue;
769
+ const metaPath = `${CONFLICTS_DIR}/${entry}/meta.json`;
739
770
  try {
740
- const raw = await this.cachedFS.readFile(`${CONFLICTS_DIR}/${entry}`);
771
+ const raw = await this.cachedFS.readFile(metaPath);
741
772
  const archive = JSON.parse(
742
773
  new TextDecoder().decode(toUint8Array(raw))
743
774
  );
@@ -749,6 +780,13 @@ var ConfigRepo = class {
749
780
  }
750
781
  return archives.sort((a, b) => a.timestamp - b.timestamp);
751
782
  }
783
+ async readConflictBackup(conflictId, fileType) {
784
+ this.assertNotDisposed();
785
+ const conflictDir = `${CONFLICTS_DIR}/${conflictId}`.replace(/\/meta\.json$/, "");
786
+ const filePath = `${conflictDir}/${fileType}`;
787
+ const raw = await this.cachedFS.readFile(filePath);
788
+ return new TextDecoder().decode(toUint8Array(raw));
789
+ }
752
790
  // -----------------------------------------------------------------------
753
791
  // IConfigRepo — Lifecycle
754
792
  // -----------------------------------------------------------------------
@@ -799,7 +837,7 @@ var ConfigRepo = class {
799
837
  );
800
838
  console.log(`[ConfigRepo] Sync pair added: pairId=${pair.pairId}, replica=${replicaId}, root=/`);
801
839
  const conflictHandler = (event) => {
802
- this.handleConflict(event, { prefix: "/", direction: "one-way" });
840
+ this.handleConflict(event);
803
841
  };
804
842
  this.syncEngine.on(pair.pairId, "conflict", conflictHandler);
805
843
  this.syncEngine.watch(pair.pairId);
@@ -835,45 +873,57 @@ var ConfigRepo = class {
835
873
  // -----------------------------------------------------------------------
836
874
  // Internal — Conflict Handling
837
875
  // -----------------------------------------------------------------------
838
- async handleConflict(event, _rule) {
876
+ async handleConflict(event) {
839
877
  const conflict = event.conflict;
840
878
  if (!conflict) return;
879
+ const conflictId = `${event.timestamp}_${conflict.path.replace(/\//g, "_")}`;
880
+ const conflictDir = `${CONFLICTS_DIR}/${conflictId}`;
881
+ const sourceBackupPath = `${conflictDir}/source`;
882
+ const targetBackupPath = `${conflictDir}/target`;
883
+ await this.ensureDir(conflictDir);
884
+ await this.cachedFS.writeFile(
885
+ sourceBackupPath,
886
+ new TextEncoder().encode(conflict.sourceContent)
887
+ );
888
+ await this.cachedFS.writeFile(
889
+ targetBackupPath,
890
+ new TextEncoder().encode(conflict.targetContent)
891
+ );
892
+ let sourceVersion = 0;
893
+ try {
894
+ const srcVer = await readVersion(this.fullFS, versionPathFor(conflict.path));
895
+ if (srcVer) sourceVersion = srcVer.version;
896
+ } catch {
897
+ }
841
898
  const archive = {
842
899
  conflictPath: conflict.path,
843
900
  timestamp: event.timestamp,
844
901
  sourceAuthor: `${this.appId}/${this.nodeId}`,
845
902
  targetAuthor: "unknown",
846
- sourceContent: this.tryParse(conflict.sourceContent),
847
- targetContent: this.tryParse(conflict.targetContent),
848
- sourceVersion: 0,
903
+ sourceVersion,
849
904
  targetVersion: 0,
850
- resolvedStrategy: conflict.resolvedWith
905
+ resolvedStrategy: conflict.resolvedWith,
906
+ sourceBackupPath: `./source`,
907
+ targetBackupPath: `./target`
851
908
  };
852
- try {
853
- const srcVer = await readVersion(this.fullFS, versionPathFor(conflict.path));
854
- if (srcVer) archive.sourceVersion = srcVer.version;
855
- } catch {
856
- }
857
- const archiveFileName = `${event.timestamp}_${conflict.path.replace(/\//g, "_")}.conflict.json`;
858
- const archivePath = `${CONFLICTS_DIR}/${archiveFileName}`;
859
- await this.ensureDir(archivePath);
909
+ const metaPath = `${conflictDir}/meta.json`;
860
910
  await this.cachedFS.writeFile(
861
- archivePath,
911
+ metaPath,
862
912
  new TextEncoder().encode(JSON.stringify(archive, null, 2))
863
913
  );
864
914
  if (this.onConflictCallback) {
865
915
  const info = {
866
- conflictId: archiveFileName,
916
+ conflictId: `${conflictId}/meta.json`,
867
917
  path: conflict.path,
868
918
  sourceAuthor: archive.sourceAuthor,
869
919
  targetAuthor: archive.targetAuthor,
870
- sourceContent: archive.sourceContent,
871
- targetContent: archive.targetContent
920
+ sourceContent: this.tryParse(conflict.sourceContent),
921
+ targetContent: this.tryParse(conflict.targetContent)
872
922
  };
873
923
  try {
874
924
  const customMerge = await this.onConflictCallback(info);
875
925
  if (customMerge !== null && customMerge !== void 0) {
876
- await this.resolveConflict(archiveFileName, customMerge);
926
+ await this.resolveConflict(`${conflictId}/meta.json`, customMerge);
877
927
  }
878
928
  } catch (err) {
879
929
  console.error("[zen-fs-config] Conflict handler error:", err);
@@ -958,14 +1008,6 @@ var ConfigRepo = class {
958
1008
  this.assertNotDisposed();
959
1009
  await this.writeMetaFile(BACKENDS_FILE, meta);
960
1010
  }
961
- async getSyncRules() {
962
- this.assertNotDisposed();
963
- return this.readMetaFile(SYNC_RULES_FILE);
964
- }
965
- async updateSyncRules(meta) {
966
- this.assertNotDisposed();
967
- await this.writeMetaFile(SYNC_RULES_FILE, meta);
968
- }
969
1011
  tryParse(content) {
970
1012
  try {
971
1013
  return JSON.parse(content);
@@ -1026,24 +1068,17 @@ async function createConfigRepo(appId, options) {
1026
1068
  );
1027
1069
  let backendsMeta = await tempRepo.readMetaFile(BACKENDS_FILE);
1028
1070
  if (!backendsMeta) {
1029
- if (options.bootstrap) {
1030
- backendsMeta = {
1031
- version: 1,
1032
- backends: options.bootstrap.backends
1033
- };
1034
- console.log(`[createConfigRepo] First init: using bootstrap backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
1035
- } else {
1036
- backendsMeta = {
1037
- version: 1,
1038
- backends: [
1039
- {
1040
- id: options.primaryBackendId,
1041
- type: options.backendInfo.type,
1042
- options: options.backendInfo.options
1043
- }
1044
- ]
1045
- };
1046
- }
1071
+ backendsMeta = {
1072
+ version: 1,
1073
+ backends: [
1074
+ {
1075
+ id: options.primaryBackendId,
1076
+ type: options.backendInfo.type,
1077
+ options: options.backendInfo.options
1078
+ }
1079
+ ]
1080
+ };
1081
+ console.log(`[createConfigRepo] First init: using bootstrap backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
1047
1082
  } else {
1048
1083
  console.log(`[createConfigRepo] Reconnect: using stored backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
1049
1084
  }
@@ -1058,45 +1093,6 @@ async function createConfigRepo(appId, options) {
1058
1093
  });
1059
1094
  }
1060
1095
  await tempRepo.writeMetaFile(BACKENDS_FILE, backendsMeta);
1061
- let syncRulesMeta = await tempRepo.readMetaFile(SYNC_RULES_FILE);
1062
- if (!syncRulesMeta) {
1063
- if (options.bootstrap) {
1064
- syncRulesMeta = {
1065
- version: 1,
1066
- rules: options.bootstrap.syncRules
1067
- };
1068
- console.log(`[createConfigRepo] First init: using bootstrap syncRules: ${syncRulesMeta.rules.length} rules`);
1069
- } else {
1070
- syncRulesMeta = {
1071
- version: 1,
1072
- rules: [
1073
- {
1074
- prefix: `/${appId}/`,
1075
- direction: "one-way",
1076
- conflictStrategy: "source-wins",
1077
- replicas: backendsMeta.backends.map((b) => b.id)
1078
- },
1079
- {
1080
- prefix: `${SHARED_DIR}/`,
1081
- direction: "bi-directional",
1082
- conflictStrategy: "merge",
1083
- replicas: backendsMeta.backends.map((b) => b.id)
1084
- },
1085
- { prefix: `${NODES_DIR}/`, direction: "none" },
1086
- {
1087
- prefix: `${META_DIR}/`,
1088
- direction: "one-way",
1089
- conflictStrategy: "source-wins",
1090
- replicas: backendsMeta.backends.map((b) => b.id)
1091
- }
1092
- ]
1093
- };
1094
- }
1095
- }
1096
- if (syncRulesMeta) {
1097
- console.log(`[createConfigRepo] Reconnect: using stored syncRules: ${syncRulesMeta.rules.length} rules`);
1098
- }
1099
- await tempRepo.writeMetaFile(SYNC_RULES_FILE, syncRulesMeta);
1100
1096
  let nodeId = options.nodeId;
1101
1097
  if (!nodeId) {
1102
1098
  nodeId = process.env.NODE_ID;
package/dist/index.mjs CHANGED
@@ -435,10 +435,8 @@ async function verifyOrRepairVersion(fs, configFilePath, author) {
435
435
  // src/config-repo.ts
436
436
  var META_DIR = "/.meta";
437
437
  var BACKENDS_FILE = `${META_DIR}/backends.json`;
438
- var SYNC_RULES_FILE = `${META_DIR}/sync-rules.json`;
439
438
  var CONFLICTS_DIR = `${META_DIR}/.conflicts`;
440
439
  var NODES_DIR = "/nodes";
441
- var SHARED_DIR = "/shared";
442
440
  var NODE_ID_FILE = `${NODES_DIR}/.node-id`;
443
441
  var ConfigRepo = class {
444
442
  appId;
@@ -484,12 +482,6 @@ var ConfigRepo = class {
484
482
  backends: data.backends
485
483
  });
486
484
  }
487
- if (data.syncRules) {
488
- await this.writeMetaFile(SYNC_RULES_FILE, {
489
- version: 1,
490
- rules: data.syncRules
491
- });
492
- }
493
485
  }
494
486
  await this.reloadConfigCache();
495
487
  }
@@ -617,33 +609,68 @@ var ConfigRepo = class {
617
609
  return Array.from(resultsMap.values());
618
610
  }
619
611
  /**
620
- * Sync .meta/ files (backends.json, sync-rules.json) to all replica backends.
612
+ * Sync .meta/ files (backends.json) to all replica backends.
621
613
  *
622
614
  * This ensures the backend topology is available on every replica, enabling
623
615
  * any program that connects to any backend to discover the full topology.
624
616
  *
625
617
  * Called automatically by createConfigRepo() after setupSync().
626
- * Can also be called manually after updateBackends() / updateSyncRules().
627
618
  */
628
619
  async syncMetaToReplicas() {
629
620
  this.assertNotDisposed();
630
- for (const metaFile of [BACKENDS_FILE, SYNC_RULES_FILE]) {
621
+ try {
622
+ const content = await this.cachedFS.readFile(BACKENDS_FILE);
623
+ const vPath = versionPathFor(BACKENDS_FILE);
624
+ let vContent;
631
625
  try {
632
- const content = await this.cachedFS.readFile(metaFile);
633
- const vPath = versionPathFor(metaFile);
634
- const vContent = await this.cachedFS.readFile(vPath);
635
- for (const [id, replica] of this.replicaBackends) {
626
+ vContent = await this.cachedFS.readFile(vPath);
627
+ } catch {
628
+ }
629
+ for (const [id, replica] of this.replicaBackends) {
630
+ try {
631
+ let needWrite = true;
636
632
  try {
637
- await replica.syncable.writeFile(metaFile, content);
638
- await replica.syncable.writeFile(vPath, vContent);
639
- console.log(`[ConfigRepo] Synced ${metaFile} + .version to replica ${id}`);
640
- } catch (err) {
641
- console.error(`[ConfigRepo] Failed to sync ${metaFile} to ${id}:`, err.message);
633
+ const existing = await replica.syncable.readFile(BACKENDS_FILE);
634
+ if (this.bufferEqual(content, existing)) {
635
+ needWrite = false;
636
+ console.log(`[ConfigRepo] ${BACKENDS_FILE} already up-to-date on ${id}, skipping`);
637
+ }
638
+ } catch {
639
+ }
640
+ if (needWrite) {
641
+ await replica.syncable.writeFile(BACKENDS_FILE, content);
642
+ console.log(`[ConfigRepo] Synced ${BACKENDS_FILE} to replica ${id}`);
642
643
  }
644
+ if (vContent) {
645
+ try {
646
+ await replica.syncable.writeFile(vPath, vContent);
647
+ } catch (err) {
648
+ console.error(`[ConfigRepo] Failed to sync ${vPath} to ${id}:`, err.message);
649
+ }
650
+ }
651
+ } catch (err) {
652
+ console.error(`[ConfigRepo] Failed to sync ${BACKENDS_FILE} to ${id}:`, err.message);
643
653
  }
644
- } catch {
645
654
  }
655
+ } catch {
656
+ }
657
+ }
658
+ /** Compare two file contents (handles Uint8Array, ArrayBuffer, string, Buffer). */
659
+ bufferEqual(a, b) {
660
+ const ua = this.toUint8Array(a);
661
+ const ub = this.toUint8Array(b);
662
+ if (ua.length !== ub.length) return false;
663
+ for (let i = 0; i < ua.length; i++) {
664
+ if (ua[i] !== ub[i]) return false;
646
665
  }
666
+ return true;
667
+ }
668
+ toUint8Array(raw) {
669
+ if (raw instanceof ArrayBuffer) return new Uint8Array(raw);
670
+ if (raw instanceof Uint8Array) return raw;
671
+ if (typeof raw === "string") return new TextEncoder().encode(raw);
672
+ if (Buffer.isBuffer(raw)) return new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
673
+ return new Uint8Array(raw);
647
674
  }
648
675
  getSyncStatuses() {
649
676
  this.assertNotDisposed();
@@ -654,9 +681,9 @@ var ConfigRepo = class {
654
681
  // -----------------------------------------------------------------------
655
682
  async resolveConflict(conflictId, mergedContent) {
656
683
  this.assertNotDisposed();
657
- const archivePath = `${CONFLICTS_DIR}/${conflictId}`;
684
+ const metaPath = `${CONFLICTS_DIR}/${conflictId}`;
658
685
  try {
659
- const raw = await this.cachedFS.readFile(archivePath);
686
+ const raw = await this.cachedFS.readFile(metaPath);
660
687
  const archive = JSON.parse(
661
688
  new TextDecoder().decode(toUint8Array(raw))
662
689
  );
@@ -671,9 +698,13 @@ var ConfigRepo = class {
671
698
  author
672
699
  );
673
700
  await writeVersion(this.fullFS, versionPathFor(configPath), version);
674
- archive.resolvedContent = mergedContent;
701
+ const conflictDir = metaPath.substring(0, metaPath.lastIndexOf("/"));
702
+ const resolvedBackupPath = `${conflictDir}/resolved`;
703
+ const resolvedBytes = typeof mergedContent === "string" ? new TextEncoder().encode(mergedContent) : new TextEncoder().encode(JSON.stringify(mergedContent, null, 2));
704
+ await this.cachedFS.writeFile(resolvedBackupPath, resolvedBytes);
705
+ archive.resolvedBackupPath = `./resolved`;
675
706
  await this.cachedFS.writeFile(
676
- archivePath,
707
+ metaPath,
677
708
  new TextEncoder().encode(JSON.stringify(archive, null, 2))
678
709
  );
679
710
  } catch (err) {
@@ -686,9 +717,9 @@ var ConfigRepo = class {
686
717
  try {
687
718
  const entries = await this.cachedFS.readdir(CONFLICTS_DIR);
688
719
  for (const entry of entries) {
689
- if (!entry.endsWith(".json")) continue;
720
+ const metaPath = `${CONFLICTS_DIR}/${entry}/meta.json`;
690
721
  try {
691
- const raw = await this.cachedFS.readFile(`${CONFLICTS_DIR}/${entry}`);
722
+ const raw = await this.cachedFS.readFile(metaPath);
692
723
  const archive = JSON.parse(
693
724
  new TextDecoder().decode(toUint8Array(raw))
694
725
  );
@@ -700,6 +731,13 @@ var ConfigRepo = class {
700
731
  }
701
732
  return archives.sort((a, b) => a.timestamp - b.timestamp);
702
733
  }
734
+ async readConflictBackup(conflictId, fileType) {
735
+ this.assertNotDisposed();
736
+ const conflictDir = `${CONFLICTS_DIR}/${conflictId}`.replace(/\/meta\.json$/, "");
737
+ const filePath = `${conflictDir}/${fileType}`;
738
+ const raw = await this.cachedFS.readFile(filePath);
739
+ return new TextDecoder().decode(toUint8Array(raw));
740
+ }
703
741
  // -----------------------------------------------------------------------
704
742
  // IConfigRepo — Lifecycle
705
743
  // -----------------------------------------------------------------------
@@ -750,7 +788,7 @@ var ConfigRepo = class {
750
788
  );
751
789
  console.log(`[ConfigRepo] Sync pair added: pairId=${pair.pairId}, replica=${replicaId}, root=/`);
752
790
  const conflictHandler = (event) => {
753
- this.handleConflict(event, { prefix: "/", direction: "one-way" });
791
+ this.handleConflict(event);
754
792
  };
755
793
  this.syncEngine.on(pair.pairId, "conflict", conflictHandler);
756
794
  this.syncEngine.watch(pair.pairId);
@@ -786,45 +824,57 @@ var ConfigRepo = class {
786
824
  // -----------------------------------------------------------------------
787
825
  // Internal — Conflict Handling
788
826
  // -----------------------------------------------------------------------
789
- async handleConflict(event, _rule) {
827
+ async handleConflict(event) {
790
828
  const conflict = event.conflict;
791
829
  if (!conflict) return;
830
+ const conflictId = `${event.timestamp}_${conflict.path.replace(/\//g, "_")}`;
831
+ const conflictDir = `${CONFLICTS_DIR}/${conflictId}`;
832
+ const sourceBackupPath = `${conflictDir}/source`;
833
+ const targetBackupPath = `${conflictDir}/target`;
834
+ await this.ensureDir(conflictDir);
835
+ await this.cachedFS.writeFile(
836
+ sourceBackupPath,
837
+ new TextEncoder().encode(conflict.sourceContent)
838
+ );
839
+ await this.cachedFS.writeFile(
840
+ targetBackupPath,
841
+ new TextEncoder().encode(conflict.targetContent)
842
+ );
843
+ let sourceVersion = 0;
844
+ try {
845
+ const srcVer = await readVersion(this.fullFS, versionPathFor(conflict.path));
846
+ if (srcVer) sourceVersion = srcVer.version;
847
+ } catch {
848
+ }
792
849
  const archive = {
793
850
  conflictPath: conflict.path,
794
851
  timestamp: event.timestamp,
795
852
  sourceAuthor: `${this.appId}/${this.nodeId}`,
796
853
  targetAuthor: "unknown",
797
- sourceContent: this.tryParse(conflict.sourceContent),
798
- targetContent: this.tryParse(conflict.targetContent),
799
- sourceVersion: 0,
854
+ sourceVersion,
800
855
  targetVersion: 0,
801
- resolvedStrategy: conflict.resolvedWith
856
+ resolvedStrategy: conflict.resolvedWith,
857
+ sourceBackupPath: `./source`,
858
+ targetBackupPath: `./target`
802
859
  };
803
- try {
804
- const srcVer = await readVersion(this.fullFS, versionPathFor(conflict.path));
805
- if (srcVer) archive.sourceVersion = srcVer.version;
806
- } catch {
807
- }
808
- const archiveFileName = `${event.timestamp}_${conflict.path.replace(/\//g, "_")}.conflict.json`;
809
- const archivePath = `${CONFLICTS_DIR}/${archiveFileName}`;
810
- await this.ensureDir(archivePath);
860
+ const metaPath = `${conflictDir}/meta.json`;
811
861
  await this.cachedFS.writeFile(
812
- archivePath,
862
+ metaPath,
813
863
  new TextEncoder().encode(JSON.stringify(archive, null, 2))
814
864
  );
815
865
  if (this.onConflictCallback) {
816
866
  const info = {
817
- conflictId: archiveFileName,
867
+ conflictId: `${conflictId}/meta.json`,
818
868
  path: conflict.path,
819
869
  sourceAuthor: archive.sourceAuthor,
820
870
  targetAuthor: archive.targetAuthor,
821
- sourceContent: archive.sourceContent,
822
- targetContent: archive.targetContent
871
+ sourceContent: this.tryParse(conflict.sourceContent),
872
+ targetContent: this.tryParse(conflict.targetContent)
823
873
  };
824
874
  try {
825
875
  const customMerge = await this.onConflictCallback(info);
826
876
  if (customMerge !== null && customMerge !== void 0) {
827
- await this.resolveConflict(archiveFileName, customMerge);
877
+ await this.resolveConflict(`${conflictId}/meta.json`, customMerge);
828
878
  }
829
879
  } catch (err) {
830
880
  console.error("[zen-fs-config] Conflict handler error:", err);
@@ -909,14 +959,6 @@ var ConfigRepo = class {
909
959
  this.assertNotDisposed();
910
960
  await this.writeMetaFile(BACKENDS_FILE, meta);
911
961
  }
912
- async getSyncRules() {
913
- this.assertNotDisposed();
914
- return this.readMetaFile(SYNC_RULES_FILE);
915
- }
916
- async updateSyncRules(meta) {
917
- this.assertNotDisposed();
918
- await this.writeMetaFile(SYNC_RULES_FILE, meta);
919
- }
920
962
  tryParse(content) {
921
963
  try {
922
964
  return JSON.parse(content);
@@ -977,24 +1019,17 @@ async function createConfigRepo(appId, options) {
977
1019
  );
978
1020
  let backendsMeta = await tempRepo.readMetaFile(BACKENDS_FILE);
979
1021
  if (!backendsMeta) {
980
- if (options.bootstrap) {
981
- backendsMeta = {
982
- version: 1,
983
- backends: options.bootstrap.backends
984
- };
985
- console.log(`[createConfigRepo] First init: using bootstrap backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
986
- } else {
987
- backendsMeta = {
988
- version: 1,
989
- backends: [
990
- {
991
- id: options.primaryBackendId,
992
- type: options.backendInfo.type,
993
- options: options.backendInfo.options
994
- }
995
- ]
996
- };
997
- }
1022
+ backendsMeta = {
1023
+ version: 1,
1024
+ backends: [
1025
+ {
1026
+ id: options.primaryBackendId,
1027
+ type: options.backendInfo.type,
1028
+ options: options.backendInfo.options
1029
+ }
1030
+ ]
1031
+ };
1032
+ console.log(`[createConfigRepo] First init: using bootstrap backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
998
1033
  } else {
999
1034
  console.log(`[createConfigRepo] Reconnect: using stored backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
1000
1035
  }
@@ -1009,45 +1044,6 @@ async function createConfigRepo(appId, options) {
1009
1044
  });
1010
1045
  }
1011
1046
  await tempRepo.writeMetaFile(BACKENDS_FILE, backendsMeta);
1012
- let syncRulesMeta = await tempRepo.readMetaFile(SYNC_RULES_FILE);
1013
- if (!syncRulesMeta) {
1014
- if (options.bootstrap) {
1015
- syncRulesMeta = {
1016
- version: 1,
1017
- rules: options.bootstrap.syncRules
1018
- };
1019
- console.log(`[createConfigRepo] First init: using bootstrap syncRules: ${syncRulesMeta.rules.length} rules`);
1020
- } else {
1021
- syncRulesMeta = {
1022
- version: 1,
1023
- rules: [
1024
- {
1025
- prefix: `/${appId}/`,
1026
- direction: "one-way",
1027
- conflictStrategy: "source-wins",
1028
- replicas: backendsMeta.backends.map((b) => b.id)
1029
- },
1030
- {
1031
- prefix: `${SHARED_DIR}/`,
1032
- direction: "bi-directional",
1033
- conflictStrategy: "merge",
1034
- replicas: backendsMeta.backends.map((b) => b.id)
1035
- },
1036
- { prefix: `${NODES_DIR}/`, direction: "none" },
1037
- {
1038
- prefix: `${META_DIR}/`,
1039
- direction: "one-way",
1040
- conflictStrategy: "source-wins",
1041
- replicas: backendsMeta.backends.map((b) => b.id)
1042
- }
1043
- ]
1044
- };
1045
- }
1046
- }
1047
- if (syncRulesMeta) {
1048
- console.log(`[createConfigRepo] Reconnect: using stored syncRules: ${syncRulesMeta.rules.length} rules`);
1049
- }
1050
- await tempRepo.writeMetaFile(SYNC_RULES_FILE, syncRulesMeta);
1051
1047
  let nodeId = options.nodeId;
1052
1048
  if (!nodeId) {
1053
1049
  nodeId = process.env.NODE_ID;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-fs-config",
3
- "version": "0.3.21",
3
+ "version": "0.3.23",
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",