zen-fs-config 0.5.7 → 0.5.9

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.
@@ -0,0 +1,22 @@
1
+ // src/cache-wrapper.ts
2
+ import { CachedFileSystem, IdbCacheStore, MemoryCacheStore } from "zen-fs-cache";
3
+ function wrapWithCache(backend, backendId, options) {
4
+ const storeType = options.storeType ?? "IdbCacheStore";
5
+ const prefix = options.storePrefix ?? `zen-fs-config:${backendId}:`;
6
+ let store;
7
+ if (storeType === "IdbCacheStore") {
8
+ store = new IdbCacheStore(prefix);
9
+ } else {
10
+ store = new MemoryCacheStore();
11
+ }
12
+ const wrapped = new CachedFileSystem(backend, store, {
13
+ ttlMs: options.ttlMs ?? 0
14
+ });
15
+ if (typeof backend.shouldSync === "function") {
16
+ wrapped.shouldSync = (...args) => backend.shouldSync(...args);
17
+ }
18
+ return wrapped;
19
+ }
20
+ export {
21
+ wrapWithCache
22
+ };
package/dist/index.d.mts CHANGED
@@ -89,11 +89,20 @@ interface ConfigSerializer {
89
89
  }
90
90
  /** Cache configuration. */
91
91
  interface CacheOptions {
92
- /** Type of cache store. */
92
+ /**
93
+ * Type of cache store for replica backends.
94
+ * Default: `'IdbCacheStore'` — persists across page reloads so cached
95
+ * content + revision tokens survive. Use `'MemoryCacheStore'` for
96
+ * session-only caching (lost on reload).
97
+ */
93
98
  storeType?: 'MemoryCacheStore' | 'IdbCacheStore';
94
- /** Cache store prefix (for IdbCacheStore). */
99
+ /** Cache store prefix (for IdbCacheStore). Default: `'zen-fs-config:'`. */
95
100
  storePrefix?: string;
96
- /** TTL in milliseconds for cache hits without revalidation. Default: 0 (always revalidate). */
101
+ /**
102
+ * TTL in milliseconds for cache hits without revalidation.
103
+ * Default: 0 (always revalidate via `getRevision` — exact, no stale data).
104
+ * Ignored when the backend implements `getRevision`.
105
+ */
97
106
  ttlMs?: number;
98
107
  }
99
108
  /** Options for creating a ConfigRepo. */
@@ -119,8 +128,19 @@ interface ConfigRepoOptions {
119
128
  idbStoreName?: string;
120
129
  /** Node identifier. Auto-detected if not provided (see DESIGN.md §8.2). */
121
130
  nodeId?: string;
122
- /** Cache configuration. */
123
- cache?: CacheOptions;
131
+ /**
132
+ * Cache configuration for replica backends.
133
+ *
134
+ * - **Default** (omitted): Caching is enabled with `IdbCacheStore`
135
+ * (persists across page reloads). Replica backends (Gitee,
136
+ * RemoteStorage, etc.) are wrapped with `CachedFileSystem` to avoid
137
+ * redundant network reads via `getRevision` revalidation.
138
+ * - Pass a `CacheOptions` object to customize the store type, prefix,
139
+ * or TTL.
140
+ * - Pass `false` to disable caching entirely (all reads go directly
141
+ * to the backend).
142
+ */
143
+ cache?: CacheOptions | false;
124
144
  /** Custom serializer. */
125
145
  serializer?: ConfigSerializer;
126
146
  /** Custom conflict handler. Called before auto-resolution. */
@@ -461,10 +481,11 @@ declare class ConfigRepo implements IConfigRepo {
461
481
  private disposed;
462
482
  private configCache;
463
483
  private readonly primaryBackendId;
484
+ private readonly cacheOptions?;
464
485
  private readonly pollIntervalMs?;
465
486
  /** Tombstone cache — avoids redundant reads within a single flush() cycle. */
466
487
  private tombstoneCache;
467
- constructor(appId: string, nodeId: string, primaryBackendId: string, cachedFS: MinimalAsyncFS, serializer: PathAwareSerializer, onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>, pollIntervalMs?: number);
488
+ constructor(appId: string, nodeId: string, primaryBackendId: string, cachedFS: MinimalAsyncFS, serializer: PathAwareSerializer, onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>, pollIntervalMs?: number, cacheOptions?: CacheOptions);
468
489
  /** Full path to this node's directory on the primary backend. */
469
490
  get nodePath(): string;
470
491
  /** Number of replica backends registered (excludes the local primary). */
package/dist/index.d.ts CHANGED
@@ -89,11 +89,20 @@ interface ConfigSerializer {
89
89
  }
90
90
  /** Cache configuration. */
91
91
  interface CacheOptions {
92
- /** Type of cache store. */
92
+ /**
93
+ * Type of cache store for replica backends.
94
+ * Default: `'IdbCacheStore'` — persists across page reloads so cached
95
+ * content + revision tokens survive. Use `'MemoryCacheStore'` for
96
+ * session-only caching (lost on reload).
97
+ */
93
98
  storeType?: 'MemoryCacheStore' | 'IdbCacheStore';
94
- /** Cache store prefix (for IdbCacheStore). */
99
+ /** Cache store prefix (for IdbCacheStore). Default: `'zen-fs-config:'`. */
95
100
  storePrefix?: string;
96
- /** TTL in milliseconds for cache hits without revalidation. Default: 0 (always revalidate). */
101
+ /**
102
+ * TTL in milliseconds for cache hits without revalidation.
103
+ * Default: 0 (always revalidate via `getRevision` — exact, no stale data).
104
+ * Ignored when the backend implements `getRevision`.
105
+ */
97
106
  ttlMs?: number;
98
107
  }
99
108
  /** Options for creating a ConfigRepo. */
@@ -119,8 +128,19 @@ interface ConfigRepoOptions {
119
128
  idbStoreName?: string;
120
129
  /** Node identifier. Auto-detected if not provided (see DESIGN.md §8.2). */
121
130
  nodeId?: string;
122
- /** Cache configuration. */
123
- cache?: CacheOptions;
131
+ /**
132
+ * Cache configuration for replica backends.
133
+ *
134
+ * - **Default** (omitted): Caching is enabled with `IdbCacheStore`
135
+ * (persists across page reloads). Replica backends (Gitee,
136
+ * RemoteStorage, etc.) are wrapped with `CachedFileSystem` to avoid
137
+ * redundant network reads via `getRevision` revalidation.
138
+ * - Pass a `CacheOptions` object to customize the store type, prefix,
139
+ * or TTL.
140
+ * - Pass `false` to disable caching entirely (all reads go directly
141
+ * to the backend).
142
+ */
143
+ cache?: CacheOptions | false;
124
144
  /** Custom serializer. */
125
145
  serializer?: ConfigSerializer;
126
146
  /** Custom conflict handler. Called before auto-resolution. */
@@ -461,10 +481,11 @@ declare class ConfigRepo implements IConfigRepo {
461
481
  private disposed;
462
482
  private configCache;
463
483
  private readonly primaryBackendId;
484
+ private readonly cacheOptions?;
464
485
  private readonly pollIntervalMs?;
465
486
  /** Tombstone cache — avoids redundant reads within a single flush() cycle. */
466
487
  private tombstoneCache;
467
- constructor(appId: string, nodeId: string, primaryBackendId: string, cachedFS: MinimalAsyncFS, serializer: PathAwareSerializer, onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>, pollIntervalMs?: number);
488
+ constructor(appId: string, nodeId: string, primaryBackendId: string, cachedFS: MinimalAsyncFS, serializer: PathAwareSerializer, onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>, pollIntervalMs?: number, cacheOptions?: CacheOptions);
468
489
  /** Full path to this node's directory on the primary backend. */
469
490
  get nodePath(): string;
470
491
  /** Number of replica backends registered (excludes the local primary). */
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,36 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/cache-wrapper.ts
34
+ var cache_wrapper_exports = {};
35
+ __export(cache_wrapper_exports, {
36
+ wrapWithCache: () => wrapWithCache
37
+ });
38
+ function wrapWithCache(backend, backendId, options) {
39
+ const storeType = options.storeType ?? "IdbCacheStore";
40
+ const prefix = options.storePrefix ?? `zen-fs-config:${backendId}:`;
41
+ let store;
42
+ if (storeType === "IdbCacheStore") {
43
+ store = new import_zen_fs_cache.IdbCacheStore(prefix);
44
+ } else {
45
+ store = new import_zen_fs_cache.MemoryCacheStore();
46
+ }
47
+ const wrapped = new import_zen_fs_cache.CachedFileSystem(backend, store, {
48
+ ttlMs: options.ttlMs ?? 0
49
+ });
50
+ if (typeof backend.shouldSync === "function") {
51
+ wrapped.shouldSync = (...args) => backend.shouldSync(...args);
52
+ }
53
+ return wrapped;
54
+ }
55
+ var import_zen_fs_cache;
56
+ var init_cache_wrapper = __esm({
57
+ "src/cache-wrapper.ts"() {
58
+ "use strict";
59
+ import_zen_fs_cache = require("zen-fs-cache");
60
+ }
61
+ });
62
+
30
63
  // src/index.ts
31
64
  var index_exports = {};
32
65
  __export(index_exports, {
@@ -460,7 +493,7 @@ function versionPathFor(configFilePath) {
460
493
  if (fileName.endsWith(".version")) {
461
494
  return null;
462
495
  }
463
- const versionFileName = `.${fileName}.version`;
496
+ const versionFileName = fileName.startsWith(".") ? `${fileName}.version` : `.${fileName}.version`;
464
497
  return dir ? `${dir}/${versionFileName}` : versionFileName;
465
498
  }
466
499
  async function sha256(data) {
@@ -585,10 +618,11 @@ var ConfigRepo = class {
585
618
  disposed = false;
586
619
  configCache = /* @__PURE__ */ new Map();
587
620
  primaryBackendId;
621
+ cacheOptions;
588
622
  pollIntervalMs;
589
623
  /** Tombstone cache — avoids redundant reads within a single flush() cycle. */
590
624
  tombstoneCache = null;
591
- constructor(appId, nodeId, primaryBackendId, cachedFS, serializer, onConflict, pollIntervalMs) {
625
+ constructor(appId, nodeId, primaryBackendId, cachedFS, serializer, onConflict, pollIntervalMs, cacheOptions) {
592
626
  this.appId = appId;
593
627
  this.nodeId = nodeId;
594
628
  this.primaryBackendId = primaryBackendId;
@@ -598,6 +632,7 @@ var ConfigRepo = class {
598
632
  this.replicaBackends = /* @__PURE__ */ new Map();
599
633
  this.onConflictCallback = onConflict;
600
634
  this.pollIntervalMs = pollIntervalMs;
635
+ this.cacheOptions = cacheOptions;
601
636
  this.fullFS = backendToSyncableFS(cachedFS, primaryBackendId);
602
637
  this.fs = createChrootFS(cachedFS, `/${appId}`);
603
638
  this.rootFS = createChrootFS(cachedFS, "/");
@@ -1036,7 +1071,13 @@ var ConfigRepo = class {
1036
1071
  console.log(`[ConfigRepo] Creating replica backend: id=${desc.id}, type=${desc.type}`);
1037
1072
  try {
1038
1073
  const instance = await createBackend(desc);
1039
- const syncable = backendToSyncableFS(instance, `${desc.type}(${desc.id})`);
1074
+ let fsInstance = instance;
1075
+ if (this.cacheOptions) {
1076
+ const { wrapWithCache: wrapWithCache2 } = await Promise.resolve().then(() => (init_cache_wrapper(), cache_wrapper_exports));
1077
+ fsInstance = wrapWithCache2(instance, desc.id, this.cacheOptions);
1078
+ console.log(`[ConfigRepo] Replica ${desc.id} wrapped with CachedFileSystem (store=${this.cacheOptions.storeType ?? "IdbCacheStore"})`);
1079
+ }
1080
+ const syncable = backendToSyncableFS(fsInstance, `${desc.type}(${desc.id})`);
1040
1081
  const pair = this.syncEngine.addPair(
1041
1082
  this.fullFS,
1042
1083
  syncable,
@@ -1047,7 +1088,7 @@ var ConfigRepo = class {
1047
1088
  },
1048
1089
  "/"
1049
1090
  );
1050
- this.replicaBackends.set(desc.id, { instance, syncable, pairId: pair.pairId });
1091
+ this.replicaBackends.set(desc.id, { instance: fsInstance, syncable, pairId: pair.pairId });
1051
1092
  const conflictHandler = (event) => {
1052
1093
  this.handleConflict(event);
1053
1094
  };
@@ -1753,6 +1794,7 @@ async function createConfigRepo(appId, options = {}) {
1753
1794
  options: { storeName: idbStoreName }
1754
1795
  });
1755
1796
  const cachedFS = primaryInstance;
1797
+ const cacheOptions = options.cache === false ? void 0 : options.cache ?? {};
1756
1798
  try {
1757
1799
  await primaryInstance.mkdir(META_DIR);
1758
1800
  console.log(`[createConfigRepo] /.meta/ ready`);
@@ -1777,7 +1819,8 @@ async function createConfigRepo(appId, options = {}) {
1777
1819
  cachedFS,
1778
1820
  createSerializerChain(),
1779
1821
  void 0,
1780
- options.syncPollIntervalMs
1822
+ options.syncPollIntervalMs,
1823
+ cacheOptions
1781
1824
  );
1782
1825
  const oldBackendsMeta = await tempRepo.readMetaFile(BACKENDS_FILE);
1783
1826
  if (oldBackendsMeta && oldBackendsMeta.backends?.length > 0) {
@@ -1843,7 +1886,8 @@ async function createConfigRepo(appId, options = {}) {
1843
1886
  cachedFS,
1844
1887
  serializer,
1845
1888
  options.onConflict,
1846
- options.syncPollIntervalMs
1889
+ options.syncPollIntervalMs,
1890
+ cacheOptions
1847
1891
  );
1848
1892
  await repo.setupSync(allBackends, LOCAL_IDB_BACKEND_ID, options.syncPollIntervalMs);
1849
1893
  await repo.load();
package/dist/index.mjs CHANGED
@@ -403,7 +403,7 @@ function versionPathFor(configFilePath) {
403
403
  if (fileName.endsWith(".version")) {
404
404
  return null;
405
405
  }
406
- const versionFileName = `.${fileName}.version`;
406
+ const versionFileName = fileName.startsWith(".") ? `${fileName}.version` : `.${fileName}.version`;
407
407
  return dir ? `${dir}/${versionFileName}` : versionFileName;
408
408
  }
409
409
  async function sha256(data) {
@@ -528,10 +528,11 @@ var ConfigRepo = class {
528
528
  disposed = false;
529
529
  configCache = /* @__PURE__ */ new Map();
530
530
  primaryBackendId;
531
+ cacheOptions;
531
532
  pollIntervalMs;
532
533
  /** Tombstone cache — avoids redundant reads within a single flush() cycle. */
533
534
  tombstoneCache = null;
534
- constructor(appId, nodeId, primaryBackendId, cachedFS, serializer, onConflict, pollIntervalMs) {
535
+ constructor(appId, nodeId, primaryBackendId, cachedFS, serializer, onConflict, pollIntervalMs, cacheOptions) {
535
536
  this.appId = appId;
536
537
  this.nodeId = nodeId;
537
538
  this.primaryBackendId = primaryBackendId;
@@ -541,6 +542,7 @@ var ConfigRepo = class {
541
542
  this.replicaBackends = /* @__PURE__ */ new Map();
542
543
  this.onConflictCallback = onConflict;
543
544
  this.pollIntervalMs = pollIntervalMs;
545
+ this.cacheOptions = cacheOptions;
544
546
  this.fullFS = backendToSyncableFS(cachedFS, primaryBackendId);
545
547
  this.fs = createChrootFS(cachedFS, `/${appId}`);
546
548
  this.rootFS = createChrootFS(cachedFS, "/");
@@ -979,7 +981,13 @@ var ConfigRepo = class {
979
981
  console.log(`[ConfigRepo] Creating replica backend: id=${desc.id}, type=${desc.type}`);
980
982
  try {
981
983
  const instance = await createBackend(desc);
982
- const syncable = backendToSyncableFS(instance, `${desc.type}(${desc.id})`);
984
+ let fsInstance = instance;
985
+ if (this.cacheOptions) {
986
+ const { wrapWithCache } = await import("./cache-wrapper-XQCPZJCD.mjs");
987
+ fsInstance = wrapWithCache(instance, desc.id, this.cacheOptions);
988
+ console.log(`[ConfigRepo] Replica ${desc.id} wrapped with CachedFileSystem (store=${this.cacheOptions.storeType ?? "IdbCacheStore"})`);
989
+ }
990
+ const syncable = backendToSyncableFS(fsInstance, `${desc.type}(${desc.id})`);
983
991
  const pair = this.syncEngine.addPair(
984
992
  this.fullFS,
985
993
  syncable,
@@ -990,7 +998,7 @@ var ConfigRepo = class {
990
998
  },
991
999
  "/"
992
1000
  );
993
- this.replicaBackends.set(desc.id, { instance, syncable, pairId: pair.pairId });
1001
+ this.replicaBackends.set(desc.id, { instance: fsInstance, syncable, pairId: pair.pairId });
994
1002
  const conflictHandler = (event) => {
995
1003
  this.handleConflict(event);
996
1004
  };
@@ -1696,6 +1704,7 @@ async function createConfigRepo(appId, options = {}) {
1696
1704
  options: { storeName: idbStoreName }
1697
1705
  });
1698
1706
  const cachedFS = primaryInstance;
1707
+ const cacheOptions = options.cache === false ? void 0 : options.cache ?? {};
1699
1708
  try {
1700
1709
  await primaryInstance.mkdir(META_DIR);
1701
1710
  console.log(`[createConfigRepo] /.meta/ ready`);
@@ -1720,7 +1729,8 @@ async function createConfigRepo(appId, options = {}) {
1720
1729
  cachedFS,
1721
1730
  createSerializerChain(),
1722
1731
  void 0,
1723
- options.syncPollIntervalMs
1732
+ options.syncPollIntervalMs,
1733
+ cacheOptions
1724
1734
  );
1725
1735
  const oldBackendsMeta = await tempRepo.readMetaFile(BACKENDS_FILE);
1726
1736
  if (oldBackendsMeta && oldBackendsMeta.backends?.length > 0) {
@@ -1786,7 +1796,8 @@ async function createConfigRepo(appId, options = {}) {
1786
1796
  cachedFS,
1787
1797
  serializer,
1788
1798
  options.onConflict,
1789
- options.syncPollIntervalMs
1799
+ options.syncPollIntervalMs,
1800
+ cacheOptions
1790
1801
  );
1791
1802
  await repo.setupSync(allBackends, LOCAL_IDB_BACKEND_ID, options.syncPollIntervalMs);
1792
1803
  await repo.load();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-fs-config",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
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",