zen-fs-config 0.4.3 → 0.4.5
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 +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +33 -15
- package/dist/index.mjs +33 -15
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -125,6 +125,12 @@ interface ConfigRepoOptions {
|
|
|
125
125
|
serializer?: ConfigSerializer;
|
|
126
126
|
/** Custom conflict handler. Called before auto-resolution. */
|
|
127
127
|
onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>;
|
|
128
|
+
/**
|
|
129
|
+
* Sync polling interval in milliseconds.
|
|
130
|
+
* Controls how often the sync engine checks for remote changes.
|
|
131
|
+
* Default: 1800000 (30 minutes).
|
|
132
|
+
*/
|
|
133
|
+
syncPollIntervalMs?: number;
|
|
128
134
|
}
|
|
129
135
|
/** The main configuration repository interface. */
|
|
130
136
|
interface IConfigRepo {
|
|
@@ -389,7 +395,7 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
389
395
|
listConflicts(): Promise<ConflictArchive[]>;
|
|
390
396
|
readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
|
|
391
397
|
dispose(): Promise<void>;
|
|
392
|
-
setupSync(backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
|
|
398
|
+
setupSync(backends: BackendDescriptor[], primaryBackendId: string, pollIntervalMs?: number): Promise<void>;
|
|
393
399
|
private persistConfig;
|
|
394
400
|
private reloadConfigCache;
|
|
395
401
|
private handleConflict;
|
package/dist/index.d.ts
CHANGED
|
@@ -125,6 +125,12 @@ interface ConfigRepoOptions {
|
|
|
125
125
|
serializer?: ConfigSerializer;
|
|
126
126
|
/** Custom conflict handler. Called before auto-resolution. */
|
|
127
127
|
onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>;
|
|
128
|
+
/**
|
|
129
|
+
* Sync polling interval in milliseconds.
|
|
130
|
+
* Controls how often the sync engine checks for remote changes.
|
|
131
|
+
* Default: 1800000 (30 minutes).
|
|
132
|
+
*/
|
|
133
|
+
syncPollIntervalMs?: number;
|
|
128
134
|
}
|
|
129
135
|
/** The main configuration repository interface. */
|
|
130
136
|
interface IConfigRepo {
|
|
@@ -389,7 +395,7 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
389
395
|
listConflicts(): Promise<ConflictArchive[]>;
|
|
390
396
|
readConflictBackup(conflictId: string, fileType: 'source' | 'target' | 'resolved'): Promise<string>;
|
|
391
397
|
dispose(): Promise<void>;
|
|
392
|
-
setupSync(backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
|
|
398
|
+
setupSync(backends: BackendDescriptor[], primaryBackendId: string, pollIntervalMs?: number): Promise<void>;
|
|
393
399
|
private persistConfig;
|
|
394
400
|
private reloadConfigCache;
|
|
395
401
|
private handleConflict;
|
package/dist/index.js
CHANGED
|
@@ -263,6 +263,9 @@ function backendToSyncableFS(backend, name) {
|
|
|
263
263
|
return backend.exists(path);
|
|
264
264
|
}
|
|
265
265
|
};
|
|
266
|
+
if (typeof backend.checkForUpdates === "function") {
|
|
267
|
+
syncable.checkForUpdates = () => backend.checkForUpdates();
|
|
268
|
+
}
|
|
266
269
|
if (name) {
|
|
267
270
|
syncable.backendName = name;
|
|
268
271
|
} else if (backend.backendName) {
|
|
@@ -894,8 +897,8 @@ var ConfigRepo = class {
|
|
|
894
897
|
// -----------------------------------------------------------------------
|
|
895
898
|
// Internal — Setup
|
|
896
899
|
// -----------------------------------------------------------------------
|
|
897
|
-
async setupSync(backends, primaryBackendId) {
|
|
898
|
-
console.log(`[ConfigRepo] setupSync: ${backends.length} backends, primary=${primaryBackendId}`);
|
|
900
|
+
async setupSync(backends, primaryBackendId, pollIntervalMs) {
|
|
901
|
+
console.log(`[ConfigRepo] setupSync: ${backends.length} backends, primary=${primaryBackendId} pollInterval=${pollIntervalMs ?? "default"}ms`);
|
|
899
902
|
for (const desc of backends) {
|
|
900
903
|
if (desc.id === primaryBackendId) continue;
|
|
901
904
|
if (desc.enabled === false) {
|
|
@@ -911,7 +914,8 @@ var ConfigRepo = class {
|
|
|
911
914
|
syncable,
|
|
912
915
|
{
|
|
913
916
|
direction: import_zen_fs_sync.SyncDirection.BiDirectional,
|
|
914
|
-
conflictStrategy: "source-wins"
|
|
917
|
+
conflictStrategy: "source-wins",
|
|
918
|
+
pollIntervalMs
|
|
915
919
|
},
|
|
916
920
|
"/"
|
|
917
921
|
);
|
|
@@ -1089,27 +1093,39 @@ var ConfigRepo = class {
|
|
|
1089
1093
|
async readAllBackendDescriptors() {
|
|
1090
1094
|
try {
|
|
1091
1095
|
const entries = await this.cachedFS.readdir(BACKENDS_DIR);
|
|
1092
|
-
const
|
|
1096
|
+
const items = [];
|
|
1093
1097
|
for (const entry of entries) {
|
|
1094
1098
|
if (!entry.endsWith(".json")) continue;
|
|
1099
|
+
const filePath = `${BACKENDS_DIR}/${entry}`;
|
|
1095
1100
|
try {
|
|
1096
|
-
const raw = await this.cachedFS.readFile(
|
|
1101
|
+
const raw = await this.cachedFS.readFile(filePath);
|
|
1097
1102
|
const desc = JSON.parse(new TextDecoder().decode(toUint8Array(raw)));
|
|
1098
1103
|
if (desc.id && desc.type) {
|
|
1099
|
-
|
|
1104
|
+
let mtime = 0;
|
|
1105
|
+
try {
|
|
1106
|
+
const stat = await this.cachedFS.stat(filePath);
|
|
1107
|
+
mtime = stat.mtimeMs ?? 0;
|
|
1108
|
+
} catch {
|
|
1109
|
+
}
|
|
1110
|
+
items.push({ desc, mtime });
|
|
1100
1111
|
}
|
|
1101
1112
|
} catch {
|
|
1102
1113
|
}
|
|
1103
1114
|
}
|
|
1104
1115
|
const seen = /* @__PURE__ */ new Map();
|
|
1105
1116
|
const duplicates = [];
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
const
|
|
1109
|
-
if (
|
|
1110
|
-
|
|
1117
|
+
for (const item of items) {
|
|
1118
|
+
const key = `${item.desc.type}:${JSON.stringify(item.desc.options ?? {})}`;
|
|
1119
|
+
const existing = seen.get(key);
|
|
1120
|
+
if (existing) {
|
|
1121
|
+
if (item.mtime < existing.mtime) {
|
|
1122
|
+
duplicates.push(existing.desc.id);
|
|
1123
|
+
seen.set(key, item);
|
|
1124
|
+
} else {
|
|
1125
|
+
duplicates.push(item.desc.id);
|
|
1126
|
+
}
|
|
1111
1127
|
} else {
|
|
1112
|
-
seen.set(key,
|
|
1128
|
+
seen.set(key, item);
|
|
1113
1129
|
}
|
|
1114
1130
|
}
|
|
1115
1131
|
if (duplicates.length > 0) {
|
|
@@ -1120,7 +1136,7 @@ var ConfigRepo = class {
|
|
|
1120
1136
|
await this.removeBackendDescriptor(dupId);
|
|
1121
1137
|
}
|
|
1122
1138
|
}
|
|
1123
|
-
return
|
|
1139
|
+
return Array.from(seen.values()).map((i) => i.desc);
|
|
1124
1140
|
} catch {
|
|
1125
1141
|
return [];
|
|
1126
1142
|
}
|
|
@@ -1331,9 +1347,11 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
1331
1347
|
serializer,
|
|
1332
1348
|
options.onConflict
|
|
1333
1349
|
);
|
|
1334
|
-
await repo.setupSync(allBackends, LOCAL_IDB_BACKEND_ID);
|
|
1335
|
-
await repo.syncMetaToReplicas();
|
|
1350
|
+
await repo.setupSync(allBackends, LOCAL_IDB_BACKEND_ID, options.syncPollIntervalMs);
|
|
1336
1351
|
await repo.load();
|
|
1352
|
+
repo.syncMetaToReplicas().catch((err) => {
|
|
1353
|
+
console.error("[createConfigRepo] background syncMetaToReplicas failed:", err);
|
|
1354
|
+
});
|
|
1337
1355
|
return repo;
|
|
1338
1356
|
}
|
|
1339
1357
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.mjs
CHANGED
|
@@ -211,6 +211,9 @@ function backendToSyncableFS(backend, name) {
|
|
|
211
211
|
return backend.exists(path);
|
|
212
212
|
}
|
|
213
213
|
};
|
|
214
|
+
if (typeof backend.checkForUpdates === "function") {
|
|
215
|
+
syncable.checkForUpdates = () => backend.checkForUpdates();
|
|
216
|
+
}
|
|
214
217
|
if (name) {
|
|
215
218
|
syncable.backendName = name;
|
|
216
219
|
} else if (backend.backendName) {
|
|
@@ -842,8 +845,8 @@ var ConfigRepo = class {
|
|
|
842
845
|
// -----------------------------------------------------------------------
|
|
843
846
|
// Internal — Setup
|
|
844
847
|
// -----------------------------------------------------------------------
|
|
845
|
-
async setupSync(backends, primaryBackendId) {
|
|
846
|
-
console.log(`[ConfigRepo] setupSync: ${backends.length} backends, primary=${primaryBackendId}`);
|
|
848
|
+
async setupSync(backends, primaryBackendId, pollIntervalMs) {
|
|
849
|
+
console.log(`[ConfigRepo] setupSync: ${backends.length} backends, primary=${primaryBackendId} pollInterval=${pollIntervalMs ?? "default"}ms`);
|
|
847
850
|
for (const desc of backends) {
|
|
848
851
|
if (desc.id === primaryBackendId) continue;
|
|
849
852
|
if (desc.enabled === false) {
|
|
@@ -859,7 +862,8 @@ var ConfigRepo = class {
|
|
|
859
862
|
syncable,
|
|
860
863
|
{
|
|
861
864
|
direction: SyncDirection.BiDirectional,
|
|
862
|
-
conflictStrategy: "source-wins"
|
|
865
|
+
conflictStrategy: "source-wins",
|
|
866
|
+
pollIntervalMs
|
|
863
867
|
},
|
|
864
868
|
"/"
|
|
865
869
|
);
|
|
@@ -1037,27 +1041,39 @@ var ConfigRepo = class {
|
|
|
1037
1041
|
async readAllBackendDescriptors() {
|
|
1038
1042
|
try {
|
|
1039
1043
|
const entries = await this.cachedFS.readdir(BACKENDS_DIR);
|
|
1040
|
-
const
|
|
1044
|
+
const items = [];
|
|
1041
1045
|
for (const entry of entries) {
|
|
1042
1046
|
if (!entry.endsWith(".json")) continue;
|
|
1047
|
+
const filePath = `${BACKENDS_DIR}/${entry}`;
|
|
1043
1048
|
try {
|
|
1044
|
-
const raw = await this.cachedFS.readFile(
|
|
1049
|
+
const raw = await this.cachedFS.readFile(filePath);
|
|
1045
1050
|
const desc = JSON.parse(new TextDecoder().decode(toUint8Array(raw)));
|
|
1046
1051
|
if (desc.id && desc.type) {
|
|
1047
|
-
|
|
1052
|
+
let mtime = 0;
|
|
1053
|
+
try {
|
|
1054
|
+
const stat = await this.cachedFS.stat(filePath);
|
|
1055
|
+
mtime = stat.mtimeMs ?? 0;
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
items.push({ desc, mtime });
|
|
1048
1059
|
}
|
|
1049
1060
|
} catch {
|
|
1050
1061
|
}
|
|
1051
1062
|
}
|
|
1052
1063
|
const seen = /* @__PURE__ */ new Map();
|
|
1053
1064
|
const duplicates = [];
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
const
|
|
1057
|
-
if (
|
|
1058
|
-
|
|
1065
|
+
for (const item of items) {
|
|
1066
|
+
const key = `${item.desc.type}:${JSON.stringify(item.desc.options ?? {})}`;
|
|
1067
|
+
const existing = seen.get(key);
|
|
1068
|
+
if (existing) {
|
|
1069
|
+
if (item.mtime < existing.mtime) {
|
|
1070
|
+
duplicates.push(existing.desc.id);
|
|
1071
|
+
seen.set(key, item);
|
|
1072
|
+
} else {
|
|
1073
|
+
duplicates.push(item.desc.id);
|
|
1074
|
+
}
|
|
1059
1075
|
} else {
|
|
1060
|
-
seen.set(key,
|
|
1076
|
+
seen.set(key, item);
|
|
1061
1077
|
}
|
|
1062
1078
|
}
|
|
1063
1079
|
if (duplicates.length > 0) {
|
|
@@ -1068,7 +1084,7 @@ var ConfigRepo = class {
|
|
|
1068
1084
|
await this.removeBackendDescriptor(dupId);
|
|
1069
1085
|
}
|
|
1070
1086
|
}
|
|
1071
|
-
return
|
|
1087
|
+
return Array.from(seen.values()).map((i) => i.desc);
|
|
1072
1088
|
} catch {
|
|
1073
1089
|
return [];
|
|
1074
1090
|
}
|
|
@@ -1279,9 +1295,11 @@ async function createConfigRepo(appId, options = {}) {
|
|
|
1279
1295
|
serializer,
|
|
1280
1296
|
options.onConflict
|
|
1281
1297
|
);
|
|
1282
|
-
await repo.setupSync(allBackends, LOCAL_IDB_BACKEND_ID);
|
|
1283
|
-
await repo.syncMetaToReplicas();
|
|
1298
|
+
await repo.setupSync(allBackends, LOCAL_IDB_BACKEND_ID, options.syncPollIntervalMs);
|
|
1284
1299
|
await repo.load();
|
|
1300
|
+
repo.syncMetaToReplicas().catch((err) => {
|
|
1301
|
+
console.error("[createConfigRepo] background syncMetaToReplicas failed:", err);
|
|
1302
|
+
});
|
|
1285
1303
|
return repo;
|
|
1286
1304
|
}
|
|
1287
1305
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zen-fs-config",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
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",
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
62
|
"vitest": "^1.6.1",
|
|
63
63
|
"zen-fs-cache": "^1.0.1",
|
|
64
|
-
"zen-fs-sync": "^0.4.
|
|
64
|
+
"zen-fs-sync": "^0.4.3"
|
|
65
65
|
}
|
|
66
66
|
}
|