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 +129 -96
- package/README.md +127 -63
- package/dist/index.d.mts +89 -12
- package/dist/index.d.ts +89 -12
- package/dist/index.js +236 -65
- package/dist/index.mjs +233 -65
- package/package.json +14 -2
package/DESIGN.md
CHANGED
|
@@ -25,18 +25,22 @@ ConfigRepo (this library)
|
|
|
25
25
|
└─ Backend Z (replica)
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
### 2.2
|
|
28
|
+
### 2.2 IndexedDB as Local Primary (Offline-First)
|
|
29
29
|
|
|
30
|
-
|
|
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
|
-
|
|
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 =
|
|
36
|
-
Program B → Primary =
|
|
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/ [
|
|
51
|
-
│ ├─ backends
|
|
52
|
-
│ ├─
|
|
53
|
-
│
|
|
54
|
-
│
|
|
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}/ [
|
|
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
|
|
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}/` |
|
|
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/` |
|
|
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
|
|
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
|
-
"
|
|
109
|
-
"
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
primaryBackendId: 'local-idb',
|
|
391
|
+
// No parameters needed — IndexedDB is always created as primary
|
|
392
|
+
const repo = await createConfigRepo('my-app');
|
|
359
393
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
367
|
-
nodeId: 'server-1',
|
|
398
|
+
### With initial replica backend
|
|
368
399
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
//
|
|
391
|
-
repo.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
//
|
|
398
|
-
await repo.
|
|
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
|
|
442
|
+
createConfigRepo('my-app', options?)
|
|
408
443
|
│
|
|
409
|
-
├─ 1.
|
|
444
|
+
├─ 1. Create IndexedDB backend (always, ID = 'local-idb')
|
|
445
|
+
│ storeName = options.idbStoreName || `zen-fs-config-${appId}`
|
|
410
446
|
│
|
|
411
|
-
├─ 2.
|
|
447
|
+
├─ 2. Ensure /.meta/ directory exists
|
|
412
448
|
│
|
|
413
|
-
├─ 3.
|
|
449
|
+
├─ 3. Migrate legacy .meta/backends.json → .meta/backends/*.json (if exists)
|
|
414
450
|
│
|
|
415
|
-
├─ 4.
|
|
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.
|
|
420
|
-
│ ├─
|
|
421
|
-
│ └─
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
|
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
|
|
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
|
|
19
|
-
import { InMemory } from '@zenfs/core';
|
|
22
|
+
import { createConfigRepo } from 'zen-fs-config';
|
|
20
23
|
|
|
21
|
-
//
|
|
22
|
-
|
|
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
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
//
|
|
47
|
-
repo.
|
|
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
|
-
//
|
|
51
|
-
await repo.
|
|
52
|
-
|
|
79
|
+
// 已注册的副本后端会自动重新连接并同步
|
|
80
|
+
const backends = await repo.getBackends();
|
|
81
|
+
console.log(backends?.backends.map(b => b.id)); // ['local-idb', 'gitee-prod', ...]
|
|
82
|
+
```
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
await repo.publishNodeConfig('node-1');
|
|
84
|
+
### 带初始后端初始化
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
|
|
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/
|
|
67
|
-
├── nodes/{nodeId}/
|
|
105
|
+
├── {appId}/ # 应用私有配置(自动同步到副本)
|
|
106
|
+
├── shared/ # 跨应用共享配置(双向同步)
|
|
107
|
+
├── nodes/{nodeId}/ # 节点本地配置(不同步)
|
|
68
108
|
└── .meta/
|
|
69
|
-
├── backends
|
|
70
|
-
├──
|
|
71
|
-
|
|
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
|
-
| `
|
|
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}/`
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
|
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
|