zen-fs-config 0.1.0 → 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 +129 -0
- package/dist/index.d.mts +174 -6
- package/dist/index.d.ts +174 -6
- package/dist/index.js +1013 -0
- package/dist/index.mjs +983 -0
- package/package.json +1 -1
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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ConflictStrategy, SyncResult, SyncPairStatus } from 'zen-fs-sync';
|
|
1
|
+
import { ConflictStrategy, SyncResult, SyncPairStatus, SyncableFS } from 'zen-fs-sync';
|
|
3
2
|
export { SyncPairStatus, SyncResult } from 'zen-fs-sync';
|
|
3
|
+
import * as node_fs from 'node:fs';
|
|
4
4
|
|
|
5
5
|
/** A single backend in the topology. */
|
|
6
6
|
interface BackendDescriptor {
|
|
@@ -143,15 +143,15 @@ interface IConfigRepo {
|
|
|
143
143
|
/** Write a config value (auto-synced). */
|
|
144
144
|
setConfig(path: string, data: unknown): void;
|
|
145
145
|
/** Read node-local config. */
|
|
146
|
-
getNodeConfig<T = unknown>(nodeId: string, path: string): T
|
|
146
|
+
getNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
147
147
|
/** Write node-local config (no auto-sync). */
|
|
148
|
-
setNodeConfig(nodeId: string, path: string, data: unknown): void
|
|
148
|
+
setNodeConfig(nodeId: string, path: string, data: unknown): Promise<void>;
|
|
149
149
|
/** Publish node-local config to sync backends (one-time, for debugging). */
|
|
150
150
|
publishNodeConfig(nodeId: string, options?: {
|
|
151
151
|
paths?: string[];
|
|
152
152
|
}): Promise<SyncResult>;
|
|
153
153
|
/** Peek at another node's published config (read-only). */
|
|
154
|
-
peekNodeConfig<T = unknown>(nodeId: string, path: string): T
|
|
154
|
+
peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
155
155
|
/** Manually flush all pending sync operations. */
|
|
156
156
|
flush(): Promise<SyncResult[]>;
|
|
157
157
|
/** Get sync status for all registered sync pairs. */
|
|
@@ -164,4 +164,172 @@ interface IConfigRepo {
|
|
|
164
164
|
dispose(): Promise<void>;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
/**
|
|
168
|
+
* zen-fs-config — Config Serializers
|
|
169
|
+
*
|
|
170
|
+
* Handles serialization/deserialization between JS values and file bytes.
|
|
171
|
+
* The default serializer handles .json, .txt, and unknown extensions.
|
|
172
|
+
* Users can provide a custom ConfigSerializer via ConfigRepoOptions.
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Extended serializer that also accepts an optional path hint for routing.
|
|
177
|
+
* The core ConfigSerializer interface only takes `data`, but internally
|
|
178
|
+
* we use the path to pick the right serializer.
|
|
179
|
+
*/
|
|
180
|
+
interface PathAwareSerializer extends ConfigSerializer {
|
|
181
|
+
serialize(data: unknown, path?: string): Uint8Array;
|
|
182
|
+
deserialize(raw: Uint8Array, path?: string): unknown;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a serializer chain from a user-provided serializer + defaults.
|
|
186
|
+
* The first serializer whose `canHandle()` returns true wins.
|
|
187
|
+
*/
|
|
188
|
+
declare function createSerializerChain(custom?: ConfigSerializer): PathAwareSerializer;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Map a config key to a file path.
|
|
192
|
+
*
|
|
193
|
+
* - `/db/host` → `/db/host.json` (append .json if no extension)
|
|
194
|
+
* - `/readme.md` → `/readme.md` (preserve existing extension)
|
|
195
|
+
*/
|
|
196
|
+
declare function configKeyToFilePath(configPath: string): string;
|
|
197
|
+
/**
|
|
198
|
+
* Extract the file extension (including the dot), or empty string.
|
|
199
|
+
*/
|
|
200
|
+
declare function getExtension(path: string): string;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* zen-fs-config — ConfigRepo Implementation
|
|
204
|
+
*
|
|
205
|
+
* Core implementation of IConfigRepo and the createConfigRepo factory.
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
interface MinimalAsyncFS {
|
|
209
|
+
readFile(path: string, ...args: any[]): Promise<any>;
|
|
210
|
+
writeFile(path: string, data: any, options?: any): Promise<void>;
|
|
211
|
+
readdir(path: string): Promise<string[]>;
|
|
212
|
+
stat(path: string): Promise<any>;
|
|
213
|
+
exists(path: string): Promise<boolean>;
|
|
214
|
+
mkdir(path: string, options?: any): Promise<any>;
|
|
215
|
+
unlink(path: string): Promise<void>;
|
|
216
|
+
rmdir?(path: string): Promise<void>;
|
|
217
|
+
rename?(oldPath: string, newPath: string): Promise<void>;
|
|
218
|
+
}
|
|
219
|
+
declare class ConfigRepo implements IConfigRepo {
|
|
220
|
+
readonly appId: string;
|
|
221
|
+
readonly nodeId: string;
|
|
222
|
+
/** Chroot-isolated fs for app-facing API. Typed as `any` to match `typeof import('node:fs')` duck-typically. */
|
|
223
|
+
readonly fs: any;
|
|
224
|
+
private cachedFS;
|
|
225
|
+
private fullFS;
|
|
226
|
+
private serializer;
|
|
227
|
+
private syncEngine;
|
|
228
|
+
private replicaBackends;
|
|
229
|
+
private onConflictCallback?;
|
|
230
|
+
private disposed;
|
|
231
|
+
private configCache;
|
|
232
|
+
constructor(appId: string, nodeId: string, cachedFS: MinimalAsyncFS, serializer: PathAwareSerializer, onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>);
|
|
233
|
+
load(rawConfig?: string): Promise<void>;
|
|
234
|
+
getConfig<T = unknown>(path: string): T;
|
|
235
|
+
setConfig(path: string, data: unknown): void;
|
|
236
|
+
getNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
237
|
+
setNodeConfig(nodeId: string, path: string, data: unknown): Promise<void>;
|
|
238
|
+
publishNodeConfig(nodeId: string, options?: {
|
|
239
|
+
paths?: string[];
|
|
240
|
+
}): Promise<SyncResult>;
|
|
241
|
+
peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
242
|
+
flush(): Promise<SyncResult[]>;
|
|
243
|
+
getSyncStatuses(): Map<string, SyncPairStatus>;
|
|
244
|
+
resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
|
|
245
|
+
listConflicts(): Promise<ConflictArchive[]>;
|
|
246
|
+
dispose(): Promise<void>;
|
|
247
|
+
setupSync(rules: SyncRule[], backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
|
|
248
|
+
private persistConfig;
|
|
249
|
+
private reloadConfigCache;
|
|
250
|
+
private handleConflict;
|
|
251
|
+
private ensureDir;
|
|
252
|
+
private walkDir;
|
|
253
|
+
writeMetaFile(path: string, data: BackendsMeta | SyncRulesMeta): Promise<void>;
|
|
254
|
+
readMetaFile<T>(path: string): Promise<T | null>;
|
|
255
|
+
private tryParse;
|
|
256
|
+
private assertNotDisposed;
|
|
257
|
+
}
|
|
258
|
+
declare function createConfigRepo(appId: string, options: ConfigRepoOptions): Promise<IConfigRepo>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* zen-fs-config — Backend Registry
|
|
262
|
+
*
|
|
263
|
+
* A pluggable registry that maps backend type names to factory functions.
|
|
264
|
+
* Built-in support for ZenFS backends (InMemory, IndexedDB, etc.)
|
|
265
|
+
* loaded from @zenfs/core.
|
|
266
|
+
*
|
|
267
|
+
* Users can register custom backends via `registerBackend()`.
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
type BackendFactory = (options: Record<string, unknown>) => Promise<BackendInstance>;
|
|
271
|
+
/**
|
|
272
|
+
* The minimal interface a backend instance must satisfy.
|
|
273
|
+
* Matches zen-fs-cache's CacheableFileSystem requirements.
|
|
274
|
+
*/
|
|
275
|
+
interface BackendInstance {
|
|
276
|
+
readFile(path: string, ...args: any[]): Promise<any>;
|
|
277
|
+
writeFile(path: string, data: string | Uint8Array | ArrayBuffer, options?: any): Promise<void>;
|
|
278
|
+
readdir(path: string): Promise<string[]>;
|
|
279
|
+
stat(path: string, ...args: any[]): Promise<any>;
|
|
280
|
+
exists(path: string): Promise<boolean>;
|
|
281
|
+
mkdir(path: string, options?: any): Promise<any>;
|
|
282
|
+
unlink(path: string): Promise<void>;
|
|
283
|
+
rmdir(path: string): Promise<void>;
|
|
284
|
+
rename?(oldPath: string, newPath: string): Promise<void>;
|
|
285
|
+
readFileMeta?(path: string, opts?: any): Promise<any>;
|
|
286
|
+
getRevision?(path: string): Promise<string | number | undefined>;
|
|
287
|
+
}
|
|
288
|
+
declare function registerBackend(type: string, factory: BackendFactory): void;
|
|
289
|
+
declare function createBackend(descriptor: Pick<BackendDescriptor, 'type' | 'options'>): Promise<BackendInstance>;
|
|
290
|
+
declare function hasBackend(type: string): boolean;
|
|
291
|
+
declare function listBackends(): string[];
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* zen-fs-config — Sidecar Version File Management
|
|
295
|
+
*
|
|
296
|
+
* Each config file has a companion .version file for version-based change
|
|
297
|
+
* detection and conflict resolution.
|
|
298
|
+
*
|
|
299
|
+
* Config file: /app-a/db.json
|
|
300
|
+
* Version file: /app-a/.db.json.version
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Compute the sidecar version file path from a config file path.
|
|
305
|
+
*
|
|
306
|
+
* /app-a/db.json → /app-a/.db.json.version
|
|
307
|
+
* /shared/flags.json → /shared/.flags.json.version
|
|
308
|
+
* /nodes/s1/env.json → /nodes/s1/.env.json.version
|
|
309
|
+
*/
|
|
310
|
+
declare function versionPathFor(configFilePath: string): string;
|
|
311
|
+
/**
|
|
312
|
+
* Compute SHA-256 hash of a Uint8Array.
|
|
313
|
+
* Returns "sha256:" prefix + hex digest.
|
|
314
|
+
*/
|
|
315
|
+
declare function sha256(data: Uint8Array): Promise<string>;
|
|
316
|
+
/**
|
|
317
|
+
* Read and parse a version file. Returns null if it doesn't exist or is invalid.
|
|
318
|
+
*/
|
|
319
|
+
declare function readVersion(fs: SyncableFS, versionFilePath: string): Promise<VersionMeta | null>;
|
|
320
|
+
/**
|
|
321
|
+
* Write a version file.
|
|
322
|
+
*/
|
|
323
|
+
declare function writeVersion(fs: SyncableFS, versionFilePath: string, meta: VersionMeta): Promise<void>;
|
|
324
|
+
/**
|
|
325
|
+
* Increment version for a config file write.
|
|
326
|
+
*/
|
|
327
|
+
declare function incrementVersion(fs: SyncableFS, configFilePath: string, newContent: Uint8Array, author: string): Promise<VersionMeta>;
|
|
328
|
+
/**
|
|
329
|
+
* Verify that the version file's hash matches the actual file content.
|
|
330
|
+
* If mismatch, auto-increment version and return updated meta.
|
|
331
|
+
* If version file doesn't exist, return null.
|
|
332
|
+
*/
|
|
333
|
+
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
334
|
+
|
|
335
|
+
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, verifyOrRepairVersion, versionPathFor, writeVersion };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ConflictStrategy, SyncResult, SyncPairStatus } from 'zen-fs-sync';
|
|
1
|
+
import { ConflictStrategy, SyncResult, SyncPairStatus, SyncableFS } from 'zen-fs-sync';
|
|
3
2
|
export { SyncPairStatus, SyncResult } from 'zen-fs-sync';
|
|
3
|
+
import * as node_fs from 'node:fs';
|
|
4
4
|
|
|
5
5
|
/** A single backend in the topology. */
|
|
6
6
|
interface BackendDescriptor {
|
|
@@ -143,15 +143,15 @@ interface IConfigRepo {
|
|
|
143
143
|
/** Write a config value (auto-synced). */
|
|
144
144
|
setConfig(path: string, data: unknown): void;
|
|
145
145
|
/** Read node-local config. */
|
|
146
|
-
getNodeConfig<T = unknown>(nodeId: string, path: string): T
|
|
146
|
+
getNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
147
147
|
/** Write node-local config (no auto-sync). */
|
|
148
|
-
setNodeConfig(nodeId: string, path: string, data: unknown): void
|
|
148
|
+
setNodeConfig(nodeId: string, path: string, data: unknown): Promise<void>;
|
|
149
149
|
/** Publish node-local config to sync backends (one-time, for debugging). */
|
|
150
150
|
publishNodeConfig(nodeId: string, options?: {
|
|
151
151
|
paths?: string[];
|
|
152
152
|
}): Promise<SyncResult>;
|
|
153
153
|
/** Peek at another node's published config (read-only). */
|
|
154
|
-
peekNodeConfig<T = unknown>(nodeId: string, path: string): T
|
|
154
|
+
peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
155
155
|
/** Manually flush all pending sync operations. */
|
|
156
156
|
flush(): Promise<SyncResult[]>;
|
|
157
157
|
/** Get sync status for all registered sync pairs. */
|
|
@@ -164,4 +164,172 @@ interface IConfigRepo {
|
|
|
164
164
|
dispose(): Promise<void>;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
/**
|
|
168
|
+
* zen-fs-config — Config Serializers
|
|
169
|
+
*
|
|
170
|
+
* Handles serialization/deserialization between JS values and file bytes.
|
|
171
|
+
* The default serializer handles .json, .txt, and unknown extensions.
|
|
172
|
+
* Users can provide a custom ConfigSerializer via ConfigRepoOptions.
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Extended serializer that also accepts an optional path hint for routing.
|
|
177
|
+
* The core ConfigSerializer interface only takes `data`, but internally
|
|
178
|
+
* we use the path to pick the right serializer.
|
|
179
|
+
*/
|
|
180
|
+
interface PathAwareSerializer extends ConfigSerializer {
|
|
181
|
+
serialize(data: unknown, path?: string): Uint8Array;
|
|
182
|
+
deserialize(raw: Uint8Array, path?: string): unknown;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a serializer chain from a user-provided serializer + defaults.
|
|
186
|
+
* The first serializer whose `canHandle()` returns true wins.
|
|
187
|
+
*/
|
|
188
|
+
declare function createSerializerChain(custom?: ConfigSerializer): PathAwareSerializer;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Map a config key to a file path.
|
|
192
|
+
*
|
|
193
|
+
* - `/db/host` → `/db/host.json` (append .json if no extension)
|
|
194
|
+
* - `/readme.md` → `/readme.md` (preserve existing extension)
|
|
195
|
+
*/
|
|
196
|
+
declare function configKeyToFilePath(configPath: string): string;
|
|
197
|
+
/**
|
|
198
|
+
* Extract the file extension (including the dot), or empty string.
|
|
199
|
+
*/
|
|
200
|
+
declare function getExtension(path: string): string;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* zen-fs-config — ConfigRepo Implementation
|
|
204
|
+
*
|
|
205
|
+
* Core implementation of IConfigRepo and the createConfigRepo factory.
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
interface MinimalAsyncFS {
|
|
209
|
+
readFile(path: string, ...args: any[]): Promise<any>;
|
|
210
|
+
writeFile(path: string, data: any, options?: any): Promise<void>;
|
|
211
|
+
readdir(path: string): Promise<string[]>;
|
|
212
|
+
stat(path: string): Promise<any>;
|
|
213
|
+
exists(path: string): Promise<boolean>;
|
|
214
|
+
mkdir(path: string, options?: any): Promise<any>;
|
|
215
|
+
unlink(path: string): Promise<void>;
|
|
216
|
+
rmdir?(path: string): Promise<void>;
|
|
217
|
+
rename?(oldPath: string, newPath: string): Promise<void>;
|
|
218
|
+
}
|
|
219
|
+
declare class ConfigRepo implements IConfigRepo {
|
|
220
|
+
readonly appId: string;
|
|
221
|
+
readonly nodeId: string;
|
|
222
|
+
/** Chroot-isolated fs for app-facing API. Typed as `any` to match `typeof import('node:fs')` duck-typically. */
|
|
223
|
+
readonly fs: any;
|
|
224
|
+
private cachedFS;
|
|
225
|
+
private fullFS;
|
|
226
|
+
private serializer;
|
|
227
|
+
private syncEngine;
|
|
228
|
+
private replicaBackends;
|
|
229
|
+
private onConflictCallback?;
|
|
230
|
+
private disposed;
|
|
231
|
+
private configCache;
|
|
232
|
+
constructor(appId: string, nodeId: string, cachedFS: MinimalAsyncFS, serializer: PathAwareSerializer, onConflict?: (conflict: ConflictInfo) => Promise<unknown | null>);
|
|
233
|
+
load(rawConfig?: string): Promise<void>;
|
|
234
|
+
getConfig<T = unknown>(path: string): T;
|
|
235
|
+
setConfig(path: string, data: unknown): void;
|
|
236
|
+
getNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
237
|
+
setNodeConfig(nodeId: string, path: string, data: unknown): Promise<void>;
|
|
238
|
+
publishNodeConfig(nodeId: string, options?: {
|
|
239
|
+
paths?: string[];
|
|
240
|
+
}): Promise<SyncResult>;
|
|
241
|
+
peekNodeConfig<T = unknown>(nodeId: string, path: string): Promise<T>;
|
|
242
|
+
flush(): Promise<SyncResult[]>;
|
|
243
|
+
getSyncStatuses(): Map<string, SyncPairStatus>;
|
|
244
|
+
resolveConflict(conflictId: string, mergedContent: unknown): Promise<void>;
|
|
245
|
+
listConflicts(): Promise<ConflictArchive[]>;
|
|
246
|
+
dispose(): Promise<void>;
|
|
247
|
+
setupSync(rules: SyncRule[], backends: BackendDescriptor[], primaryBackendId: string): Promise<void>;
|
|
248
|
+
private persistConfig;
|
|
249
|
+
private reloadConfigCache;
|
|
250
|
+
private handleConflict;
|
|
251
|
+
private ensureDir;
|
|
252
|
+
private walkDir;
|
|
253
|
+
writeMetaFile(path: string, data: BackendsMeta | SyncRulesMeta): Promise<void>;
|
|
254
|
+
readMetaFile<T>(path: string): Promise<T | null>;
|
|
255
|
+
private tryParse;
|
|
256
|
+
private assertNotDisposed;
|
|
257
|
+
}
|
|
258
|
+
declare function createConfigRepo(appId: string, options: ConfigRepoOptions): Promise<IConfigRepo>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* zen-fs-config — Backend Registry
|
|
262
|
+
*
|
|
263
|
+
* A pluggable registry that maps backend type names to factory functions.
|
|
264
|
+
* Built-in support for ZenFS backends (InMemory, IndexedDB, etc.)
|
|
265
|
+
* loaded from @zenfs/core.
|
|
266
|
+
*
|
|
267
|
+
* Users can register custom backends via `registerBackend()`.
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
type BackendFactory = (options: Record<string, unknown>) => Promise<BackendInstance>;
|
|
271
|
+
/**
|
|
272
|
+
* The minimal interface a backend instance must satisfy.
|
|
273
|
+
* Matches zen-fs-cache's CacheableFileSystem requirements.
|
|
274
|
+
*/
|
|
275
|
+
interface BackendInstance {
|
|
276
|
+
readFile(path: string, ...args: any[]): Promise<any>;
|
|
277
|
+
writeFile(path: string, data: string | Uint8Array | ArrayBuffer, options?: any): Promise<void>;
|
|
278
|
+
readdir(path: string): Promise<string[]>;
|
|
279
|
+
stat(path: string, ...args: any[]): Promise<any>;
|
|
280
|
+
exists(path: string): Promise<boolean>;
|
|
281
|
+
mkdir(path: string, options?: any): Promise<any>;
|
|
282
|
+
unlink(path: string): Promise<void>;
|
|
283
|
+
rmdir(path: string): Promise<void>;
|
|
284
|
+
rename?(oldPath: string, newPath: string): Promise<void>;
|
|
285
|
+
readFileMeta?(path: string, opts?: any): Promise<any>;
|
|
286
|
+
getRevision?(path: string): Promise<string | number | undefined>;
|
|
287
|
+
}
|
|
288
|
+
declare function registerBackend(type: string, factory: BackendFactory): void;
|
|
289
|
+
declare function createBackend(descriptor: Pick<BackendDescriptor, 'type' | 'options'>): Promise<BackendInstance>;
|
|
290
|
+
declare function hasBackend(type: string): boolean;
|
|
291
|
+
declare function listBackends(): string[];
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* zen-fs-config — Sidecar Version File Management
|
|
295
|
+
*
|
|
296
|
+
* Each config file has a companion .version file for version-based change
|
|
297
|
+
* detection and conflict resolution.
|
|
298
|
+
*
|
|
299
|
+
* Config file: /app-a/db.json
|
|
300
|
+
* Version file: /app-a/.db.json.version
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Compute the sidecar version file path from a config file path.
|
|
305
|
+
*
|
|
306
|
+
* /app-a/db.json → /app-a/.db.json.version
|
|
307
|
+
* /shared/flags.json → /shared/.flags.json.version
|
|
308
|
+
* /nodes/s1/env.json → /nodes/s1/.env.json.version
|
|
309
|
+
*/
|
|
310
|
+
declare function versionPathFor(configFilePath: string): string;
|
|
311
|
+
/**
|
|
312
|
+
* Compute SHA-256 hash of a Uint8Array.
|
|
313
|
+
* Returns "sha256:" prefix + hex digest.
|
|
314
|
+
*/
|
|
315
|
+
declare function sha256(data: Uint8Array): Promise<string>;
|
|
316
|
+
/**
|
|
317
|
+
* Read and parse a version file. Returns null if it doesn't exist or is invalid.
|
|
318
|
+
*/
|
|
319
|
+
declare function readVersion(fs: SyncableFS, versionFilePath: string): Promise<VersionMeta | null>;
|
|
320
|
+
/**
|
|
321
|
+
* Write a version file.
|
|
322
|
+
*/
|
|
323
|
+
declare function writeVersion(fs: SyncableFS, versionFilePath: string, meta: VersionMeta): Promise<void>;
|
|
324
|
+
/**
|
|
325
|
+
* Increment version for a config file write.
|
|
326
|
+
*/
|
|
327
|
+
declare function incrementVersion(fs: SyncableFS, configFilePath: string, newContent: Uint8Array, author: string): Promise<VersionMeta>;
|
|
328
|
+
/**
|
|
329
|
+
* Verify that the version file's hash matches the actual file content.
|
|
330
|
+
* If mismatch, auto-increment version and return updated meta.
|
|
331
|
+
* If version file doesn't exist, return null.
|
|
332
|
+
*/
|
|
333
|
+
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
334
|
+
|
|
335
|
+
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, verifyOrRepairVersion, versionPathFor, writeVersion };
|