@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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/README.zh-CN.md +422 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.js +636 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the @objectql/driver-fs package will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.1] - 2024-01-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `initialData` configuration option to pre-populate data on initialization
|
|
9
|
+
- `clear(objectName)` method to clear all data for a specific object
|
|
10
|
+
- `clearAll()` method to clear all data from all objects
|
|
11
|
+
- `invalidateCache(objectName)` method to force cache reload
|
|
12
|
+
- `getCacheSize()` method to get the number of cached objects
|
|
13
|
+
- Chinese documentation (README.zh-CN.md)
|
|
14
|
+
- Better error handling for invalid JSON files
|
|
15
|
+
- Support for empty JSON files
|
|
16
|
+
|
|
17
|
+
### Improved
|
|
18
|
+
- Enhanced JSON parse error messages with more detailed information
|
|
19
|
+
- Better documentation with examples for all new features
|
|
20
|
+
- Added 7 new test cases (total: 36 tests)
|
|
21
|
+
- TypeScript configuration for proper workspace resolution
|
|
22
|
+
|
|
23
|
+
## [0.1.0] - 2024-01-16
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Initial release of FileSystem Driver for ObjectQL
|
|
27
|
+
- One JSON file per table/object type
|
|
28
|
+
- Atomic write operations with temp file + rename strategy
|
|
29
|
+
- Automatic backup files (`.bak`) on write
|
|
30
|
+
- Full query support (filters, sorting, pagination, field projection)
|
|
31
|
+
- Support for all standard Driver interface methods:
|
|
32
|
+
- find, findOne, create, update, delete
|
|
33
|
+
- count, distinct
|
|
34
|
+
- createMany, updateMany, deleteMany
|
|
35
|
+
- Pretty-printed JSON for human readability
|
|
36
|
+
- Zero external dependencies (only @objectql/types)
|
|
37
|
+
- Comprehensive test suite with 30+ test cases
|
|
38
|
+
- Complete documentation and examples
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ObjectQL Contributors (https://github.com/objectql)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# @objectql/driver-fs
|
|
2
|
+
|
|
3
|
+
File System Driver for ObjectQL - JSON file-based persistent storage with one file per table.
|
|
4
|
+
|
|
5
|
+
> **中文文档**: [README.zh-CN.md](./README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
✅ **Persistent Storage** - Data survives process restarts
|
|
10
|
+
✅ **One File Per Table** - Each object type stored in a separate JSON file (e.g., `users.json`, `projects.json`)
|
|
11
|
+
✅ **Human-Readable** - Pretty-printed JSON for easy inspection and debugging
|
|
12
|
+
✅ **Atomic Writes** - Temp file + rename strategy prevents corruption
|
|
13
|
+
✅ **Backup Support** - Automatic backup files (`.bak`) on write
|
|
14
|
+
✅ **Full Query Support** - Filters, sorting, pagination, field projection
|
|
15
|
+
✅ **Zero Database Setup** - No external dependencies or database installation required
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @objectql/driver-fs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { ObjectQL } from '@objectql/core';
|
|
27
|
+
import { FileSystemDriver } from '@objectql/driver-fs';
|
|
28
|
+
|
|
29
|
+
// 1. Initialize Driver
|
|
30
|
+
const driver = new FileSystemDriver({
|
|
31
|
+
dataDir: './data' // Directory where JSON files will be stored
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 2. Initialize ObjectQL
|
|
35
|
+
const app = new ObjectQL({
|
|
36
|
+
datasources: {
|
|
37
|
+
default: driver
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 3. Define Objects
|
|
42
|
+
app.registerObject({
|
|
43
|
+
name: 'users',
|
|
44
|
+
fields: {
|
|
45
|
+
name: { type: 'text', required: true },
|
|
46
|
+
email: { type: 'email' },
|
|
47
|
+
age: { type: 'number' }
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await app.init();
|
|
52
|
+
|
|
53
|
+
// 4. Use the API
|
|
54
|
+
const ctx = app.createContext({ isSystem: true });
|
|
55
|
+
const users = ctx.object('users');
|
|
56
|
+
|
|
57
|
+
// Create
|
|
58
|
+
await users.create({ name: 'Alice', email: 'alice@example.com', age: 30 });
|
|
59
|
+
|
|
60
|
+
// Find
|
|
61
|
+
const allUsers = await users.find({});
|
|
62
|
+
console.log(allUsers);
|
|
63
|
+
|
|
64
|
+
// Query with filters
|
|
65
|
+
const youngUsers = await users.find({
|
|
66
|
+
filters: [['age', '<', 25]]
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
interface FileSystemDriverConfig {
|
|
74
|
+
/** Directory path where JSON files will be stored */
|
|
75
|
+
dataDir: string;
|
|
76
|
+
|
|
77
|
+
/** Enable pretty-print JSON for readability (default: true) */
|
|
78
|
+
prettyPrint?: boolean;
|
|
79
|
+
|
|
80
|
+
/** Enable backup files on write (default: true) */
|
|
81
|
+
enableBackup?: boolean;
|
|
82
|
+
|
|
83
|
+
/** Enable strict mode (throw on missing objects) (default: false) */
|
|
84
|
+
strictMode?: boolean;
|
|
85
|
+
|
|
86
|
+
/** Initial data to populate the store (optional) */
|
|
87
|
+
initialData?: Record<string, any[]>;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Example with Options
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const driver = new FileSystemDriver({
|
|
95
|
+
dataDir: './data',
|
|
96
|
+
prettyPrint: true, // Human-readable JSON
|
|
97
|
+
enableBackup: true, // Create .bak files
|
|
98
|
+
strictMode: false, // Graceful handling of missing records
|
|
99
|
+
initialData: { // Pre-populate with initial data
|
|
100
|
+
users: [
|
|
101
|
+
{ id: 'admin', name: 'Admin User', role: 'admin' }
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## File Storage Format
|
|
108
|
+
|
|
109
|
+
Each object type is stored in a separate JSON file:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
./data/
|
|
113
|
+
├── users.json
|
|
114
|
+
├── users.json.bak (backup)
|
|
115
|
+
├── projects.json
|
|
116
|
+
├── projects.json.bak
|
|
117
|
+
└── tasks.json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### File Content Example (`users.json`)
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
[
|
|
124
|
+
{
|
|
125
|
+
"id": "users-1234567890-1",
|
|
126
|
+
"name": "Alice",
|
|
127
|
+
"email": "alice@example.com",
|
|
128
|
+
"age": 30,
|
|
129
|
+
"created_at": "2024-01-15T10:30:00.000Z",
|
|
130
|
+
"updated_at": "2024-01-15T10:30:00.000Z"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "users-1234567891-2",
|
|
134
|
+
"name": "Bob",
|
|
135
|
+
"email": "bob@example.com",
|
|
136
|
+
"age": 25,
|
|
137
|
+
"created_at": "2024-01-15T11:00:00.000Z",
|
|
138
|
+
"updated_at": "2024-01-15T11:00:00.000Z"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## API Examples
|
|
144
|
+
|
|
145
|
+
### CRUD Operations
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const ctx = app.createContext({ isSystem: true });
|
|
149
|
+
const products = ctx.object('products');
|
|
150
|
+
|
|
151
|
+
// Create
|
|
152
|
+
const product = await products.create({
|
|
153
|
+
name: 'Laptop',
|
|
154
|
+
price: 1000,
|
|
155
|
+
category: 'electronics'
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Find One by ID
|
|
159
|
+
const found = await products.findOne(product.id);
|
|
160
|
+
|
|
161
|
+
// Update
|
|
162
|
+
await products.update(product.id, { price: 950 });
|
|
163
|
+
|
|
164
|
+
// Delete
|
|
165
|
+
await products.delete(product.id);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Querying
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// Filter
|
|
172
|
+
const electronics = await products.find({
|
|
173
|
+
filters: [['category', '=', 'electronics']]
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Multiple filters with OR
|
|
177
|
+
const results = await products.find({
|
|
178
|
+
filters: [
|
|
179
|
+
['price', '<', 500],
|
|
180
|
+
'or',
|
|
181
|
+
['category', '=', 'sale']
|
|
182
|
+
]
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Sorting
|
|
186
|
+
const sorted = await products.find({
|
|
187
|
+
sort: [['price', 'desc']]
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Pagination
|
|
191
|
+
const page1 = await products.find({
|
|
192
|
+
limit: 10,
|
|
193
|
+
skip: 0
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Field Projection
|
|
197
|
+
const names = await products.find({
|
|
198
|
+
fields: ['name', 'price']
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Bulk Operations
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// Create Many
|
|
206
|
+
await products.createMany([
|
|
207
|
+
{ name: 'Item 1', price: 10 },
|
|
208
|
+
{ name: 'Item 2', price: 20 },
|
|
209
|
+
{ name: 'Item 3', price: 30 }
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
// Update Many
|
|
213
|
+
await products.updateMany(
|
|
214
|
+
[['category', '=', 'electronics']], // filters
|
|
215
|
+
{ onSale: true } // update data
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// Delete Many
|
|
219
|
+
await products.deleteMany([
|
|
220
|
+
['price', '<', 10]
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
// Count
|
|
224
|
+
const count = await products.count({
|
|
225
|
+
filters: [['category', '=', 'electronics']]
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Distinct Values
|
|
229
|
+
const categories = await products.distinct('category');
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Supported Query Operators
|
|
233
|
+
|
|
234
|
+
- **Equality**: `=`, `==`, `!=`, `<>`
|
|
235
|
+
- **Comparison**: `>`, `>=`, `<`, `<=`
|
|
236
|
+
- **Membership**: `in`, `nin` (not in)
|
|
237
|
+
- **String Matching**: `like`, `contains`, `startswith`, `endswith`
|
|
238
|
+
- **Range**: `between`
|
|
239
|
+
|
|
240
|
+
## Use Cases
|
|
241
|
+
|
|
242
|
+
### ✅ Ideal For
|
|
243
|
+
|
|
244
|
+
- **Small to Medium Datasets** (< 10k records per object)
|
|
245
|
+
- **Development and Prototyping** with persistent data
|
|
246
|
+
- **Configuration Storage** (settings, metadata)
|
|
247
|
+
- **Embedded Applications** (Electron, Tauri)
|
|
248
|
+
- **Scenarios without Database** (no DB setup required)
|
|
249
|
+
- **Human-Inspectable Data** (easy to debug and modify)
|
|
250
|
+
|
|
251
|
+
### ❌ Not Recommended For
|
|
252
|
+
|
|
253
|
+
- **Large Datasets** (> 10k records per object)
|
|
254
|
+
- **High-Concurrency Writes** (multiple processes writing simultaneously)
|
|
255
|
+
- **Production High-Traffic Apps** (use SQL/MongoDB drivers instead)
|
|
256
|
+
- **Complex Transactions** (use SQL driver with transaction support)
|
|
257
|
+
|
|
258
|
+
## Performance Characteristics
|
|
259
|
+
|
|
260
|
+
- **Read Performance**: O(n) for filtered queries, fast for simple lookups
|
|
261
|
+
- **Write Performance**: O(n) - entire file is rewritten on each update
|
|
262
|
+
- **Storage Format**: Human-readable JSON (larger than binary formats)
|
|
263
|
+
- **Concurrency**: Single-process safe, multi-process requires external locking
|
|
264
|
+
|
|
265
|
+
## Data Safety
|
|
266
|
+
|
|
267
|
+
### Atomic Writes
|
|
268
|
+
|
|
269
|
+
The driver uses a temp file + rename strategy to prevent corruption:
|
|
270
|
+
|
|
271
|
+
1. Write new data to `{file}.tmp`
|
|
272
|
+
2. Rename `{file}.tmp` → `{file}` (atomic operation)
|
|
273
|
+
3. If the process crashes during write, the original file remains intact
|
|
274
|
+
|
|
275
|
+
### Backup Files
|
|
276
|
+
|
|
277
|
+
When `enableBackup: true`, the driver creates `.bak` files:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
users.json ← Current data
|
|
281
|
+
users.json.bak ← Previous version
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
To restore from backup:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
cp data/users.json.bak data/users.json
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Advanced Usage
|
|
291
|
+
|
|
292
|
+
### Custom ID Generation
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Use your own ID
|
|
296
|
+
await products.create({
|
|
297
|
+
id: 'PROD-001',
|
|
298
|
+
name: 'Custom Product'
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Or use _id (MongoDB-style)
|
|
302
|
+
await products.create({
|
|
303
|
+
_id: '507f1f77bcf86cd799439011',
|
|
304
|
+
name: 'Mongo-Style Product'
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Loading Initial Data
|
|
309
|
+
|
|
310
|
+
**Method 1: Provide in configuration**
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
const driver = new FileSystemDriver({
|
|
314
|
+
dataDir: './data',
|
|
315
|
+
initialData: {
|
|
316
|
+
users: [
|
|
317
|
+
{ id: 'admin-001', name: 'Admin User', role: 'admin' }
|
|
318
|
+
],
|
|
319
|
+
settings: [
|
|
320
|
+
{ key: 'theme', value: 'dark' }
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Method 2: Pre-create JSON files**
|
|
327
|
+
|
|
328
|
+
You can pre-populate JSON files:
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
// ./data/users.json
|
|
332
|
+
[
|
|
333
|
+
{
|
|
334
|
+
"id": "admin-001",
|
|
335
|
+
"name": "Admin User",
|
|
336
|
+
"email": "admin@example.com",
|
|
337
|
+
"role": "admin",
|
|
338
|
+
"created_at": "2024-01-01T00:00:00.000Z",
|
|
339
|
+
"updated_at": "2024-01-01T00:00:00.000Z"
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The driver will load this data on startup.
|
|
345
|
+
|
|
346
|
+
### Multiple Data Directories
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Development
|
|
350
|
+
const devDriver = new FileSystemDriver({
|
|
351
|
+
dataDir: './data/dev'
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Testing
|
|
355
|
+
const testDriver = new FileSystemDriver({
|
|
356
|
+
dataDir: './data/test'
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Utility Methods
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// Clear all data for a specific object
|
|
364
|
+
await driver.clear('users');
|
|
365
|
+
|
|
366
|
+
// Clear all data for all objects
|
|
367
|
+
await driver.clearAll();
|
|
368
|
+
|
|
369
|
+
// Invalidate cache for an object
|
|
370
|
+
driver.invalidateCache('users');
|
|
371
|
+
|
|
372
|
+
// Get cache size
|
|
373
|
+
const size = driver.getCacheSize();
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Comparison with Other Drivers
|
|
377
|
+
|
|
378
|
+
| Feature | FileSystem | Memory | SQL | MongoDB |
|
|
379
|
+
|---------|-----------|--------|-----|---------|
|
|
380
|
+
| Persistence | ✅ Yes | ❌ No | ✅ Yes | ✅ Yes |
|
|
381
|
+
| Setup Required | ❌ No | ❌ No | ✅ Yes | ✅ Yes |
|
|
382
|
+
| Human-Readable | ✅ Yes | ❌ No | ❌ No | ⚠️ Partial |
|
|
383
|
+
| Performance (Large Data) | ⚠️ Slow | ✅ Fast | ✅ Fast | ✅ Fast |
|
|
384
|
+
| Transactions | ❌ No | ❌ No | ✅ Yes | ✅ Yes |
|
|
385
|
+
| Best For | Dev/Config | Testing | Production | Production |
|
|
386
|
+
|
|
387
|
+
## Troubleshooting
|
|
388
|
+
|
|
389
|
+
### File Corruption
|
|
390
|
+
|
|
391
|
+
If a JSON file becomes corrupted, restore from backup:
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
cp data/users.json.bak data/users.json
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Permission Issues
|
|
398
|
+
|
|
399
|
+
Ensure the process has read/write permissions:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
chmod 755 ./data
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Large Files
|
|
406
|
+
|
|
407
|
+
If files become too large (> 1MB), consider:
|
|
408
|
+
|
|
409
|
+
1. Splitting data into multiple object types
|
|
410
|
+
2. Using SQL/MongoDB drivers for production
|
|
411
|
+
3. Implementing data archiving strategy
|
|
412
|
+
|
|
413
|
+
## License
|
|
414
|
+
|
|
415
|
+
MIT
|
|
416
|
+
|
|
417
|
+
## Contributing
|
|
418
|
+
|
|
419
|
+
Contributions are welcome! Please open an issue or PR on GitHub.
|
|
420
|
+
|
|
421
|
+
## Related Packages
|
|
422
|
+
|
|
423
|
+
- [@objectql/core](https://www.npmjs.com/package/@objectql/core) - Core ObjectQL engine
|
|
424
|
+
- [@objectql/driver-sql](https://www.npmjs.com/package/@objectql/driver-sql) - SQL driver (PostgreSQL, MySQL, SQLite)
|
|
425
|
+
- [@objectql/driver-mongo](https://www.npmjs.com/package/@objectql/driver-mongo) - MongoDB driver
|
|
426
|
+
- [@objectql/driver-memory](https://www.npmjs.com/package/@objectql/driver-memory) - In-memory driver
|