@objectql/driver-fs 0.1.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.
@@ -0,0 +1,422 @@
1
+ # @objectql/driver-fs
2
+
3
+ ObjectQL 文件系统驱动 - 基于 JSON 文件的持久化存储,每个表一个文件。
4
+
5
+ ## 特性
6
+
7
+ ✅ **持久化存储** - 数据在进程重启后保留
8
+ ✅ **每表一文件** - 每个对象类型存储在独立的 JSON 文件中(例如:`users.json`、`projects.json`)
9
+ ✅ **人类可读** - 格式化的 JSON,便于检查和调试
10
+ ✅ **原子写入** - 临时文件 + 重命名策略防止数据损坏
11
+ ✅ **备份支持** - 写入时自动创建备份文件(`.bak`)
12
+ ✅ **完整查询支持** - 过滤、排序、分页、字段投影
13
+ ✅ **零数据库配置** - 无需外部依赖或数据库安装
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ npm install @objectql/driver-fs
19
+ ```
20
+
21
+ ## 快速开始
22
+
23
+ ```typescript
24
+ import { ObjectQL } from '@objectql/core';
25
+ import { FileSystemDriver } from '@objectql/driver-fs';
26
+
27
+ // 1. 初始化驱动
28
+ const driver = new FileSystemDriver({
29
+ dataDir: './data' // JSON 文件存储目录
30
+ });
31
+
32
+ // 2. 初始化 ObjectQL
33
+ const app = new ObjectQL({
34
+ datasources: {
35
+ default: driver
36
+ }
37
+ });
38
+
39
+ // 3. 定义对象
40
+ app.registerObject({
41
+ name: 'users',
42
+ fields: {
43
+ name: { type: 'text', required: true },
44
+ email: { type: 'email' },
45
+ age: { type: 'number' }
46
+ }
47
+ });
48
+
49
+ await app.init();
50
+
51
+ // 4. 使用 API
52
+ const ctx = app.createContext({ isSystem: true });
53
+ const users = ctx.object('users');
54
+
55
+ // 创建
56
+ await users.create({ name: 'Alice', email: 'alice@example.com', age: 30 });
57
+
58
+ // 查询
59
+ const allUsers = await users.find({});
60
+ console.log(allUsers);
61
+
62
+ // 带过滤条件查询
63
+ const youngUsers = await users.find({
64
+ filters: [['age', '<', 25]]
65
+ });
66
+ ```
67
+
68
+ ## 配置选项
69
+
70
+ ```typescript
71
+ interface FileSystemDriverConfig {
72
+ /** JSON 文件存储目录路径 */
73
+ dataDir: string;
74
+
75
+ /** 启用格式化 JSON 以提高可读性(默认:true) */
76
+ prettyPrint?: boolean;
77
+
78
+ /** 启用写入时备份文件(默认:true) */
79
+ enableBackup?: boolean;
80
+
81
+ /** 启用严格模式(缺失对象时抛出错误)(默认:false) */
82
+ strictMode?: boolean;
83
+
84
+ /** 初始数据(可选) */
85
+ initialData?: Record<string, any[]>;
86
+ }
87
+ ```
88
+
89
+ ### 配置示例
90
+
91
+ ```typescript
92
+ const driver = new FileSystemDriver({
93
+ dataDir: './data',
94
+ prettyPrint: true, // 人类可读的 JSON
95
+ enableBackup: true, // 创建 .bak 备份文件
96
+ strictMode: false, // 优雅处理缺失记录
97
+ initialData: { // 预加载初始数据
98
+ users: [
99
+ { id: 'admin', name: '管理员', role: 'admin' }
100
+ ]
101
+ }
102
+ });
103
+ ```
104
+
105
+ ## 文件存储格式
106
+
107
+ 每个对象类型存储在独立的 JSON 文件中:
108
+
109
+ ```
110
+ ./data/
111
+ ├── users.json
112
+ ├── users.json.bak (备份)
113
+ ├── projects.json
114
+ ├── projects.json.bak
115
+ └── tasks.json
116
+ ```
117
+
118
+ ### 文件内容示例(`users.json`)
119
+
120
+ ```json
121
+ [
122
+ {
123
+ "id": "users-1234567890-1",
124
+ "name": "Alice",
125
+ "email": "alice@example.com",
126
+ "age": 30,
127
+ "created_at": "2024-01-15T10:30:00.000Z",
128
+ "updated_at": "2024-01-15T10:30:00.000Z"
129
+ },
130
+ {
131
+ "id": "users-1234567891-2",
132
+ "name": "Bob",
133
+ "email": "bob@example.com",
134
+ "age": 25,
135
+ "created_at": "2024-01-15T11:00:00.000Z",
136
+ "updated_at": "2024-01-15T11:00:00.000Z"
137
+ }
138
+ ]
139
+ ```
140
+
141
+ ## API 示例
142
+
143
+ ### CRUD 操作
144
+
145
+ ```typescript
146
+ const ctx = app.createContext({ isSystem: true });
147
+ const products = ctx.object('products');
148
+
149
+ // 创建
150
+ const product = await products.create({
151
+ name: '笔记本电脑',
152
+ price: 1000,
153
+ category: '电子产品'
154
+ });
155
+
156
+ // 查询单个
157
+ const found = await products.findOne(product.id);
158
+
159
+ // 更新
160
+ await products.update(product.id, { price: 950 });
161
+
162
+ // 删除
163
+ await products.delete(product.id);
164
+ ```
165
+
166
+ ### 查询操作
167
+
168
+ ```typescript
169
+ // 过滤
170
+ const electronics = await products.find({
171
+ filters: [['category', '=', '电子产品']]
172
+ });
173
+
174
+ // 多条件 OR 查询
175
+ const results = await products.find({
176
+ filters: [
177
+ ['price', '<', 500],
178
+ 'or',
179
+ ['category', '=', '促销']
180
+ ]
181
+ });
182
+
183
+ // 排序
184
+ const sorted = await products.find({
185
+ sort: [['price', 'desc']]
186
+ });
187
+
188
+ // 分页
189
+ const page1 = await products.find({
190
+ limit: 10,
191
+ skip: 0
192
+ });
193
+
194
+ // 字段投影
195
+ const names = await products.find({
196
+ fields: ['name', 'price']
197
+ });
198
+ ```
199
+
200
+ ### 批量操作
201
+
202
+ ```typescript
203
+ // 批量创建
204
+ await products.createMany([
205
+ { name: '商品 1', price: 10 },
206
+ { name: '商品 2', price: 20 },
207
+ { name: '商品 3', price: 30 }
208
+ ]);
209
+
210
+ // 批量更新
211
+ await products.updateMany(
212
+ [['category', '=', '电子产品']], // 过滤条件
213
+ { onSale: true } // 更新数据
214
+ );
215
+
216
+ // 批量删除
217
+ await products.deleteMany([
218
+ ['price', '<', 10]
219
+ ]);
220
+
221
+ // 计数
222
+ const count = await products.count({
223
+ filters: [['category', '=', '电子产品']]
224
+ });
225
+
226
+ // 去重值
227
+ const categories = await products.distinct('category');
228
+ ```
229
+
230
+ ## 支持的查询操作符
231
+
232
+ - **相等性**:`=`、`==`、`!=`、`<>`
233
+ - **比较**:`>`、`>=`、`<`、`<=`
234
+ - **成员**:`in`、`nin`(不在其中)
235
+ - **字符串匹配**:`like`、`contains`、`startswith`、`endswith`
236
+ - **范围**:`between`
237
+
238
+ ## 使用场景
239
+
240
+ ### ✅ 适用于
241
+
242
+ - **小到中等数据集**(每个对象 < 10k 条记录)
243
+ - **开发和原型设计**,需要持久化数据
244
+ - **配置存储**(设置、元数据)
245
+ - **嵌入式应用**(Electron、Tauri)
246
+ - **无需数据库**的场景(无需数据库配置)
247
+ - **人类可读数据**(易于调试和修改)
248
+
249
+ ### ❌ 不推荐用于
250
+
251
+ - **大型数据集**(每个对象 > 10k 条记录)
252
+ - **高并发写入**(多进程同时写入)
253
+ - **生产环境高流量应用**(使用 SQL/MongoDB 驱动替代)
254
+ - **复杂事务**(使用支持事务的 SQL 驱动)
255
+
256
+ ## 性能特征
257
+
258
+ - **读性能**:过滤查询 O(n),简单查找速度快
259
+ - **写性能**:O(n) - 每次更新重写整个文件
260
+ - **存储格式**:人类可读的 JSON(比二进制格式大)
261
+ - **并发**:单进程安全,多进程需要外部锁
262
+
263
+ ## 数据安全
264
+
265
+ ### 原子写入
266
+
267
+ 驱动使用临时文件 + 重命名策略防止损坏:
268
+
269
+ 1. 将新数据写入 `{file}.tmp`
270
+ 2. 重命名 `{file}.tmp` → `{file}`(原子操作)
271
+ 3. 如果进程在写入过程中崩溃,原始文件保持完整
272
+
273
+ ### 备份文件
274
+
275
+ 当 `enableBackup: true` 时,驱动创建 `.bak` 文件:
276
+
277
+ ```
278
+ users.json ← 当前数据
279
+ users.json.bak ← 上一版本
280
+ ```
281
+
282
+ 从备份恢复:
283
+
284
+ ```bash
285
+ cp data/users.json.bak data/users.json
286
+ ```
287
+
288
+ ## 高级用法
289
+
290
+ ### 自定义 ID 生成
291
+
292
+ ```typescript
293
+ // 使用自己的 ID
294
+ await products.create({
295
+ id: 'PROD-001',
296
+ name: '自定义产品'
297
+ });
298
+
299
+ // 或使用 _id(MongoDB 风格)
300
+ await products.create({
301
+ _id: '507f1f77bcf86cd799439011',
302
+ name: 'Mongo 风格产品'
303
+ });
304
+ ```
305
+
306
+ ### 加载初始数据
307
+
308
+ 方法 1:在配置中提供
309
+
310
+ ```typescript
311
+ const driver = new FileSystemDriver({
312
+ dataDir: './data',
313
+ initialData: {
314
+ users: [
315
+ { id: 'admin-001', name: '管理员', role: 'admin' }
316
+ ],
317
+ settings: [
318
+ { key: 'theme', value: 'dark' }
319
+ ]
320
+ }
321
+ });
322
+ ```
323
+
324
+ 方法 2:预创建 JSON 文件
325
+
326
+ ```json
327
+ // ./data/users.json
328
+ [
329
+ {
330
+ "id": "admin-001",
331
+ "name": "管理员",
332
+ "email": "admin@example.com",
333
+ "role": "admin",
334
+ "created_at": "2024-01-01T00:00:00.000Z",
335
+ "updated_at": "2024-01-01T00:00:00.000Z"
336
+ }
337
+ ]
338
+ ```
339
+
340
+ 驱动将在启动时加载此数据。
341
+
342
+ ### 多个数据目录
343
+
344
+ ```typescript
345
+ // 开发环境
346
+ const devDriver = new FileSystemDriver({
347
+ dataDir: './data/dev'
348
+ });
349
+
350
+ // 测试环境
351
+ const testDriver = new FileSystemDriver({
352
+ dataDir: './data/test'
353
+ });
354
+ ```
355
+
356
+ ### 工具方法
357
+
358
+ ```typescript
359
+ // 清除特定对象的所有数据
360
+ await driver.clear('users');
361
+
362
+ // 清除所有对象的数据
363
+ await driver.clearAll();
364
+
365
+ // 使缓存失效
366
+ driver.invalidateCache('users');
367
+
368
+ // 获取缓存大小
369
+ const size = driver.getCacheSize();
370
+ ```
371
+
372
+ ## 与其他驱动的对比
373
+
374
+ | 特性 | FileSystem | Memory | SQL | MongoDB |
375
+ |------|-----------|--------|-----|---------|
376
+ | 持久化 | ✅ 是 | ❌ 否 | ✅ 是 | ✅ 是 |
377
+ | 需要配置 | ❌ 否 | ❌ 否 | ✅ 是 | ✅ 是 |
378
+ | 人类可读 | ✅ 是 | ❌ 否 | ❌ 否 | ⚠️ 部分 |
379
+ | 性能(大数据) | ⚠️ 慢 | ✅ 快 | ✅ 快 | ✅ 快 |
380
+ | 事务 | ❌ 否 | ❌ 否 | ✅ 是 | ✅ 是 |
381
+ | 最适合 | 开发/配置 | 测试 | 生产 | 生产 |
382
+
383
+ ## 故障排除
384
+
385
+ ### 文件损坏
386
+
387
+ 如果 JSON 文件损坏,从备份恢复:
388
+
389
+ ```bash
390
+ cp data/users.json.bak data/users.json
391
+ ```
392
+
393
+ ### 权限问题
394
+
395
+ 确保进程具有读/写权限:
396
+
397
+ ```bash
398
+ chmod 755 ./data
399
+ ```
400
+
401
+ ### 文件过大
402
+
403
+ 如果文件变得太大(> 1MB),考虑:
404
+
405
+ 1. 将数据拆分为多个对象类型
406
+ 2. 在生产环境使用 SQL/MongoDB 驱动
407
+ 3. 实施数据归档策略
408
+
409
+ ## 许可证
410
+
411
+ MIT
412
+
413
+ ## 贡献
414
+
415
+ 欢迎贡献!请在 GitHub 上开启 issue 或 PR。
416
+
417
+ ## 相关包
418
+
419
+ - [@objectql/core](https://www.npmjs.com/package/@objectql/core) - ObjectQL 核心引擎
420
+ - [@objectql/driver-sql](https://www.npmjs.com/package/@objectql/driver-sql) - SQL 驱动(PostgreSQL、MySQL、SQLite)
421
+ - [@objectql/driver-mongo](https://www.npmjs.com/package/@objectql/driver-mongo) - MongoDB 驱动
422
+ - [@objectql/driver-memory](https://www.npmjs.com/package/@objectql/driver-memory) - 内存驱动
@@ -0,0 +1,162 @@
1
+ /**
2
+ * File System Driver for ObjectQL (Production-Ready)
3
+ *
4
+ * A persistent file-based driver for ObjectQL that stores data in JSON files.
5
+ * Each object type is stored in a separate JSON file for easy inspection and backup.
6
+ *
7
+ * ✅ Production-ready features:
8
+ * - Persistent storage with JSON files
9
+ * - One file per table/object (e.g., users.json, projects.json)
10
+ * - Atomic write operations with temp file + rename strategy
11
+ * - Full query support (filters, sorting, pagination)
12
+ * - Backup on write for data safety
13
+ * - Human-readable JSON format
14
+ *
15
+ * Use Cases:
16
+ * - Small to medium datasets (< 10k records per object)
17
+ * - Development and prototyping with persistent data
18
+ * - Configuration and metadata storage
19
+ * - Embedded applications
20
+ * - Scenarios where database setup is not desired
21
+ */
22
+ import { Driver } from '@objectql/types';
23
+ /**
24
+ * Configuration options for the FileSystem driver.
25
+ */
26
+ export interface FileSystemDriverConfig {
27
+ /** Directory path where JSON files will be stored */
28
+ dataDir: string;
29
+ /** Optional: Enable pretty-print JSON for readability (default: true) */
30
+ prettyPrint?: boolean;
31
+ /** Optional: Enable backup files on write (default: true) */
32
+ enableBackup?: boolean;
33
+ /** Optional: Enable strict mode (throw on missing objects) */
34
+ strictMode?: boolean;
35
+ /** Optional: Initial data to populate the store */
36
+ initialData?: Record<string, any[]>;
37
+ }
38
+ /**
39
+ * FileSystem Driver Implementation
40
+ *
41
+ * Stores ObjectQL documents in JSON files with format:
42
+ * - File: `{dataDir}/{objectName}.json`
43
+ * - Content: Array of records `[{id: "1", ...}, {id: "2", ...}]`
44
+ */
45
+ export declare class FileSystemDriver implements Driver {
46
+ private config;
47
+ private idCounters;
48
+ private cache;
49
+ constructor(config: FileSystemDriverConfig);
50
+ /**
51
+ * Load initial data into the store.
52
+ */
53
+ private loadInitialData;
54
+ /**
55
+ * Get the file path for an object type.
56
+ */
57
+ private getFilePath;
58
+ /**
59
+ * Load records from file into memory cache.
60
+ */
61
+ private loadRecords;
62
+ /**
63
+ * Save records to file with atomic write strategy.
64
+ */
65
+ private saveRecords;
66
+ /**
67
+ * Find multiple records matching the query criteria.
68
+ */
69
+ find(objectName: string, query?: any, options?: any): Promise<any[]>;
70
+ /**
71
+ * Find a single record by ID or query.
72
+ */
73
+ findOne(objectName: string, id: string | number, query?: any, options?: any): Promise<any>;
74
+ /**
75
+ * Create a new record.
76
+ */
77
+ create(objectName: string, data: any, options?: any): Promise<any>;
78
+ /**
79
+ * Update an existing record.
80
+ */
81
+ update(objectName: string, id: string | number, data: any, options?: any): Promise<any>;
82
+ /**
83
+ * Delete a record.
84
+ */
85
+ delete(objectName: string, id: string | number, options?: any): Promise<any>;
86
+ /**
87
+ * Count records matching filters.
88
+ */
89
+ count(objectName: string, filters: any, options?: any): Promise<number>;
90
+ /**
91
+ * Get distinct values for a field.
92
+ */
93
+ distinct(objectName: string, field: string, filters?: any, options?: any): Promise<any[]>;
94
+ /**
95
+ * Create multiple records at once.
96
+ */
97
+ createMany(objectName: string, data: any[], options?: any): Promise<any>;
98
+ /**
99
+ * Update multiple records matching filters.
100
+ */
101
+ updateMany(objectName: string, filters: any, data: any, options?: any): Promise<any>;
102
+ /**
103
+ * Delete multiple records matching filters.
104
+ */
105
+ deleteMany(objectName: string, filters: any, options?: any): Promise<any>;
106
+ /**
107
+ * Disconnect (flush cache).
108
+ */
109
+ disconnect(): Promise<void>;
110
+ /**
111
+ * Clear all data from a specific object.
112
+ * Useful for testing or data reset scenarios.
113
+ */
114
+ clear(objectName: string): Promise<void>;
115
+ /**
116
+ * Clear all data from all objects.
117
+ * Removes all JSON files in the data directory.
118
+ */
119
+ clearAll(): Promise<void>;
120
+ /**
121
+ * Invalidate cache for a specific object.
122
+ * Forces reload from file on next access.
123
+ */
124
+ invalidateCache(objectName: string): void;
125
+ /**
126
+ * Get the size of the cache (number of objects cached).
127
+ */
128
+ getCacheSize(): number;
129
+ /**
130
+ * Apply filters to an array of records.
131
+ *
132
+ * Supports ObjectQL filter format with logical operators (AND/OR):
133
+ * [
134
+ * ['field', 'operator', value],
135
+ * 'or',
136
+ * ['field2', 'operator', value2]
137
+ * ]
138
+ */
139
+ private applyFilters;
140
+ /**
141
+ * Check if a single record matches the filter conditions.
142
+ */
143
+ private matchesFilters;
144
+ /**
145
+ * Evaluate a single filter condition.
146
+ */
147
+ private evaluateCondition;
148
+ /**
149
+ * Apply sorting to an array of records.
150
+ */
151
+ private applySort;
152
+ /**
153
+ * Project specific fields from a document.
154
+ */
155
+ private projectFields;
156
+ /**
157
+ * Generate a unique ID for a record.
158
+ * Uses timestamp + counter for uniqueness.
159
+ * Note: For production use with high-frequency writes, consider using crypto.randomUUID().
160
+ */
161
+ private generateId;
162
+ }