zen-fs-config 0.1.1 → 0.1.2

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/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # zen-fs-config
2
+
3
+ 基于 ZenFS 的分布式配置管理库。多个应用实例通过任意 ZenFS 后端共享配置,支持应用隔离、共享空间、节点本地配置和冲突安全。
4
+
5
+ **GitHub**: https://github.com/weijia/zen-fs-config
6
+ **NPM**: `zen-fs-config`
7
+ **设计文档**: [DESIGN.md](./DESIGN.md)
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ npm install zen-fs-config @zenfs/core zen-fs-cache zen-fs-sync
13
+ ```
14
+
15
+ ## 快速开始
16
+
17
+ ```typescript
18
+ import { createConfigRepo, registerBackend } from 'zen-fs-config';
19
+ import { InMemory } from '@zenfs/core';
20
+
21
+ // 1. 注册自定义后端(可选,InMemory 已内置)
22
+ registerBackend('InMemory', async (options) => {
23
+ return InMemory.create({ maxSize: options.maxSize as number ?? 100 * 1024 * 1024 });
24
+ });
25
+
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
+ },
44
+ });
45
+
46
+ // 3. 读写配置(同步 API,从内存缓存读取)
47
+ repo.setConfig('/database', { host: 'localhost', port: 5432 });
48
+ const db = repo.getConfig<{ host: string; port: number }>('/database');
49
+
50
+ // 4. 节点本地配置(异步 API,不自动同步)
51
+ await repo.setNodeConfig('node-1', '/debug', { level: 'verbose' });
52
+ const debug = await repo.getNodeConfig('node-1', '/debug');
53
+
54
+ // 5. 发布节点配置到同步后端(用于调试)
55
+ await repo.publishNodeConfig('node-1');
56
+
57
+ // 6. 清理
58
+ await repo.dispose();
59
+ ```
60
+
61
+ ## 目录结构
62
+
63
+ ```
64
+ /
65
+ ├── {appId}/ # 应用私有配置(单向同步,每个应用只能读写自己的)
66
+ ├── shared/ # 跨应用共享配置(双向同步,merge 冲突策略)
67
+ ├── nodes/{nodeId}/ # 节点本地配置(不同步,除非手动 publish)
68
+ └── .meta/
69
+ ├── backends.json # 后端拓扑(自描述,任意后端可引导)
70
+ ├── sync-rules.json
71
+ └── .conflicts/ # 冲突归档(双方内容都保存,永不丢失)
72
+ ```
73
+
74
+ 每个配置文件有 sidecar 版本文件:`db.json` → `.db.json.version`(版本号 + SHA-256 哈希)。
75
+
76
+ ## 核心 API
77
+
78
+ | 方法 | 说明 |
79
+ |---|---|
80
+ | `getConfig<T>(path)` | 同步读取应用配置(从内存缓存) |
81
+ | `setConfig(path, data)` | 同步写入应用配置(异步持久化 + 自动同步) |
82
+ | `getNodeConfig<T>(nodeId, path)` | 异步读取节点本地配置 |
83
+ | `setNodeConfig(nodeId, path, data)` | 异步写入节点本地配置(不同步) |
84
+ | `publishNodeConfig(nodeId)` | 将节点配置一次性同步到所有后端 |
85
+ | `peekNodeConfig<T>(nodeId, path)` | 只读查看其他节点的已发布配置 |
86
+ | `flush()` | 手动触发所有同步 |
87
+ | `listConflicts()` | 列出所有冲突归档 |
88
+ | `resolveConflict(id, merged)` | 用合并内容解决冲突 |
89
+ | `fs.promises.*` | 标准 fs API,chroot 隔离到 `/{appId}/` 和 `/shared/` |
90
+ | `dispose()` | 停止同步、释放资源 |
91
+
92
+ ## 后端注册
93
+
94
+ ```typescript
95
+ import { registerBackend } from 'zen-fs-config';
96
+
97
+ // 注册 S3 后端
98
+ registerBackend('S3Bucket', async (options) => {
99
+ const { S3Bucket } = await import('@zenfs/core');
100
+ return S3Bucket.create(options);
101
+ });
102
+
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
+ });
117
+ ```
118
+
119
+ ## 依赖
120
+
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` | 跨后端同步引擎 |
126
+
127
+ ## License
128
+
129
+ MIT
package/dist/index.d.mts CHANGED
@@ -267,15 +267,10 @@ declare function createConfigRepo(appId: string, options: ConfigRepoOptions): Pr
267
267
  * Users can register custom backends via `registerBackend()`.
268
268
  */
269
269
 
270
- /**
271
- * A factory function that creates a file system instance from options.
272
- * The returned value must satisfy SyncableFS (and ideally CacheableFileSystem).
273
- */
274
270
  type BackendFactory = (options: Record<string, unknown>) => Promise<BackendInstance>;
275
271
  /**
276
272
  * The minimal interface a backend instance must satisfy.
277
- * Combines SyncableFS (from zen-fs-sync) with the write signature
278
- * needed by CachedFileSystem.
273
+ * Matches zen-fs-cache's CacheableFileSystem requirements.
279
274
  */
280
275
  interface BackendInstance {
281
276
  readFile(path: string, ...args: any[]): Promise<any>;
@@ -285,26 +280,14 @@ interface BackendInstance {
285
280
  exists(path: string): Promise<boolean>;
286
281
  mkdir(path: string, options?: any): Promise<any>;
287
282
  unlink(path: string): Promise<void>;
288
- rmdir?(path: string): Promise<void>;
283
+ rmdir(path: string): Promise<void>;
289
284
  rename?(oldPath: string, newPath: string): Promise<void>;
290
285
  readFileMeta?(path: string, opts?: any): Promise<any>;
291
286
  getRevision?(path: string): Promise<string | number | undefined>;
292
287
  }
293
- /**
294
- * Register a backend factory by type name.
295
- */
296
288
  declare function registerBackend(type: string, factory: BackendFactory): void;
297
- /**
298
- * Create a backend instance from a descriptor.
299
- */
300
289
  declare function createBackend(descriptor: Pick<BackendDescriptor, 'type' | 'options'>): Promise<BackendInstance>;
301
- /**
302
- * Check if a backend type is registered.
303
- */
304
290
  declare function hasBackend(type: string): boolean;
305
- /**
306
- * List all registered backend type names.
307
- */
308
291
  declare function listBackends(): string[];
309
292
 
310
293
  /**
package/dist/index.d.ts CHANGED
@@ -267,15 +267,10 @@ declare function createConfigRepo(appId: string, options: ConfigRepoOptions): Pr
267
267
  * Users can register custom backends via `registerBackend()`.
268
268
  */
269
269
 
270
- /**
271
- * A factory function that creates a file system instance from options.
272
- * The returned value must satisfy SyncableFS (and ideally CacheableFileSystem).
273
- */
274
270
  type BackendFactory = (options: Record<string, unknown>) => Promise<BackendInstance>;
275
271
  /**
276
272
  * The minimal interface a backend instance must satisfy.
277
- * Combines SyncableFS (from zen-fs-sync) with the write signature
278
- * needed by CachedFileSystem.
273
+ * Matches zen-fs-cache's CacheableFileSystem requirements.
279
274
  */
280
275
  interface BackendInstance {
281
276
  readFile(path: string, ...args: any[]): Promise<any>;
@@ -285,26 +280,14 @@ interface BackendInstance {
285
280
  exists(path: string): Promise<boolean>;
286
281
  mkdir(path: string, options?: any): Promise<any>;
287
282
  unlink(path: string): Promise<void>;
288
- rmdir?(path: string): Promise<void>;
283
+ rmdir(path: string): Promise<void>;
289
284
  rename?(oldPath: string, newPath: string): Promise<void>;
290
285
  readFileMeta?(path: string, opts?: any): Promise<any>;
291
286
  getRevision?(path: string): Promise<string | number | undefined>;
292
287
  }
293
- /**
294
- * Register a backend factory by type name.
295
- */
296
288
  declare function registerBackend(type: string, factory: BackendFactory): void;
297
- /**
298
- * Create a backend instance from a descriptor.
299
- */
300
289
  declare function createBackend(descriptor: Pick<BackendDescriptor, 'type' | 'options'>): Promise<BackendInstance>;
301
- /**
302
- * Check if a backend type is registered.
303
- */
304
290
  declare function hasBackend(type: string): boolean;
305
- /**
306
- * List all registered backend type names.
307
- */
308
291
  declare function listBackends(): string[];
309
292
 
310
293
  /**
package/dist/index.js CHANGED
@@ -308,12 +308,7 @@ async function createBackend(descriptor) {
308
308
  `Unknown backend type: "${descriptor.type}". Available types: ${Array.from(registry.keys()).join(", ")}. Use registerBackend() to register a custom backend.`
309
309
  );
310
310
  }
311
- const instance = await factory(descriptor.options);
312
- if (!instance.rmdir) {
313
- instance.rmdir = async (_path) => {
314
- };
315
- }
316
- return instance;
311
+ return factory(descriptor.options);
317
312
  }
318
313
  function hasBackend(type) {
319
314
  return registry.has(type);
@@ -321,11 +316,59 @@ function hasBackend(type) {
321
316
  function listBackends() {
322
317
  return Array.from(registry.keys());
323
318
  }
319
+ function syncToAsync(backend) {
320
+ return {
321
+ readFile(path, ...args) {
322
+ const result = backend.readFile(path, ...args);
323
+ return Promise.resolve(result);
324
+ },
325
+ writeFile(path, data, options) {
326
+ backend.writeFile(path, data, options);
327
+ return Promise.resolve();
328
+ },
329
+ readdir(path) {
330
+ const entries = backend.readdir(path);
331
+ return Promise.resolve(entries.map((e) => typeof e === "string" ? e : e.name));
332
+ },
333
+ stat(path, ...args) {
334
+ return Promise.resolve(backend.stat(path, ...args));
335
+ },
336
+ exists(path) {
337
+ try {
338
+ backend.stat(path);
339
+ return Promise.resolve(true);
340
+ } catch {
341
+ return Promise.resolve(false);
342
+ }
343
+ },
344
+ mkdir(path, options) {
345
+ backend.mkdir(path, options);
346
+ return Promise.resolve();
347
+ },
348
+ unlink(path) {
349
+ backend.unlink(path);
350
+ return Promise.resolve();
351
+ },
352
+ rmdir(path) {
353
+ if (typeof backend.rmdir === "function") {
354
+ backend.rmdir(path);
355
+ }
356
+ return Promise.resolve();
357
+ },
358
+ rename(oldPath, newPath) {
359
+ if (typeof backend.rename === "function") {
360
+ backend.rename(oldPath, newPath);
361
+ }
362
+ return Promise.resolve();
363
+ }
364
+ };
365
+ }
324
366
  registerBackend("InMemory", async (options) => {
325
367
  const { InMemory } = await import("@zenfs/core");
326
368
  const maxSize = options.maxSize ?? 100 * 1024 * 1024;
327
369
  const label = options.label ?? "zen-fs-config";
328
- return InMemory.create({ maxSize, label });
370
+ const fs = InMemory.create({ maxSize, label });
371
+ return syncToAsync(fs);
329
372
  });
330
373
 
331
374
  // src/version.ts
package/dist/index.mjs CHANGED
@@ -261,12 +261,7 @@ async function createBackend(descriptor) {
261
261
  `Unknown backend type: "${descriptor.type}". Available types: ${Array.from(registry.keys()).join(", ")}. Use registerBackend() to register a custom backend.`
262
262
  );
263
263
  }
264
- const instance = await factory(descriptor.options);
265
- if (!instance.rmdir) {
266
- instance.rmdir = async (_path) => {
267
- };
268
- }
269
- return instance;
264
+ return factory(descriptor.options);
270
265
  }
271
266
  function hasBackend(type) {
272
267
  return registry.has(type);
@@ -274,11 +269,59 @@ function hasBackend(type) {
274
269
  function listBackends() {
275
270
  return Array.from(registry.keys());
276
271
  }
272
+ function syncToAsync(backend) {
273
+ return {
274
+ readFile(path, ...args) {
275
+ const result = backend.readFile(path, ...args);
276
+ return Promise.resolve(result);
277
+ },
278
+ writeFile(path, data, options) {
279
+ backend.writeFile(path, data, options);
280
+ return Promise.resolve();
281
+ },
282
+ readdir(path) {
283
+ const entries = backend.readdir(path);
284
+ return Promise.resolve(entries.map((e) => typeof e === "string" ? e : e.name));
285
+ },
286
+ stat(path, ...args) {
287
+ return Promise.resolve(backend.stat(path, ...args));
288
+ },
289
+ exists(path) {
290
+ try {
291
+ backend.stat(path);
292
+ return Promise.resolve(true);
293
+ } catch {
294
+ return Promise.resolve(false);
295
+ }
296
+ },
297
+ mkdir(path, options) {
298
+ backend.mkdir(path, options);
299
+ return Promise.resolve();
300
+ },
301
+ unlink(path) {
302
+ backend.unlink(path);
303
+ return Promise.resolve();
304
+ },
305
+ rmdir(path) {
306
+ if (typeof backend.rmdir === "function") {
307
+ backend.rmdir(path);
308
+ }
309
+ return Promise.resolve();
310
+ },
311
+ rename(oldPath, newPath) {
312
+ if (typeof backend.rename === "function") {
313
+ backend.rename(oldPath, newPath);
314
+ }
315
+ return Promise.resolve();
316
+ }
317
+ };
318
+ }
277
319
  registerBackend("InMemory", async (options) => {
278
320
  const { InMemory } = await import("@zenfs/core");
279
321
  const maxSize = options.maxSize ?? 100 * 1024 * 1024;
280
322
  const label = options.label ?? "zen-fs-config";
281
- return InMemory.create({ maxSize, label });
323
+ const fs = InMemory.create({ maxSize, label });
324
+ return syncToAsync(fs);
282
325
  });
283
326
 
284
327
  // src/version.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-fs-config",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",