zen-fs-config 0.3.30 → 0.4.0

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/DESIGN.md CHANGED
@@ -25,18 +25,22 @@ ConfigRepo (this library)
25
25
  └─ Backend Z (replica)
26
26
  ```
27
27
 
28
- ### 2.2 No Global Primary
28
+ ### 2.2 IndexedDB as Local Primary (Offline-First)
29
29
 
30
- Globally, all backends are equal peers none is the "source of truth."
30
+ Every program instance uses **IndexedDB** as its local primary backend. All config reads and writes target IndexedDB directly, ensuring offline availability and fast local access.
31
31
 
32
- Locally, each program instance has a "primary backend"the backend it directly reads/writes through. This is a per-instance choice, not a property of the backend itself.
32
+ User-provided backends (Gitee, S3, RemoteStorage, etc.) are added as **replicas**they receive bi-directional sync with the local IndexedDB but are never the direct target of config operations.
33
33
 
34
34
  ```
35
- Program A → Primary = Backend X, Replicas = [Y, Z]
36
- Program B → Primary = Backend Y, Replicas = [X, Z]
37
- Program C → Primary = Backend Z, Replicas = [X, Y]
35
+ Program A → Primary = IndexedDB (local), Replicas = [Gitee, S3]
36
+ Program B → Primary = IndexedDB (local), Replicas = [Gitee, S3]
38
37
  ```
39
38
 
39
+ This means:
40
+ - Config is always available offline (IndexedDB persists in the browser)
41
+ - Remote backends accelerate multi-device sync, not local access
42
+ - Re-opening the app requires zero backend parameters — IndexedDB + `.meta/backends/` contain everything needed
43
+
40
44
  ### 2.3 Self-Describing Configuration
41
45
 
42
46
  Backend topology and sync rules are stored **inside** the configuration repository (in `.meta/`), not passed as external parameters. This means any node that can read the config repo can bootstrap the entire sync network.
@@ -47,18 +51,25 @@ External input at startup is limited to: **which backend to connect to** and opt
47
51
 
48
52
  ```
49
53
  /
50
- ├─ .meta/ [not synced]
51
- │ ├─ backends.json Backend topology (self-describing)
52
- │ ├─ sync-rules.json Sync rules
53
- └─ .conflicts/ Conflict archives (safekeeping)
54
- └─ {timestamp}_{path}.from-{a}.to-{b}.json
54
+ ├─ .meta/ [synced to replicas]
55
+ │ ├─ backends/ Backend topology (one file per backend)
56
+ ├─ local-idb.json { id, type, options, description }
57
+ │ ├─ gitee-prod.json
58
+ └─ ...
59
+ │ ├─ .deleted/ Tombstones for deletion propagation
60
+ │ │ └─ {encoded-path}.json
61
+ │ └─ .conflicts/ Conflict archives (safekeeping)
62
+ │ └─ {timestamp}_{path}/
63
+ │ ├─ meta.json
64
+ │ ├─ source
65
+ │ └─ target
55
66
 
56
- ├─ {appId}/ [one-way: owner → replicas, no conflict]
67
+ ├─ {appId}/ [synced: owner → replicas]
57
68
  │ ├─ db.json
58
69
  │ ├─ cache.json
59
70
  │ └─ .db.json.version Sidecar version file
60
71
 
61
- ├─ shared/ [bi-directional, conflict possible]
72
+ ├─ shared/ [synced: bi-directional]
62
73
  │ ├─ feature-flags.json
63
74
  │ ├─ api-version.json
64
75
  │ └─ .feature-flags.json.version
@@ -74,10 +85,10 @@ External input at startup is limited to: **which backend to connect to** and opt
74
85
 
75
86
  | Directory | Sync Direction | Conflict Risk | Purpose |
76
87
  |---|---|---|---|
77
- | `/{appId}/` | One-way (owner replicas) | None (single writer) | Per-app private config |
88
+ | `/{appId}/` | Bi-directional (primary replicas) | Low (single device) | Per-app private config |
78
89
  | `/shared/` | Bi-directional | Possible (multiple writers) | Cross-app shared config |
79
90
  | `/nodes/` | None (by default) | None | Per-node local config |
80
- | `/.meta/` | None | None | Topology, rules, conflict archives |
91
+ | `/.meta/` | Bi-directional | None (topology files) | Backend topology, tombstones, conflict archives |
81
92
 
82
93
  ### 3.2 Config-to-File Mapping
83
94
 
@@ -101,29 +112,33 @@ The serializer is determined by file extension:
101
112
 
102
113
  Users can inject a custom `ConfigSerializer` for other formats.
103
114
 
104
- ## 4. Backend Topology (`.meta/backends.json`)
115
+ ## 4. Backend Topology (`.meta/backends/*.json`)
116
+
117
+ Each backend is stored as an individual JSON file in `.meta/backends/`. This allows atomic add/remove operations without rewriting the entire topology.
105
118
 
119
+ **`.meta/backends/local-idb.json`** (always present):
106
120
  ```json
107
121
  {
108
- "version": 1,
109
- "backends": [
110
- {
111
- "id": "local-idb",
112
- "type": "IndexedDB",
113
- "options": { "dbName": "app-config" },
114
- "description": "Browser local storage"
115
- },
116
- {
117
- "id": "remote-s3",
118
- "type": "S3Bucket",
119
- "options": { "bucket": "app-config-bucket", "region": "us-east-1" },
120
- "description": "Cloud backup"
121
- }
122
- ]
122
+ "id": "local-idb",
123
+ "type": "IndexedDB",
124
+ "options": { "storeName": "zen-fs-config-my-app" },
125
+ "description": "Local IndexedDB primary backend"
123
126
  }
124
127
  ```
125
128
 
126
- Each program instance selects one backend as its primary via `primaryBackendId`. Others become replicas.
129
+ **`.meta/backends/gitee-prod.json`** (user-added replica):
130
+ ```json
131
+ {
132
+ "id": "gitee-prod",
133
+ "type": "Gitee",
134
+ "options": { "token": "...", "owner": "...", "repo": "...", "branch": "main" },
135
+ "description": "Production Gitee config repo"
136
+ }
137
+ ```
138
+
139
+ The local IndexedDB backend (`local-idb`) is always the primary — all config operations target it directly. All other backends are replicas with bi-directional sync.
140
+
141
+ **Migration**: If a legacy `.meta/backends.json` file exists (pre-0.4.0), it is automatically migrated to individual files on startup.
127
142
 
128
143
  ## 5. Sync Rules (`.meta/sync-rules.json`)
129
144
 
@@ -343,99 +358,116 @@ interface ConfigRepo {
343
358
  /** List conflict archives */
344
359
  listConflicts(): Promise<ConflictArchive[]>;
345
360
 
346
- /** Dispose: stop sync, release cache FS */
361
+ /** Read backend topology (aggregated from .meta/backends/*.json) */
362
+ getBackends(): Promise<BackendsMeta | null>;
363
+
364
+ /** Write backend topology (writes each backend as individual file) */
365
+ updateBackends(meta: BackendsMeta): Promise<void>;
366
+
367
+ /** Dynamically add a replica backend */
368
+ addBackend(id: string, type: string, options: Record<string, unknown>, description?: string): Promise<void>;
369
+
370
+ /** Dynamically remove a replica backend */
371
+ removeBackend(id: string): Promise<void>;
372
+
373
+ /** Delete a file with tombstone (propagates deletion to all backends) */
374
+ deleteFile(path: string): Promise<void>;
375
+
376
+ /** Sync .meta/ files to all replicas */
377
+ syncMetaToReplicas(): Promise<void>;
378
+
379
+ /** Dispose: stop sync, release resources */
347
380
  dispose(): Promise<void>;
348
381
  }
349
382
  ```
350
383
 
351
384
  ## 10. Initialization
352
385
 
386
+ ### Zero-parameter (offline-first)
387
+
353
388
  ```typescript
354
389
  import { createConfigRepo } from 'zen-fs-config';
355
390
 
356
- const repo = await createConfigRepo('app-a', {
357
- // The only required external input: which backend to connect to
358
- primaryBackendId: 'local-idb',
391
+ // No parameters needed IndexedDB is always created as primary
392
+ const repo = await createConfigRepo('my-app');
359
393
 
360
- // Backend connection info (only for the primary)
361
- backendInfo: {
362
- type: 'IndexedDB',
363
- options: { dbName: 'app-a-config' }
364
- },
394
+ // Config is immediately available from IndexedDB
395
+ repo.setConfig('/db/host', { hostname: 'localhost', port: 3306 });
396
+ ```
365
397
 
366
- // Node ID (optional, see §8.2 for auto-detection)
367
- nodeId: 'server-1',
398
+ ### With initial replica backend
368
399
 
369
- // Cache configuration (optional, defaults shown)
370
- cache: {
371
- storeType: 'MemoryCacheStore',
372
- ttlMs: 60000
400
+ ```typescript
401
+ const repo = await createConfigRepo('my-app', {
402
+ // Optional: provide a remote backend as initial replica
403
+ primaryBackendId: 'gitee-prod',
404
+ backendInfo: {
405
+ type: 'Gitee',
406
+ options: { token: '...', owner: '...', repo: '...', branch: 'main' },
373
407
  },
374
-
375
- // Bootstrap data (only used when .meta/backends.json doesn't exist)
376
- bootstrap: {
377
- backends: [
378
- { id: 'local-idb', type: 'IndexedDB', options: { dbName: 'app-config' } },
379
- { id: 'remote-s3', type: 'S3Bucket', options: { bucket: 'app-config' } }
380
- ],
381
- syncRules: [
382
- { prefix: '/app-a/', direction: 'one-way', conflictStrategy: 'source-wins', replicas: ['local-idb', 'remote-s3'] },
383
- { prefix: '/shared/', direction: 'bi-directional', conflictStrategy: 'merge', replicas: ['local-idb', 'remote-s3'] },
384
- { prefix: '/nodes/', direction: 'none' },
385
- { prefix: '/.meta/', direction: 'none' }
386
- ]
387
- }
408
+ // Optional: customize IndexedDB store name
409
+ idbStoreName: 'my-app-config',
410
+ // Optional: node ID (auto-detected if not provided)
411
+ nodeId: 'server-1',
388
412
  });
389
413
 
390
- // Normal config operations
391
- repo.setConfig('/db/host', { hostname: 'localhost', port: 3306 });
392
- const dbConfig = repo.getConfig<{ hostname: string; port: number }>('/db/host');
393
-
394
- // Node-local config
395
- repo.setNodeConfig('server-1', '/local.json', { ip: '10.0.0.1' });
414
+ // Later, add more backends dynamically
415
+ await repo.addBackend('s3-backup', 'S3Bucket', {
416
+ bucket: 'app-config',
417
+ region: 'us-east-1',
418
+ }, 'S3 backup');
396
419
 
397
- // Publish for debugging (one-time sync)
398
- await repo.publishNodeConfig('server-1');
420
+ // Remove a backend
421
+ await repo.removeBackend('gitee-prod');
399
422
 
400
423
  // Cleanup
401
424
  await repo.dispose();
402
425
  ```
403
426
 
427
+ ### Re-opening (zero parameters)
428
+
429
+ ```typescript
430
+ // On subsequent opens, just pass appId
431
+ // IndexedDB + .meta/backends/ contain all state
432
+ const repo = await createConfigRepo('my-app');
433
+
434
+ // All previously added backends are automatically reconnected
435
+ const backends = await repo.getBackends();
436
+ // backends.backends = [{ id: 'local-idb', ... }, { id: 's3-backup', ... }]
437
+ ```
438
+
404
439
  ## 11. Initialization Flow
405
440
 
406
441
  ```
407
- createConfigRepo('app-a', { primaryBackendId: 'X', backendInfo: {...}, bootstrap: {...} })
442
+ createConfigRepo('my-app', options?)
408
443
 
409
- ├─ 1. Connect to primary backend (backendId = 'X')
444
+ ├─ 1. Create IndexedDB backend (always, ID = 'local-idb')
445
+ │ storeName = options.idbStoreName || `zen-fs-config-${appId}`
410
446
 
411
- ├─ 2. Wrap with zen-fs-cache → CachedFileSystem(primaryBackend, cacheStore, { ttlMs })
447
+ ├─ 2. Ensure /.meta/ directory exists
412
448
 
413
- ├─ 3. Configure ZenFS VFS: { '/': cachedFS }
449
+ ├─ 3. Migrate legacy .meta/backends.json .meta/backends/*.json (if exists)
414
450
 
415
- ├─ 4. Read .meta/backends.json
416
- │ ├─ Exists → parse topology, create replica backend instances
417
- │ └─ Not exists → write bootstrap data to .meta/
451
+ ├─ 4. Ensure local-idb descriptor exists in .meta/backends/
418
452
 
419
- ├─ 5. Read .meta/sync-rules.json
420
- │ ├─ Exists parse rules
421
- │ └─ Not exists use bootstrap syncRules
453
+ ├─ 5. If options.backendInfo provided:
454
+ │ ├─ Generate replica ID (options.primaryBackendId or auto)
455
+ │ └─ Write descriptor to .meta/backends/{replicaId}.json (if not exists)
422
456
 
423
- ├─ 6. For each rule with direction != 'none':
424
- │ ├─ Create SyncPair(
425
- │ │ source: cachedFS,
426
- │ │ target: replicaBackend,
427
- │ │ { direction, conflictStrategy, filter: { includePrefixes: [rule.prefix] } }
428
- │ │ )
429
- │ └─ syncEngine.watch(pairId)
457
+ ├─ 6. Read all backend descriptors from .meta/backends/
430
458
 
431
459
  ├─ 7. Determine nodeId (explicit > env > auto-generated .node-id file)
432
460
 
433
- ├─ 8. Create ZenFS Context:
434
- │ ├─ Allowed paths: /{appId}/, /shared/, /nodes/{nodeId}/, /.meta/
435
- │ ├─ Root chroot: /{appId}/ (for getConfig/setConfig)
436
- │ └─ Full access for zen-fs-sync (unrestricted)
461
+ ├─ 8. Create ConfigRepo with primary = 'local-idb'
437
462
 
438
- └─ 9. Return ConfigRepo { fs, appId, nodeId, ... }
463
+ ├─ 9. setupSync: for each backend (except local-idb):
464
+ │ ├─ Create backend instance
465
+ │ ├─ Create SyncPair(IndexedDB, replica, bi-directional)
466
+ │ └─ syncEngine.watch(pairId)
467
+
468
+ ├─ 10. syncMetaToReplicas: push .meta/ changes to all replicas
469
+
470
+ └─ 11. Load config cache from IndexedDB
439
471
  ```
440
472
 
441
473
  ## 12. Data Flow
@@ -486,11 +518,12 @@ Application
486
518
 
487
519
  ## 13. Peer Dependencies
488
520
 
489
- | Package | Role | Version |
490
- |---|---|---|
491
- | `@zenfs/core` | Virtual file system, backends, VFS, Context | >=2.3.0 |
492
- | `zen-fs-cache` | Read caching with ETag/304 revalidation | >=1.0.0 |
493
- | `zen-fs-sync` | Cross-backend sync engine | >=0.1.0 |
521
+ | Package | Role | Version | Required |
522
+ |---|---|---|---|
523
+ | `@zenfs/core` | Virtual file system, backends, VFS, Context | >=2.3.0 | Yes |
524
+ | `@zenfs/dom` | IndexedDB backend (browser) | >=1.0.0 | Yes (browser) |
525
+ | `zen-fs-sync` | Cross-backend sync engine | >=0.1.0 | Yes |
526
+ | `zen-fs-cache` | Read caching with ETag/304 revalidation | >=1.0.0 | No (optional) |
494
527
 
495
528
  ## 14. Extension Points
496
529
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # zen-fs-config
2
2
 
3
- 基于 ZenFS 的分布式配置管理库。多个应用实例通过任意 ZenFS 后端共享配置,支持应用隔离、共享空间、节点本地配置和冲突安全。
3
+ 基于 ZenFS 的分布式配置管理库。以 IndexedDB 为本地主后端(offline-first),用户提供的远程后端作为副本自动同步。支持应用隔离、共享空间、节点本地配置和冲突安全。
4
4
 
5
5
  **GitHub**: https://github.com/weijia/zen-fs-config
6
6
  **NPM**: `zen-fs-config`
@@ -9,66 +9,109 @@
9
9
  ## 安装
10
10
 
11
11
  ```bash
12
- npm install zen-fs-config @zenfs/core zen-fs-cache zen-fs-sync
12
+ npm install zen-fs-config @zenfs/core @zenfs/dom zen-fs-sync
13
13
  ```
14
14
 
15
+ > `@zenfs/dom` 提供 IndexedDB 后端(浏览器环境必需)。`zen-fs-cache` 为可选依赖。
16
+
15
17
  ## 快速开始
16
18
 
19
+ ### 1. 初始化(零参数)
20
+
17
21
  ```typescript
18
- import { createConfigRepo, registerBackend } from 'zen-fs-config';
19
- import { InMemory } from '@zenfs/core';
22
+ import { createConfigRepo } from 'zen-fs-config';
20
23
 
21
- // 1. 注册自定义后端(可选,InMemory 已内置)
22
- registerBackend('InMemory', async (options) => {
23
- return InMemory.create({ maxSize: options.maxSize as number ?? 100 * 1024 * 1024 });
24
- });
24
+ // 不传任何后端参数,自动创建 IndexedDB 本地主后端
25
+ const repo = await createConfigRepo('my-app');
25
26
 
26
- // 2. 创建配置仓库
27
- const repo = await createConfigRepo('my-app', {
28
- primaryBackendId: 'local-memory',
29
- backendInfo: {
30
- type: 'InMemory',
31
- options: { label: 'my-app-config' },
32
- },
33
- cache: { storeType: 'MemoryCacheStore', ttlMs: 60_000 },
34
- bootstrap: {
35
- backends: [
36
- { id: 'local-memory', type: 'InMemory', options: { label: 'primary' } },
37
- ],
38
- syncRules: [
39
- { prefix: '/my-app/', direction: 'one-way', conflictStrategy: 'source-wins', replicas: ['local-memory'] },
40
- { prefix: '/shared/', direction: 'bi-directional', conflictStrategy: 'merge', replicas: ['local-memory'] },
41
- { prefix: '/nodes/', direction: 'none' },
42
- ],
43
- },
27
+ // 读写配置(同步 API,从 IndexedDB 读取)
28
+ repo.setConfig('/database', { host: 'localhost', port: 5432 });
29
+ const db = repo.getConfig<{ host: string; port: number }>('/database');
30
+ ```
31
+
32
+ ### 2. 设置新的配置
33
+
34
+ ```typescript
35
+ repo.setConfig('/cache', { ttl: 3600, maxSize: '100MB' });
36
+ repo.setConfig('/feature-flags', { newUI: true, beta: false });
37
+ ```
38
+
39
+ ### 3. 增加数据后端(副本)
40
+
41
+ ```typescript
42
+ import { registerBackend } from 'zen-fs-config';
43
+ import { Gitee } from 'zen-fs-gitee';
44
+
45
+ // 注册后端类型
46
+ registerBackend('Gitee', async (options) => {
47
+ return Gitee.create(options);
44
48
  });
45
49
 
46
- // 3. 读写配置(同步 API,从内存缓存读取)
47
- repo.setConfig('/database', { host: 'localhost', port: 5432 });
50
+ // 动态添加副本后端,自动与本地 IndexedDB 双向同步
51
+ await repo.addBackend('gitee-prod', 'Gitee', {
52
+ token: 'your-token',
53
+ owner: 'your-name',
54
+ repo: 'config-repo',
55
+ branch: 'main',
56
+ }, '生产环境 Gitee 配置仓库');
57
+ ```
58
+
59
+ ### 4. 自动同步
60
+
61
+ ```typescript
62
+ // setConfig 写入 IndexedDB 后,自动同步到所有副本后端
63
+ repo.setConfig('/database', { host: 'new-host', port: 5432 });
64
+
65
+ // 手动触发同步(通常不需要,同步是自动的)
66
+ await repo.flush();
67
+ ```
68
+
69
+ ### 5. 再次打开时初始化
70
+
71
+ ```typescript
72
+ // 重新打开页面时,只需传入 appId
73
+ // IndexedDB 中的配置和后端拓扑会自动恢复
74
+ const repo = await createConfigRepo('my-app');
75
+
76
+ // 配置直接从 IndexedDB 读取(离线可用)
48
77
  const db = repo.getConfig<{ host: string; port: number }>('/database');
49
78
 
50
- // 4. 节点本地配置(异步 API,不自动同步)
51
- await repo.setNodeConfig('node-1', '/debug', { level: 'verbose' });
52
- const debug = await repo.getNodeConfig('node-1', '/debug');
79
+ // 已注册的副本后端会自动重新连接并同步
80
+ const backends = await repo.getBackends();
81
+ console.log(backends?.backends.map(b => b.id)); // ['local-idb', 'gitee-prod', ...]
82
+ ```
53
83
 
54
- // 5. 发布节点配置到同步后端(用于调试)
55
- await repo.publishNodeConfig('node-1');
84
+ ### 带初始后端初始化
56
85
 
57
- // 6. 清理
58
- await repo.dispose();
86
+ ```typescript
87
+ // 首次初始化时可以直接传入远程后端
88
+ const repo = await createConfigRepo('my-app', {
89
+ primaryBackendId: 'gitee-prod', // 副本后端 ID
90
+ backendInfo: {
91
+ type: 'Gitee',
92
+ options: { token: 'xxx', owner: 'xxx', repo: 'xxx', branch: 'main' },
93
+ },
94
+ idbStoreName: 'my-app-config', // 自定义 IndexedDB store 名称
95
+ });
96
+
97
+ // 之后重新打开时不需要再传后端参数
98
+ const repo2 = await createConfigRepo('my-app');
59
99
  ```
60
100
 
61
101
  ## 目录结构
62
102
 
63
103
  ```
64
104
  /
65
- ├── {appId}/ # 应用私有配置(单向同步,每个应用只能读写自己的)
66
- ├── shared/ # 跨应用共享配置(双向同步,merge 冲突策略)
67
- ├── nodes/{nodeId}/ # 节点本地配置(不同步,除非手动 publish)
105
+ ├── {appId}/ # 应用私有配置(自动同步到副本)
106
+ ├── shared/ # 跨应用共享配置(双向同步)
107
+ ├── nodes/{nodeId}/ # 节点本地配置(不同步)
68
108
  └── .meta/
69
- ├── backends.json # 后端拓扑(自描述,任意后端可引导)
70
- ├── sync-rules.json
71
- └── .conflicts/ # 冲突归档(双方内容都保存,永不丢失)
109
+ ├── backends/ # 后端拓扑(每个后端一个文件)
110
+ ├── local-idb.json
111
+ │ ├── gitee-prod.json
112
+ │ └── ...
113
+ ├── .deleted/ # 删除墓碑(跨后端删除传播)
114
+ └── .conflicts/ # 冲突归档(双方内容都保存)
72
115
  ```
73
116
 
74
117
  每个配置文件有 sidecar 版本文件:`db.json` → `.db.json.version`(版本号 + SHA-256 哈希)。
@@ -77,8 +120,12 @@ await repo.dispose();
77
120
 
78
121
  | 方法 | 说明 |
79
122
  |---|---|
80
- | `getConfig<T>(path)` | 同步读取应用配置(从内存缓存) |
123
+ | `createConfigRepo(appId, options?)` | 创建配置仓库。IndexedDB 始终为主后端,`backendInfo` 作为副本 |
124
+ | `getConfig<T>(path)` | 同步读取应用配置(从 IndexedDB) |
81
125
  | `setConfig(path, data)` | 同步写入应用配置(异步持久化 + 自动同步) |
126
+ | `addBackend(id, type, options, desc?)` | 动态添加副本后端,自动建立双向同步 |
127
+ | `removeBackend(id)` | 移除副本后端,停止同步 |
128
+ | `getBackends()` | 读取所有后端拓扑(从 `.meta/backends/*.json` 聚合) |
82
129
  | `getNodeConfig<T>(nodeId, path)` | 异步读取节点本地配置 |
83
130
  | `setNodeConfig(nodeId, path, data)` | 异步写入节点本地配置(不同步) |
84
131
  | `publishNodeConfig(nodeId)` | 将节点配置一次性同步到所有后端 |
@@ -86,44 +133,61 @@ await repo.dispose();
86
133
  | `flush()` | 手动触发所有同步 |
87
134
  | `listConflicts()` | 列出所有冲突归档 |
88
135
  | `resolveConflict(id, merged)` | 用合并内容解决冲突 |
89
- | `fs.promises.*` | 标准 fs API,chroot 隔离到 `/{appId}/` 和 `/shared/` |
136
+ | `fs.promises.*` | 标准 fs API,chroot 隔离到 `/{appId}/` |
90
137
  | `dispose()` | 停止同步、释放资源 |
91
138
 
92
139
  ## 后端注册
93
140
 
141
+ zen-fs-config 内置两个后端:
142
+ - **IndexedDB** — 本地主后端(基于 `@zenfs/dom`),无需注册
143
+ - **InMemory** — 内存后端(基于 `@zenfs/core`),用于测试
144
+
145
+ 注册自定义后端:
146
+
94
147
  ```typescript
95
148
  import { registerBackend } from 'zen-fs-config';
96
149
 
150
+ // 注册 Gitee 后端
151
+ registerBackend('Gitee', async (options) => {
152
+ const { Gitee } = await import('zen-fs-gitee');
153
+ return Gitee.create(options);
154
+ });
155
+
97
156
  // 注册 S3 后端
98
157
  registerBackend('S3Bucket', async (options) => {
99
158
  const { S3Bucket } = await import('@zenfs/core');
100
159
  return S3Bucket.create(options);
101
160
  });
161
+ ```
102
162
 
103
- // 注册自定义后端
104
- registerBackend('my-custom', async (options) => {
105
- return {
106
- readFile: (path) => { /* ... */ },
107
- writeFile: (path, data) => { /* ... */ },
108
- readdir: (path) => { /* ... */ },
109
- stat: (path) => { /* ... */ },
110
- exists: (path) => { /* ... */ },
111
- mkdir: (path) => { /* ... */ },
112
- unlink: (path) => { /* ... */ },
113
- rmdir: (path) => { /* ... */ },
114
- rename: (old, newPath) => { /* ... */ },
115
- };
116
- });
163
+ ## 架构概览
164
+
165
+ ```
166
+ Application code
167
+ (reads/writes via standard fs API)
168
+ ConfigRepo (this library)
169
+ ├─ IndexedDB (local primary, always)
170
+ │ └─ All config operations target IndexedDB first
171
+ └─ zen-fs-sync Bi-directional sync
172
+ ├─ Replica X (e.g., Gitee)
173
+ ├─ Replica Y (e.g., S3)
174
+ └─ Replica Z (e.g., RemoteStorage)
117
175
  ```
118
176
 
177
+ - **IndexedDB 是唯一的主后端**:所有读写操作直接操作 IndexedDB,保证离线可用
178
+ - **远程后端是副本**:通过 `addBackend()` 或 `createConfigRepo({ backendInfo })` 添加
179
+ - **自动同步**:对 IndexedDB 的修改会自动同步到所有副本后端
180
+ - **自描述拓扑**:后端配置存储在 `.meta/backends/` 目录中,每个后端一个 JSON 文件
181
+
119
182
  ## 依赖
120
183
 
121
- | 包 | 说明 |
122
- |---|---|
123
- | `@zenfs/core >=2.3.0` | ZenFS 虚拟文件系统 |
124
- | `zen-fs-cache >=1.0.0` | ETag/TTL 缓存层 |
125
- | `zen-fs-sync >=0.1.0` | 跨后端同步引擎 |
184
+ | 包 | 说明 | 必需 |
185
+ |---|---|---|
186
+ | `@zenfs/core >=2.3.0` | ZenFS 虚拟文件系统 | 是 |
187
+ | `@zenfs/dom >=1.0.0` | IndexedDB 后端(浏览器) | 是(浏览器) |
188
+ | `zen-fs-sync >=0.1.0` | 跨后端同步引擎 | 是 |
189
+ | `zen-fs-cache >=1.0.0` | ETag/TTL 缓存层 | 否(可选) |
126
190
 
127
191
  ## License
128
192
 
129
- MIT
193
+ MIT