zen-fs-config 0.3.21 → 0.3.22
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 +17 -43
- package/dist/index.d.ts +17 -43
- package/dist/index.js +72 -114
- package/dist/index.mjs +72 -114
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
/**
|
|
71
|
-
|
|
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
|
|
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,18 @@ 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
|
|
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>;
|
|
272
247
|
getSyncStatuses(): Map<string, SyncPairStatus>;
|
|
273
248
|
resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
|
|
274
249
|
listConflicts(): Promise<ConflictArchive[]>;
|
|
250
|
+
readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
|
|
275
251
|
dispose(): Promise<void>;
|
|
276
252
|
setupSync(backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
|
|
277
253
|
private persistConfig;
|
|
@@ -279,12 +255,10 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
279
255
|
private handleConflict;
|
|
280
256
|
private ensureDir;
|
|
281
257
|
private walkDir;
|
|
282
|
-
writeMetaFile(path: string, data: BackendsMeta
|
|
258
|
+
writeMetaFile(path: string, data: BackendsMeta): Promise<void>;
|
|
283
259
|
readMetaFile<T>(path: string): Promise<T | null>;
|
|
284
260
|
getBackends(): Promise<BackendsMeta | null>;
|
|
285
261
|
updateBackends(meta: BackendsMeta): Promise<void>;
|
|
286
|
-
getSyncRules(): Promise<SyncRulesMeta | null>;
|
|
287
|
-
updateSyncRules(meta: SyncRulesMeta): Promise<void>;
|
|
288
262
|
private tryParse;
|
|
289
263
|
private assertNotDisposed;
|
|
290
264
|
}
|
|
@@ -374,4 +348,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
374
348
|
*/
|
|
375
349
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
376
350
|
|
|
377
|
-
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type
|
|
351
|
+
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 {
|
|
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
|
-
/**
|
|
71
|
-
|
|
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
|
|
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,18 @@ 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
|
|
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>;
|
|
272
247
|
getSyncStatuses(): Map<string, SyncPairStatus>;
|
|
273
248
|
resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
|
|
274
249
|
listConflicts(): Promise<ConflictArchive[]>;
|
|
250
|
+
readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
|
|
275
251
|
dispose(): Promise<void>;
|
|
276
252
|
setupSync(backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
|
|
277
253
|
private persistConfig;
|
|
@@ -279,12 +255,10 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
279
255
|
private handleConflict;
|
|
280
256
|
private ensureDir;
|
|
281
257
|
private walkDir;
|
|
282
|
-
writeMetaFile(path: string, data: BackendsMeta
|
|
258
|
+
writeMetaFile(path: string, data: BackendsMeta): Promise<void>;
|
|
283
259
|
readMetaFile<T>(path: string): Promise<T | null>;
|
|
284
260
|
getBackends(): Promise<BackendsMeta | null>;
|
|
285
261
|
updateBackends(meta: BackendsMeta): Promise<void>;
|
|
286
|
-
getSyncRules(): Promise<SyncRulesMeta | null>;
|
|
287
|
-
updateSyncRules(meta: SyncRulesMeta): Promise<void>;
|
|
288
262
|
private tryParse;
|
|
289
263
|
private assertNotDisposed;
|
|
290
264
|
}
|
|
@@ -374,4 +348,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
374
348
|
*/
|
|
375
349
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
376
350
|
|
|
377
|
-
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type
|
|
351
|
+
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,32 +658,29 @@ var ConfigRepo = class {
|
|
|
666
658
|
return Array.from(resultsMap.values());
|
|
667
659
|
}
|
|
668
660
|
/**
|
|
669
|
-
* Sync .meta/ files (backends.json
|
|
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
console.error(`[ConfigRepo] Failed to sync ${metaFile} to ${id}:`, err.message);
|
|
691
|
-
}
|
|
670
|
+
try {
|
|
671
|
+
const content = await this.cachedFS.readFile(BACKENDS_FILE);
|
|
672
|
+
const vPath = versionPathFor(BACKENDS_FILE);
|
|
673
|
+
const vContent = await this.cachedFS.readFile(vPath);
|
|
674
|
+
for (const [id, replica] of this.replicaBackends) {
|
|
675
|
+
try {
|
|
676
|
+
await replica.syncable.writeFile(BACKENDS_FILE, content);
|
|
677
|
+
await replica.syncable.writeFile(vPath, vContent);
|
|
678
|
+
console.log(`[ConfigRepo] Synced ${BACKENDS_FILE} + .version to replica ${id}`);
|
|
679
|
+
} catch (err) {
|
|
680
|
+
console.error(`[ConfigRepo] Failed to sync ${BACKENDS_FILE} to ${id}:`, err.message);
|
|
692
681
|
}
|
|
693
|
-
} catch {
|
|
694
682
|
}
|
|
683
|
+
} catch {
|
|
695
684
|
}
|
|
696
685
|
}
|
|
697
686
|
getSyncStatuses() {
|
|
@@ -703,9 +692,9 @@ var ConfigRepo = class {
|
|
|
703
692
|
// -----------------------------------------------------------------------
|
|
704
693
|
async resolveConflict(conflictId, mergedContent) {
|
|
705
694
|
this.assertNotDisposed();
|
|
706
|
-
const
|
|
695
|
+
const metaPath = `${CONFLICTS_DIR}/${conflictId}`;
|
|
707
696
|
try {
|
|
708
|
-
const raw = await this.cachedFS.readFile(
|
|
697
|
+
const raw = await this.cachedFS.readFile(metaPath);
|
|
709
698
|
const archive = JSON.parse(
|
|
710
699
|
new TextDecoder().decode(toUint8Array(raw))
|
|
711
700
|
);
|
|
@@ -720,9 +709,13 @@ var ConfigRepo = class {
|
|
|
720
709
|
author
|
|
721
710
|
);
|
|
722
711
|
await writeVersion(this.fullFS, versionPathFor(configPath), version);
|
|
723
|
-
|
|
712
|
+
const conflictDir = metaPath.substring(0, metaPath.lastIndexOf("/"));
|
|
713
|
+
const resolvedBackupPath = `${conflictDir}/resolved`;
|
|
714
|
+
const resolvedBytes = typeof mergedContent === "string" ? new TextEncoder().encode(mergedContent) : new TextEncoder().encode(JSON.stringify(mergedContent, null, 2));
|
|
715
|
+
await this.cachedFS.writeFile(resolvedBackupPath, resolvedBytes);
|
|
716
|
+
archive.resolvedBackupPath = `./resolved`;
|
|
724
717
|
await this.cachedFS.writeFile(
|
|
725
|
-
|
|
718
|
+
metaPath,
|
|
726
719
|
new TextEncoder().encode(JSON.stringify(archive, null, 2))
|
|
727
720
|
);
|
|
728
721
|
} catch (err) {
|
|
@@ -735,9 +728,9 @@ var ConfigRepo = class {
|
|
|
735
728
|
try {
|
|
736
729
|
const entries = await this.cachedFS.readdir(CONFLICTS_DIR);
|
|
737
730
|
for (const entry of entries) {
|
|
738
|
-
|
|
731
|
+
const metaPath = `${CONFLICTS_DIR}/${entry}/meta.json`;
|
|
739
732
|
try {
|
|
740
|
-
const raw = await this.cachedFS.readFile(
|
|
733
|
+
const raw = await this.cachedFS.readFile(metaPath);
|
|
741
734
|
const archive = JSON.parse(
|
|
742
735
|
new TextDecoder().decode(toUint8Array(raw))
|
|
743
736
|
);
|
|
@@ -749,6 +742,13 @@ var ConfigRepo = class {
|
|
|
749
742
|
}
|
|
750
743
|
return archives.sort((a, b) => a.timestamp - b.timestamp);
|
|
751
744
|
}
|
|
745
|
+
async readConflictBackup(conflictId, fileType) {
|
|
746
|
+
this.assertNotDisposed();
|
|
747
|
+
const conflictDir = `${CONFLICTS_DIR}/${conflictId}`.replace(/\/meta\.json$/, "");
|
|
748
|
+
const filePath = `${conflictDir}/${fileType}`;
|
|
749
|
+
const raw = await this.cachedFS.readFile(filePath);
|
|
750
|
+
return new TextDecoder().decode(toUint8Array(raw));
|
|
751
|
+
}
|
|
752
752
|
// -----------------------------------------------------------------------
|
|
753
753
|
// IConfigRepo — Lifecycle
|
|
754
754
|
// -----------------------------------------------------------------------
|
|
@@ -799,7 +799,7 @@ var ConfigRepo = class {
|
|
|
799
799
|
);
|
|
800
800
|
console.log(`[ConfigRepo] Sync pair added: pairId=${pair.pairId}, replica=${replicaId}, root=/`);
|
|
801
801
|
const conflictHandler = (event) => {
|
|
802
|
-
this.handleConflict(event
|
|
802
|
+
this.handleConflict(event);
|
|
803
803
|
};
|
|
804
804
|
this.syncEngine.on(pair.pairId, "conflict", conflictHandler);
|
|
805
805
|
this.syncEngine.watch(pair.pairId);
|
|
@@ -835,45 +835,57 @@ var ConfigRepo = class {
|
|
|
835
835
|
// -----------------------------------------------------------------------
|
|
836
836
|
// Internal — Conflict Handling
|
|
837
837
|
// -----------------------------------------------------------------------
|
|
838
|
-
async handleConflict(event
|
|
838
|
+
async handleConflict(event) {
|
|
839
839
|
const conflict = event.conflict;
|
|
840
840
|
if (!conflict) return;
|
|
841
|
+
const conflictId = `${event.timestamp}_${conflict.path.replace(/\//g, "_")}`;
|
|
842
|
+
const conflictDir = `${CONFLICTS_DIR}/${conflictId}`;
|
|
843
|
+
const sourceBackupPath = `${conflictDir}/source`;
|
|
844
|
+
const targetBackupPath = `${conflictDir}/target`;
|
|
845
|
+
await this.ensureDir(conflictDir);
|
|
846
|
+
await this.cachedFS.writeFile(
|
|
847
|
+
sourceBackupPath,
|
|
848
|
+
new TextEncoder().encode(conflict.sourceContent)
|
|
849
|
+
);
|
|
850
|
+
await this.cachedFS.writeFile(
|
|
851
|
+
targetBackupPath,
|
|
852
|
+
new TextEncoder().encode(conflict.targetContent)
|
|
853
|
+
);
|
|
854
|
+
let sourceVersion = 0;
|
|
855
|
+
try {
|
|
856
|
+
const srcVer = await readVersion(this.fullFS, versionPathFor(conflict.path));
|
|
857
|
+
if (srcVer) sourceVersion = srcVer.version;
|
|
858
|
+
} catch {
|
|
859
|
+
}
|
|
841
860
|
const archive = {
|
|
842
861
|
conflictPath: conflict.path,
|
|
843
862
|
timestamp: event.timestamp,
|
|
844
863
|
sourceAuthor: `${this.appId}/${this.nodeId}`,
|
|
845
864
|
targetAuthor: "unknown",
|
|
846
|
-
|
|
847
|
-
targetContent: this.tryParse(conflict.targetContent),
|
|
848
|
-
sourceVersion: 0,
|
|
865
|
+
sourceVersion,
|
|
849
866
|
targetVersion: 0,
|
|
850
|
-
resolvedStrategy: conflict.resolvedWith
|
|
867
|
+
resolvedStrategy: conflict.resolvedWith,
|
|
868
|
+
sourceBackupPath: `./source`,
|
|
869
|
+
targetBackupPath: `./target`
|
|
851
870
|
};
|
|
852
|
-
|
|
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);
|
|
871
|
+
const metaPath = `${conflictDir}/meta.json`;
|
|
860
872
|
await this.cachedFS.writeFile(
|
|
861
|
-
|
|
873
|
+
metaPath,
|
|
862
874
|
new TextEncoder().encode(JSON.stringify(archive, null, 2))
|
|
863
875
|
);
|
|
864
876
|
if (this.onConflictCallback) {
|
|
865
877
|
const info = {
|
|
866
|
-
conflictId:
|
|
878
|
+
conflictId: `${conflictId}/meta.json`,
|
|
867
879
|
path: conflict.path,
|
|
868
880
|
sourceAuthor: archive.sourceAuthor,
|
|
869
881
|
targetAuthor: archive.targetAuthor,
|
|
870
|
-
sourceContent:
|
|
871
|
-
targetContent:
|
|
882
|
+
sourceContent: this.tryParse(conflict.sourceContent),
|
|
883
|
+
targetContent: this.tryParse(conflict.targetContent)
|
|
872
884
|
};
|
|
873
885
|
try {
|
|
874
886
|
const customMerge = await this.onConflictCallback(info);
|
|
875
887
|
if (customMerge !== null && customMerge !== void 0) {
|
|
876
|
-
await this.resolveConflict(
|
|
888
|
+
await this.resolveConflict(`${conflictId}/meta.json`, customMerge);
|
|
877
889
|
}
|
|
878
890
|
} catch (err) {
|
|
879
891
|
console.error("[zen-fs-config] Conflict handler error:", err);
|
|
@@ -958,14 +970,6 @@ var ConfigRepo = class {
|
|
|
958
970
|
this.assertNotDisposed();
|
|
959
971
|
await this.writeMetaFile(BACKENDS_FILE, meta);
|
|
960
972
|
}
|
|
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
973
|
tryParse(content) {
|
|
970
974
|
try {
|
|
971
975
|
return JSON.parse(content);
|
|
@@ -1026,24 +1030,17 @@ async function createConfigRepo(appId, options) {
|
|
|
1026
1030
|
);
|
|
1027
1031
|
let backendsMeta = await tempRepo.readMetaFile(BACKENDS_FILE);
|
|
1028
1032
|
if (!backendsMeta) {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
id: options.primaryBackendId,
|
|
1041
|
-
type: options.backendInfo.type,
|
|
1042
|
-
options: options.backendInfo.options
|
|
1043
|
-
}
|
|
1044
|
-
]
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1033
|
+
backendsMeta = {
|
|
1034
|
+
version: 1,
|
|
1035
|
+
backends: [
|
|
1036
|
+
{
|
|
1037
|
+
id: options.primaryBackendId,
|
|
1038
|
+
type: options.backendInfo.type,
|
|
1039
|
+
options: options.backendInfo.options
|
|
1040
|
+
}
|
|
1041
|
+
]
|
|
1042
|
+
};
|
|
1043
|
+
console.log(`[createConfigRepo] First init: using bootstrap backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
|
|
1047
1044
|
} else {
|
|
1048
1045
|
console.log(`[createConfigRepo] Reconnect: using stored backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
|
|
1049
1046
|
}
|
|
@@ -1058,45 +1055,6 @@ async function createConfigRepo(appId, options) {
|
|
|
1058
1055
|
});
|
|
1059
1056
|
}
|
|
1060
1057
|
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
1058
|
let nodeId = options.nodeId;
|
|
1101
1059
|
if (!nodeId) {
|
|
1102
1060
|
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,32 +609,29 @@ var ConfigRepo = class {
|
|
|
617
609
|
return Array.from(resultsMap.values());
|
|
618
610
|
}
|
|
619
611
|
/**
|
|
620
|
-
* Sync .meta/ files (backends.json
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
console.error(`[ConfigRepo] Failed to sync ${metaFile} to ${id}:`, err.message);
|
|
642
|
-
}
|
|
621
|
+
try {
|
|
622
|
+
const content = await this.cachedFS.readFile(BACKENDS_FILE);
|
|
623
|
+
const vPath = versionPathFor(BACKENDS_FILE);
|
|
624
|
+
const vContent = await this.cachedFS.readFile(vPath);
|
|
625
|
+
for (const [id, replica] of this.replicaBackends) {
|
|
626
|
+
try {
|
|
627
|
+
await replica.syncable.writeFile(BACKENDS_FILE, content);
|
|
628
|
+
await replica.syncable.writeFile(vPath, vContent);
|
|
629
|
+
console.log(`[ConfigRepo] Synced ${BACKENDS_FILE} + .version to replica ${id}`);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
console.error(`[ConfigRepo] Failed to sync ${BACKENDS_FILE} to ${id}:`, err.message);
|
|
643
632
|
}
|
|
644
|
-
} catch {
|
|
645
633
|
}
|
|
634
|
+
} catch {
|
|
646
635
|
}
|
|
647
636
|
}
|
|
648
637
|
getSyncStatuses() {
|
|
@@ -654,9 +643,9 @@ var ConfigRepo = class {
|
|
|
654
643
|
// -----------------------------------------------------------------------
|
|
655
644
|
async resolveConflict(conflictId, mergedContent) {
|
|
656
645
|
this.assertNotDisposed();
|
|
657
|
-
const
|
|
646
|
+
const metaPath = `${CONFLICTS_DIR}/${conflictId}`;
|
|
658
647
|
try {
|
|
659
|
-
const raw = await this.cachedFS.readFile(
|
|
648
|
+
const raw = await this.cachedFS.readFile(metaPath);
|
|
660
649
|
const archive = JSON.parse(
|
|
661
650
|
new TextDecoder().decode(toUint8Array(raw))
|
|
662
651
|
);
|
|
@@ -671,9 +660,13 @@ var ConfigRepo = class {
|
|
|
671
660
|
author
|
|
672
661
|
);
|
|
673
662
|
await writeVersion(this.fullFS, versionPathFor(configPath), version);
|
|
674
|
-
|
|
663
|
+
const conflictDir = metaPath.substring(0, metaPath.lastIndexOf("/"));
|
|
664
|
+
const resolvedBackupPath = `${conflictDir}/resolved`;
|
|
665
|
+
const resolvedBytes = typeof mergedContent === "string" ? new TextEncoder().encode(mergedContent) : new TextEncoder().encode(JSON.stringify(mergedContent, null, 2));
|
|
666
|
+
await this.cachedFS.writeFile(resolvedBackupPath, resolvedBytes);
|
|
667
|
+
archive.resolvedBackupPath = `./resolved`;
|
|
675
668
|
await this.cachedFS.writeFile(
|
|
676
|
-
|
|
669
|
+
metaPath,
|
|
677
670
|
new TextEncoder().encode(JSON.stringify(archive, null, 2))
|
|
678
671
|
);
|
|
679
672
|
} catch (err) {
|
|
@@ -686,9 +679,9 @@ var ConfigRepo = class {
|
|
|
686
679
|
try {
|
|
687
680
|
const entries = await this.cachedFS.readdir(CONFLICTS_DIR);
|
|
688
681
|
for (const entry of entries) {
|
|
689
|
-
|
|
682
|
+
const metaPath = `${CONFLICTS_DIR}/${entry}/meta.json`;
|
|
690
683
|
try {
|
|
691
|
-
const raw = await this.cachedFS.readFile(
|
|
684
|
+
const raw = await this.cachedFS.readFile(metaPath);
|
|
692
685
|
const archive = JSON.parse(
|
|
693
686
|
new TextDecoder().decode(toUint8Array(raw))
|
|
694
687
|
);
|
|
@@ -700,6 +693,13 @@ var ConfigRepo = class {
|
|
|
700
693
|
}
|
|
701
694
|
return archives.sort((a, b) => a.timestamp - b.timestamp);
|
|
702
695
|
}
|
|
696
|
+
async readConflictBackup(conflictId, fileType) {
|
|
697
|
+
this.assertNotDisposed();
|
|
698
|
+
const conflictDir = `${CONFLICTS_DIR}/${conflictId}`.replace(/\/meta\.json$/, "");
|
|
699
|
+
const filePath = `${conflictDir}/${fileType}`;
|
|
700
|
+
const raw = await this.cachedFS.readFile(filePath);
|
|
701
|
+
return new TextDecoder().decode(toUint8Array(raw));
|
|
702
|
+
}
|
|
703
703
|
// -----------------------------------------------------------------------
|
|
704
704
|
// IConfigRepo — Lifecycle
|
|
705
705
|
// -----------------------------------------------------------------------
|
|
@@ -750,7 +750,7 @@ var ConfigRepo = class {
|
|
|
750
750
|
);
|
|
751
751
|
console.log(`[ConfigRepo] Sync pair added: pairId=${pair.pairId}, replica=${replicaId}, root=/`);
|
|
752
752
|
const conflictHandler = (event) => {
|
|
753
|
-
this.handleConflict(event
|
|
753
|
+
this.handleConflict(event);
|
|
754
754
|
};
|
|
755
755
|
this.syncEngine.on(pair.pairId, "conflict", conflictHandler);
|
|
756
756
|
this.syncEngine.watch(pair.pairId);
|
|
@@ -786,45 +786,57 @@ var ConfigRepo = class {
|
|
|
786
786
|
// -----------------------------------------------------------------------
|
|
787
787
|
// Internal — Conflict Handling
|
|
788
788
|
// -----------------------------------------------------------------------
|
|
789
|
-
async handleConflict(event
|
|
789
|
+
async handleConflict(event) {
|
|
790
790
|
const conflict = event.conflict;
|
|
791
791
|
if (!conflict) return;
|
|
792
|
+
const conflictId = `${event.timestamp}_${conflict.path.replace(/\//g, "_")}`;
|
|
793
|
+
const conflictDir = `${CONFLICTS_DIR}/${conflictId}`;
|
|
794
|
+
const sourceBackupPath = `${conflictDir}/source`;
|
|
795
|
+
const targetBackupPath = `${conflictDir}/target`;
|
|
796
|
+
await this.ensureDir(conflictDir);
|
|
797
|
+
await this.cachedFS.writeFile(
|
|
798
|
+
sourceBackupPath,
|
|
799
|
+
new TextEncoder().encode(conflict.sourceContent)
|
|
800
|
+
);
|
|
801
|
+
await this.cachedFS.writeFile(
|
|
802
|
+
targetBackupPath,
|
|
803
|
+
new TextEncoder().encode(conflict.targetContent)
|
|
804
|
+
);
|
|
805
|
+
let sourceVersion = 0;
|
|
806
|
+
try {
|
|
807
|
+
const srcVer = await readVersion(this.fullFS, versionPathFor(conflict.path));
|
|
808
|
+
if (srcVer) sourceVersion = srcVer.version;
|
|
809
|
+
} catch {
|
|
810
|
+
}
|
|
792
811
|
const archive = {
|
|
793
812
|
conflictPath: conflict.path,
|
|
794
813
|
timestamp: event.timestamp,
|
|
795
814
|
sourceAuthor: `${this.appId}/${this.nodeId}`,
|
|
796
815
|
targetAuthor: "unknown",
|
|
797
|
-
|
|
798
|
-
targetContent: this.tryParse(conflict.targetContent),
|
|
799
|
-
sourceVersion: 0,
|
|
816
|
+
sourceVersion,
|
|
800
817
|
targetVersion: 0,
|
|
801
|
-
resolvedStrategy: conflict.resolvedWith
|
|
818
|
+
resolvedStrategy: conflict.resolvedWith,
|
|
819
|
+
sourceBackupPath: `./source`,
|
|
820
|
+
targetBackupPath: `./target`
|
|
802
821
|
};
|
|
803
|
-
|
|
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);
|
|
822
|
+
const metaPath = `${conflictDir}/meta.json`;
|
|
811
823
|
await this.cachedFS.writeFile(
|
|
812
|
-
|
|
824
|
+
metaPath,
|
|
813
825
|
new TextEncoder().encode(JSON.stringify(archive, null, 2))
|
|
814
826
|
);
|
|
815
827
|
if (this.onConflictCallback) {
|
|
816
828
|
const info = {
|
|
817
|
-
conflictId:
|
|
829
|
+
conflictId: `${conflictId}/meta.json`,
|
|
818
830
|
path: conflict.path,
|
|
819
831
|
sourceAuthor: archive.sourceAuthor,
|
|
820
832
|
targetAuthor: archive.targetAuthor,
|
|
821
|
-
sourceContent:
|
|
822
|
-
targetContent:
|
|
833
|
+
sourceContent: this.tryParse(conflict.sourceContent),
|
|
834
|
+
targetContent: this.tryParse(conflict.targetContent)
|
|
823
835
|
};
|
|
824
836
|
try {
|
|
825
837
|
const customMerge = await this.onConflictCallback(info);
|
|
826
838
|
if (customMerge !== null && customMerge !== void 0) {
|
|
827
|
-
await this.resolveConflict(
|
|
839
|
+
await this.resolveConflict(`${conflictId}/meta.json`, customMerge);
|
|
828
840
|
}
|
|
829
841
|
} catch (err) {
|
|
830
842
|
console.error("[zen-fs-config] Conflict handler error:", err);
|
|
@@ -909,14 +921,6 @@ var ConfigRepo = class {
|
|
|
909
921
|
this.assertNotDisposed();
|
|
910
922
|
await this.writeMetaFile(BACKENDS_FILE, meta);
|
|
911
923
|
}
|
|
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
924
|
tryParse(content) {
|
|
921
925
|
try {
|
|
922
926
|
return JSON.parse(content);
|
|
@@ -977,24 +981,17 @@ async function createConfigRepo(appId, options) {
|
|
|
977
981
|
);
|
|
978
982
|
let backendsMeta = await tempRepo.readMetaFile(BACKENDS_FILE);
|
|
979
983
|
if (!backendsMeta) {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
id: options.primaryBackendId,
|
|
992
|
-
type: options.backendInfo.type,
|
|
993
|
-
options: options.backendInfo.options
|
|
994
|
-
}
|
|
995
|
-
]
|
|
996
|
-
};
|
|
997
|
-
}
|
|
984
|
+
backendsMeta = {
|
|
985
|
+
version: 1,
|
|
986
|
+
backends: [
|
|
987
|
+
{
|
|
988
|
+
id: options.primaryBackendId,
|
|
989
|
+
type: options.backendInfo.type,
|
|
990
|
+
options: options.backendInfo.options
|
|
991
|
+
}
|
|
992
|
+
]
|
|
993
|
+
};
|
|
994
|
+
console.log(`[createConfigRepo] First init: using bootstrap backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
|
|
998
995
|
} else {
|
|
999
996
|
console.log(`[createConfigRepo] Reconnect: using stored backends: ${backendsMeta.backends.map((b) => b.id).join(", ")}`);
|
|
1000
997
|
}
|
|
@@ -1009,45 +1006,6 @@ async function createConfigRepo(appId, options) {
|
|
|
1009
1006
|
});
|
|
1010
1007
|
}
|
|
1011
1008
|
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
1009
|
let nodeId = options.nodeId;
|
|
1052
1010
|
if (!nodeId) {
|
|
1053
1011
|
nodeId = process.env.NODE_ID;
|