jexidb 2.0.0 → 2.0.1

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 CHANGED
@@ -1,556 +1,524 @@
1
- # JexiDB - Pure JavaScript JSONL Database
2
-
3
- **JexiDB** is a lightweight, high-performance JSONL (JSON Lines) database for Node.js built in pure JavaScript that provides fast data storage and retrieval with persistent indexing.
4
-
5
- ## 🚀 Features
6
-
7
- - **JSONL Architecture**: Each database is a single JSONL file for simplicity and portability
8
- - **Persistent Indexes**: Fast searches with disk-persisted indexes that don't need rebuilding
9
- - **Point Reading**: Efficient memory usage - only reads necessary data
10
- - **Rich Query API**: Support for complex queries with operators, sorting, and pagination
11
- - **Automatic Integrity Validation**: Built-in data integrity checking and repair
12
- - **Event System**: Real-time notifications for database operations
13
- - **Legacy Compatibility**: Automatic migration from JexiDB 1.x databases
14
- - **Pure JavaScript**: No native dependencies, works everywhere, easy to deploy
15
-
16
- ## 📦 Installation
17
-
18
- ```bash
19
- npm install jexidb
20
- ```
21
-
22
- ## 🚀 Quick Start
23
-
24
- ```javascript
25
- // import { Database } from 'jexidb'
26
- const { Database } = require('jexidb');
27
-
28
- // Create database with indexes (supports both .jdb and .jsonl)
29
- const db = new Database('./users.jdb', {
30
- indexes: {
31
- id: 'number',
32
- email: 'string',
33
- age: 'number'
34
- },
35
- autoSave: true,
36
- validateOnInit: true
37
- });
38
-
39
- // Initialize
40
- await db.init();
41
-
42
- // Event listeners
43
- db.on('insert', (record, index) => console.log(`Record inserted at index ${index}`));
44
- db.on('update', (record, index) => console.log(`Record updated at index ${index}`));
45
- db.on('save', () => console.log('Changes saved'));
46
-
47
- // Insert data
48
- const user = await db.insert({
49
- id: 1,
50
- name: 'John Doe',
51
- email: 'john@example.com',
52
- age: 30
53
- });
54
-
55
- // Search data (both methods work)
56
- const john = await db.findOne({ id: 1 });
57
- const youngUsers = await db.find({ age: { '<': 30 } });
58
-
59
- // JexiDB 1.x compatible query
60
- const results = await db.query({ name: 'john doe' }, { caseInsensitive: true });
61
-
62
- // Update data
63
- await db.update({ id: 1 }, { age: 31 });
64
-
65
- // Remove data
66
- await db.delete({ id: 1 });
67
-
68
- // Save changes
69
- await db.save();
70
-
71
- // Destroy database
72
- await db.destroy();
73
- ```
74
-
75
- ## 📚 API Reference
76
-
77
- ### Constructor
78
-
79
- ```javascript
80
- const db = new Database(filePath, options);
81
- ```
82
-
83
- **Parameters:**
84
- - `filePath` (string): Path to the main file (.jdb)
85
- - `options` (object): Configuration options
86
-
87
- **Options:**
88
- ```javascript
89
- {
90
- indexes: {}, // Indexes for fields
91
- markDeleted: true, // Mark as deleted instead of physically removing
92
- autoSave: true, // Automatically save after operations
93
- validateOnInit: false // Validate integrity on initialization
94
- }
95
- ```
96
-
97
- ### Main Methods
98
-
99
- #### `init()`
100
- Initializes the database.
101
-
102
- #### `insert(data)`
103
- Inserts a record.
104
-
105
- #### `insertMany(dataArray)`
106
- Inserts multiple records.
107
-
108
- #### `find(criteria, options)` / `query(criteria, options)`
109
- Searches records with optional criteria. Both methods work identically.
110
-
111
- **Supported operators:**
112
- ```javascript
113
- // Comparison
114
- { age: { '>': 25 } }
115
- { age: { '>=': 25 } }
116
- { age: { '<': 30 } }
117
- { age: { '<=': 30 } }
118
- { age: { '!=': 25 } }
119
-
120
- // Arrays
121
- { tags: { in: ['developer', 'admin'] } }
122
- { tags: { nin: ['designer'] } }
123
-
124
- // Strings
125
- { name: { regex: 'john' } }
126
- { name: { contains: 'john' } }
127
- ```
128
-
129
- **Options:**
130
- ```javascript
131
- {
132
- limit: 10, // Limit results
133
- skip: 5, // Skip records
134
- sort: { age: 1 }, // Sorting (1 = ascending, -1 = descending)
135
- caseInsensitive: false, // Case insensitive matching (query() only)
136
- matchAny: false // OR instead of AND
137
- }
138
- ```
139
-
140
- **JexiDB 1.x Compatibility:**
141
- ```javascript
142
- // Both work identically
143
- const results1 = await db.find({ name: 'John' });
144
- const results2 = await db.query({ name: 'John' });
145
-
146
- // Case insensitive query (JexiDB 1.x style)
147
- const results = await db.query({ name: 'john' }, { caseInsensitive: true });
148
- ```
149
-
150
- #### `findOne(criteria, options)`
151
- Searches for one record.
152
-
153
- #### `update(criteria, updateData, options)`
154
- Updates records.
155
-
156
- #### `delete(criteria, options)`
157
- Removes records.
158
-
159
- **Delete options:**
160
- ```javascript
161
- {
162
- physical: false, // Physically remove instead of marking as deleted
163
- limit: 1 // Limit number of records to delete
164
- }
165
- ```
166
-
167
- #### `count(criteria, options)`
168
- Counts records.
169
-
170
- #### `save()`
171
- Saves pending changes.
172
-
173
- #### `destroy()`
174
- Destroys the database.
175
-
176
- #### `validateIntegrity(options)`
177
- Validates database integrity.
178
-
179
- #### `rebuildIndexes(options)`
180
- Rebuilds indexes.
181
-
182
- #### `getStats()`
183
- Gets detailed statistics.
184
-
185
- ### `walk()` Iterator
186
-
187
- For traversing large volumes of data:
188
-
189
- ```javascript
190
- // Traverse all records
191
- for await (const record of db.walk()) {
192
- console.log(record.name);
193
- }
194
-
195
- // With options
196
- for await (const record of db.walk({
197
- limit: 100,
198
- skip: 50,
199
- includeDeleted: false
200
- })) {
201
- console.log(record.name);
202
- }
203
- ```
204
-
205
- ### Properties
206
-
207
- #### `length`
208
- Total number of records.
209
-
210
- #### `indexStats`
211
- Index statistics.
212
-
213
- ### Events
214
-
215
- ```javascript
216
- db.on('init', () => console.log('Database initialized'));
217
- db.on('insert', (record, index) => console.log('Record inserted'));
218
- db.on('update', (record, index) => console.log('Record updated'));
219
- db.on('delete', (record, index) => console.log('Record deleted'));
220
- db.on('before-save', () => console.log('Before save'));
221
- db.on('save', () => console.log('Save completed'));
222
- db.on('destroy', () => console.log('Database destroyed'));
223
- ```
224
-
225
- ## 📁 File Structure
226
-
227
- For each database, 2 files are created:
228
-
229
- ```
230
- users.jdb # Data (JSON Lines format)
231
- users.idx.jdb # Compressed persistent indexes
232
- ```
233
-
234
- ### 🔄 Legacy Compatibility
235
-
236
- JexiDB automatically detects and migrates JexiDB 1.x files:
237
-
238
- **Legacy Format (JexiDB 1.x):**
239
- ```
240
- users.jsonl # Data + indexes + offsets in single file
241
- ```
242
-
243
- **New Format (JexiDB):**
244
- ```
245
- users.jdb # Data + offsets
246
- users.idx.jdb # Compressed indexes
247
- ```
248
-
249
-
250
-
251
- ### 🚀 Persistent Indexes
252
-
253
- JexiDB implements **persistent indexes** that are saved to disk:
254
-
255
- **Benefits:**
256
- - **Fast startup**: No need to read all data to rebuild indexes
257
- - **Scalable**: Works well with large databases (100k+ records)
258
- - **Consistent**: Indexes synchronized with data
259
- - **Portable**: Only 2 files to manage
260
- - **Compressed**: Indexes compressed using gzip
261
-
262
- **🔧 How it works:**
263
- 1. **First open**: Indexes are built by reading data
264
- 2. **Save**: Indexes are persisted and compressed to `users.idx.jdb`
265
- 3. **Reopen**: Indexes are loaded instantly from disk
266
- 4. **Fallback**: If index file is corrupted, rebuilds automatically
267
-
268
- ### JSONL Format
269
-
270
- Each line is a valid JSON record:
271
-
272
- ```json
273
- {"id":1,"name":"John","email":"john@example.com","_created":"2024-12-19T10:00:00.000Z","_updated":"2024-12-19T10:00:00.000Z"}
274
- {"id":2,"name":"Jane","email":"jane@example.com","_created":"2024-12-19T10:01:00.000Z","_updated":"2024-12-19T10:01:00.000Z"}
275
- ```
276
-
277
- ## 🔍 Advanced Examples
278
-
279
- ### Complex Search
280
-
281
- ```javascript
282
- // Young users from New York who are developers
283
- const users = await db.find({
284
- age: { '<': 30 },
285
- 'profile.city': 'New York',
286
- tags: { in: ['developer'] }
287
- }, {
288
- sort: { age: 1 },
289
- limit: 10
290
- });
291
- ```
292
-
293
- ### Batch Update
294
-
295
- ```javascript
296
- // Update age of all users from a city
297
- const updated = await db.update(
298
- { 'profile.city': 'New York' },
299
- { 'profile.country': 'USA' }
300
- );
301
- ```
302
-
303
- ### Integrity Validation
304
-
305
- ```javascript
306
- // Validate integrity with details
307
- const integrity = await db.validateIntegrity({
308
- checkData: true,
309
- checkIndexes: true,
310
- checkOffsets: true,
311
- verbose: true
312
- });
313
-
314
- if (!integrity.isValid) {
315
- console.log('Errors:', integrity.errors);
316
- console.log('Warnings:', integrity.warnings);
317
- }
318
- ```
319
-
320
- ### Detailed Statistics
321
-
322
- ```javascript
323
- const stats = await db.getStats();
324
- console.log('File size:', stats.file.size);
325
- console.log('Total records:', stats.summary.totalRecords);
326
- console.log('Indexes:', stats.indexes.indexCount);
327
- ```
328
-
329
- ## 🧪 Tests
330
-
331
- ```bash
332
- npm test
333
- ```
334
-
335
- **Automatic Cleanup**: The test script automatically removes all test files after execution to keep the project directory clean.
336
-
337
- **Manual Cleanup**: If you need to clean up test files manually:
338
- ```bash
339
- npm run test:clean
340
- ```
341
-
342
- **Available Test Scripts**:
343
- - `npm test` - Run all tests with automatic cleanup
344
- - `npm run test:watch` - Run tests in watch mode
345
- - `npm run test:clean` - Clean up test files manually
346
- - `npm run test:optimized` - Run optimized performance tests
347
- - `npm run test:parallel` - Run tests in parallel
348
- - `npm run test:fast` - Run fast tests without isolation
349
-
350
- ## 📈 Performance
351
-
352
- ### JSONL Features
353
-
354
- - **Point reading**: Only reads necessary lines
355
- - **In-memory indexes**: Fast search by indexed fields
356
- - **No complete parsing**: Doesn't load entire file into memory
357
- - **Large volume support**: Scales with millions of records
358
-
359
- ### Comparison: JexiDB vs 1.x
360
-
361
- | Feature | JexiDB | JexiDB 1.x |
362
- |---------|---------------|------------|
363
- | Safe truncation | ✅ | ❌ |
364
- | Consistent offsets | ✅ | ❌ |
365
- | Integrity validation | ✅ | ❌ |
366
- | Isolated tests | ✅ | ❌ |
367
- | No V8 dependency | ✅ | ❌ |
368
- | Similar API | ✅ | ✅ |
369
-
370
- ## 🔧 Utilities
371
-
372
- ```javascript
373
- const { utils } = require('jexidb');
374
-
375
- // Validate JSONL file
376
- const validation = await utils.validateJSONLFile('./data.jsonl');
377
-
378
- // Convert JSON to JSONL (basic)
379
- await utils.convertJSONToJSONL('./data.json', './data.jsonl');
380
-
381
- // Convert JSONL to JSON
382
- await utils.convertJSONLToJSON('./data.jsonl', './data.json');
383
-
384
- // Create JexiDB database with automatic indexes
385
- const result = await utils.createDatabaseFromJSON('./users.json', './users.jsonl', {
386
- autoDetectIndexes: true,
387
- autoIndexFields: ['id', 'email', 'name', 'username']
388
- });
389
-
390
- // Analyze JSON and suggest optimal indexes
391
- const analysis = await utils.analyzeJSONForIndexes('./users.json', 100);
392
- console.log('Recommended indexes:', analysis.suggestions.recommended);
393
-
394
- // Migrate from JexiDB 1.x to JexiDB
395
- await utils.migrateFromJexiDB('./jexidb-v1-database', './users.jsonl');
396
- ```
397
-
398
- ### 🔍 **How Utilities Work**
399
-
400
- #### **1. Basic Conversion (No Indexes)**
401
- ```javascript
402
- // Only converts format - DOES NOT add indexes
403
- await utils.convertJSONToJSONL('./data.json', './data.jsonl');
404
- ```
405
- - ✅ Converts JSON to JSONL
406
- - ❌ **DOES NOT create indexes**
407
- - ❌ **DOES NOT create JexiDB database**
408
- - ✅ Pure JSONL file
409
-
410
- #### **2. Database Creation with Automatic Indexes**
411
- ```javascript
412
- // Create complete JexiDB database with indexes
413
- const result = await utils.createDatabaseFromJSON('./users.json', './users.jsonl', {
414
- autoDetectIndexes: true,
415
- autoIndexFields: ['id', 'email', 'name']
416
- });
417
-
418
- console.log(result);
419
- // {
420
- // success: true,
421
- // recordCount: 1000,
422
- // indexes: ['id', 'email', 'name'],
423
- // dbPath: './users.jsonl'
424
- // }
425
- ```
426
- - ✅ Converts JSON to JSONL
427
- - ✅ **Creates indexes automatically**
428
- - ✅ **Creates complete JexiDB database**
429
- - ✅ File ready for use
430
-
431
- #### **3. Intelligent Index Analysis**
432
- ```javascript
433
- // Analyze data and suggest optimal indexes
434
- const analysis = await utils.analyzeJSONForIndexes('./users.json');
435
-
436
- console.log('Recommended:', analysis.suggestions.recommended);
437
- // [
438
- // { field: 'id', type: 'number', coverage: 100, uniqueness: 100 },
439
- // { field: 'email', type: 'string', coverage: 95, uniqueness: 98 }
440
- // ]
441
- ```
442
-
443
-
444
-
445
- ## 🔄 Migration from JexiDB 1.x
446
-
447
- ### Seamless Migration
448
-
449
- JexiDB is **fully backward compatible** with JexiDB 1.x! You can use the same API:
450
-
451
- ```javascript
452
- // JexiDB 1.x code works unchanged in JexiDB
453
- import { Database } from 'jexidb';
454
-
455
- const db = new Database('./database.jdb', {
456
- indexes: { id: 'number', name: 'string' }
457
- });
458
- await db.init();
459
-
460
- // All JexiDB 1.x methods work:
461
- await db.insert({ id: 1, name: 'John Doe' });
462
- const results = await db.query({ name: 'John Doe' }, { caseInsensitive: true });
463
- await db.update({ id: 1 }, { name: 'John Smith' });
464
- await db.delete({ id: 1 });
465
- await db.save();
466
- ```
467
-
468
- ### File Format Support
469
-
470
- JexiDB supports both file formats:
471
- - **`.jdb`** (preferred) - JexiDB's branded extension
472
- - **`.jsonl`** (standard) - JSON Lines format
473
-
474
- ```javascript
475
- // Both work identically:
476
- const db1 = new Database('./users.jdb', { indexes: { id: 'number' } });
477
- const db2 = new Database('./users.jsonl', { indexes: { id: 'number' } });
478
- ```
479
-
480
- ### Key Improvements
481
-
482
- | Feature | JexiDB 1.x | JexiDB |
483
- |---------|------------|--------------|
484
- | **API Compatibility** | Original | ✅ **100% Backward Compatible** |
485
- | **Query Methods** | `db.query()` | ✅ `db.query()` + `db.find()` |
486
- | **File Format** | `.jdb` (proprietary) | ✅ `.jdb` + `.jsonl` support |
487
- | **Performance** | Basic | ✅ **10-100x faster** |
488
- | **Memory Usage** | Higher | ✅ **25% less memory** |
489
- | **Data Integrity** | Basic | ✅ **Advanced validation** |
490
-
491
- ## 📝 Changelog
492
-
493
- See [CHANGELOG.md](CHANGELOG.md) for complete change history.
494
-
495
- ## 🤝 Contributing
496
-
497
- 1. Fork the project
498
- 2. Create a branch for your feature
499
- 3. Commit your changes
500
- 4. Push to the branch
501
- 5. Open a Pull Request
502
-
503
- ## 📄 License
504
-
505
- MIT License - see [LICENSE](LICENSE) for details.
506
-
507
- ---
508
-
509
- ## 🎯 About JexiDB
510
-
511
- JexiDB maintains the original JexiDB philosophy while fixing bugs and implementing a more robust architecture.
512
-
513
- ### 📈 Version 2.0.0 Changes
514
-
515
- **JexiDB** addresses the problems of version 1.x:
516
-
517
- | JexiDB 1.x Problem | JexiDB Solution |
518
- |---------------------|----------------------|
519
- | Unsafe truncation | ✅ Mandatory truncation after operations |
520
- | Inconsistent offsets | Offsets always recalculated |
521
- | No integrity validation | ✅ Automatic validation |
522
- | V8 dependency | ✅ Pure JavaScript |
523
- | Non-isolated tests | Tests always clean files |
524
- | Inconsistent serialization | ✅ Standardized line breaks |
525
-
526
- ### 🚀 Performance
527
-
528
- **JexiDB** performance compared to version 1.x:
529
-
530
- - **Find operations**: 103x faster
531
- - **Update operations**: 26x faster
532
- - **Insert operations**: 6-11x faster
533
- - **Memory usage**: 25% less memory
534
-
535
- ### 🔧 Simple Migration
536
-
537
- Migration from JexiDB 1.x to JexiDB is simple:
538
-
539
- ```javascript
540
- // Before (JexiDB 1.x)
541
- const { Database } = require('jexidb');
542
- const db = new Database('./database.jdb', {
543
- indexes: { id: 'number', name: 'string' }
544
- });
545
- await db.init();
546
- await db.insert({ id: 1, name: 'John Doe' });
547
- await db.save();
548
-
549
- // Now (JexiDB)
550
- import { Database } from 'jexidb';
551
- const db = new Database('./users.jdb', { indexes: { id: 'number' } });
552
- await db.init();
553
- await db.insert({ id: 1, name: 'John Doe' });
554
- ```
555
-
556
- **We maintain compatibility with the JexiDB 1.x API with improved performance and reliability.**
1
+ # JexiDB - Pure JavaScript JSONL Database
2
+
3
+ **JexiDB** is a lightweight, high-performance JSONL (JSON Lines) database for Node.js built in pure JavaScript that provides fast data storage and retrieval with persistent indexing.
4
+
5
+ ## 🚀 Features
6
+
7
+ - **JSONL Architecture**: Each database is a single JSONL file for simplicity and portability
8
+ - **Persistent Indexes**: Fast searches with disk-persisted indexes that don't need rebuilding
9
+ - **Point Reading**: Efficient memory usage - only reads necessary data
10
+ - **Rich Query API**: Support for complex queries with operators, sorting, and pagination
11
+ - **Automatic Integrity Validation**: Built-in data integrity checking and repair
12
+ - **Event System**: Real-time notifications for database operations
13
+ - **Legacy Compatibility**: Automatic migration from JexiDB 1.x databases
14
+ - **Pure JavaScript**: No native dependencies, works everywhere, easy to deploy
15
+
16
+ ## 📦 Installation
17
+
18
+ ```bash
19
+ npm install jexidb
20
+ ```
21
+
22
+ ## 🚀 Quick Start
23
+
24
+ ```javascript
25
+ // import { Database } from 'jexidb'
26
+ const { Database } = require('jexidb');
27
+
28
+ // Create database with indexes (supports both .jdb and .jsonl)
29
+ const db = new Database('./users.jdb', {
30
+ indexes: {
31
+ id: 'number',
32
+ email: 'string',
33
+ age: 'number'
34
+ },
35
+ autoSave: true,
36
+ validateOnInit: true
37
+ });
38
+
39
+ // Initialize
40
+ await db.init();
41
+
42
+ // Event listeners
43
+ db.on('insert', (record, index) => console.log(`Record inserted at index ${index}`));
44
+ db.on('update', (record, index) => console.log(`Record updated at index ${index}`));
45
+ db.on('save', () => console.log('Changes saved'));
46
+
47
+ // Insert data
48
+ const user = await db.insert({
49
+ id: 1,
50
+ name: 'John Doe',
51
+ email: 'john@example.com',
52
+ age: 30
53
+ });
54
+
55
+ // Search data (both methods work)
56
+ const john = await db.findOne({ id: 1 });
57
+ const youngUsers = await db.find({ age: { '<': 30 } });
58
+
59
+ // JexiDB 1.x compatible query
60
+ const results = await db.query({ name: 'john doe' }, { caseInsensitive: true });
61
+
62
+ // Update data
63
+ await db.update({ id: 1 }, { age: 31 });
64
+
65
+ // Remove data
66
+ await db.delete({ id: 1 });
67
+
68
+ // Save changes
69
+ await db.save();
70
+
71
+ // Destroy database
72
+ await db.destroy();
73
+ ```
74
+
75
+ ## 📚 API Reference
76
+
77
+ ### Constructor
78
+
79
+ ```javascript
80
+ const db = new Database(filePath, options);
81
+ ```
82
+
83
+ **Parameters:**
84
+ - `filePath` (string): Path to the main file (.jdb)
85
+ - `options` (object): Configuration options
86
+
87
+ **Options:**
88
+ ```javascript
89
+ {
90
+ indexes: {}, // Indexes for fields
91
+ markDeleted: true, // Mark as deleted instead of physically removing
92
+ autoSave: true, // Automatically save after operations
93
+ validateOnInit: false // Validate integrity on initialization
94
+ }
95
+ ```
96
+
97
+ ### Main Methods
98
+
99
+ #### `init()`
100
+ Initializes the database.
101
+
102
+ #### `insert(data)`
103
+ Inserts a record.
104
+
105
+ #### `insertMany(dataArray)`
106
+ Inserts multiple records.
107
+
108
+ #### `find(criteria, options)` / `query(criteria, options)`
109
+ Searches records with optional criteria. Both methods work identically.
110
+
111
+ **Supported operators:**
112
+ ```javascript
113
+ // Comparison
114
+ { age: { '>': 25 } }
115
+ { age: { '>=': 25 } }
116
+ { age: { '<': 30 } }
117
+ { age: { '<=': 30 } }
118
+ { age: { '!=': 25 } }
119
+
120
+ // Arrays
121
+ { tags: { in: ['developer', 'admin'] } }
122
+ { tags: { nin: ['designer'] } }
123
+
124
+ // Strings
125
+ { name: { regex: 'john' } }
126
+ { name: { contains: 'john' } }
127
+ ```
128
+
129
+ **Options:**
130
+ ```javascript
131
+ {
132
+ limit: 10, // Limit results
133
+ skip: 5, // Skip records
134
+ sort: { age: 1 }, // Sorting (1 = ascending, -1 = descending)
135
+ caseInsensitive: false, // Case insensitive matching (query() only)
136
+ matchAny: false // OR instead of AND
137
+ }
138
+ ```
139
+
140
+ **JexiDB 1.x Compatibility:**
141
+ ```javascript
142
+ // Both work identically
143
+ const results1 = await db.find({ name: 'John' });
144
+ const results2 = await db.query({ name: 'John' });
145
+
146
+ // Case insensitive query (JexiDB 1.x style)
147
+ const results = await db.query({ name: 'john' }, { caseInsensitive: true });
148
+ ```
149
+
150
+ #### `findOne(criteria, options)`
151
+ Searches for one record.
152
+
153
+ #### `update(criteria, updateData, options)`
154
+ Updates records.
155
+
156
+ #### `delete(criteria, options)`
157
+ Removes records.
158
+
159
+ **Delete options:**
160
+ ```javascript
161
+ {
162
+ physical: false, // Physically remove instead of marking as deleted
163
+ limit: 1 // Limit number of records to delete
164
+ }
165
+ ```
166
+
167
+ #### `count(criteria, options)`
168
+ Counts records.
169
+
170
+ #### `save()`
171
+ Saves pending changes.
172
+
173
+ #### `destroy()`
174
+ Destroys the database.
175
+
176
+ #### `validateIntegrity(options)`
177
+ Validates database integrity.
178
+
179
+ #### `rebuildIndexes(options)`
180
+ Rebuilds indexes.
181
+
182
+ #### `getStats()`
183
+ Gets detailed statistics.
184
+
185
+ ### `walk()` Iterator
186
+
187
+ For traversing large volumes of data:
188
+
189
+ ```javascript
190
+ // Traverse all records
191
+ for await (const record of db.walk()) {
192
+ console.log(record.name);
193
+ }
194
+
195
+ // With options
196
+ for await (const record of db.walk({
197
+ limit: 100,
198
+ skip: 50,
199
+ includeDeleted: false
200
+ })) {
201
+ console.log(record.name);
202
+ }
203
+ ```
204
+
205
+ ### Properties
206
+
207
+ #### `length`
208
+ Total number of records.
209
+
210
+ #### `indexStats`
211
+ Index statistics.
212
+
213
+ ### Events
214
+
215
+ ```javascript
216
+ db.on('init', () => console.log('Database initialized'));
217
+ db.on('insert', (record, index) => console.log('Record inserted'));
218
+ db.on('update', (record, index) => console.log('Record updated'));
219
+ db.on('delete', (record, index) => console.log('Record deleted'));
220
+ db.on('before-save', () => console.log('Before save'));
221
+ db.on('save', () => console.log('Save completed'));
222
+ db.on('destroy', () => console.log('Database destroyed'));
223
+ ```
224
+
225
+ ## 📁 File Structure
226
+
227
+ For each database, 2 files are created:
228
+
229
+ ```
230
+ users.jdb # Data (JSON Lines format)
231
+ users.idx.jdb # Compressed persistent indexes
232
+ ```
233
+
234
+ ### 🔄 Legacy Compatibility
235
+
236
+ JexiDB automatically detects and migrates JexiDB 1.x files:
237
+
238
+ **Legacy Format (JexiDB 1.x):**
239
+ ```
240
+ users.jsonl # Data + indexes + offsets in single file
241
+ ```
242
+
243
+ **New Format (JexiDB):**
244
+ ```
245
+ users.jdb # Data + offsets
246
+ users.idx.jdb # Compressed indexes
247
+ ```
248
+
249
+
250
+
251
+ ### 🚀 Persistent Indexes
252
+
253
+ JexiDB implements **persistent indexes** that are saved to disk:
254
+
255
+ **Benefits:**
256
+ - **Fast startup**: No need to read all data to rebuild indexes
257
+ - **Scalable**: Works well with large databases (100k+ records)
258
+ - **Consistent**: Indexes synchronized with data
259
+ - **Portable**: Only 2 files to manage
260
+ - **Compressed**: Indexes compressed using gzip
261
+
262
+ **🔧 How it works:**
263
+ 1. **First open**: Indexes are built by reading data
264
+ 2. **Save**: Indexes are persisted and compressed to `users.idx.jdb`
265
+ 3. **Reopen**: Indexes are loaded instantly from disk
266
+ 4. **Fallback**: If index file is corrupted, rebuilds automatically
267
+
268
+ ### JSONL Format
269
+
270
+ Each line is a valid JSON record:
271
+
272
+ ```json
273
+ {"id":1,"name":"John","email":"john@example.com","_created":"2024-12-19T10:00:00.000Z","_updated":"2024-12-19T10:00:00.000Z"}
274
+ {"id":2,"name":"Jane","email":"jane@example.com","_created":"2024-12-19T10:01:00.000Z","_updated":"2024-12-19T10:01:00.000Z"}
275
+ ```
276
+
277
+ ## 🔍 Advanced Examples
278
+
279
+ ### Complex Search
280
+
281
+ ```javascript
282
+ // Young users from New York who are developers
283
+ const users = await db.find({
284
+ age: { '<': 30 },
285
+ 'profile.city': 'New York',
286
+ tags: { in: ['developer'] }
287
+ }, {
288
+ sort: { age: 1 },
289
+ limit: 10
290
+ });
291
+ ```
292
+
293
+ ### Batch Update
294
+
295
+ ```javascript
296
+ // Update age of all users from a city
297
+ const updated = await db.update(
298
+ { 'profile.city': 'New York' },
299
+ { 'profile.country': 'USA' }
300
+ );
301
+ ```
302
+
303
+ ### Integrity Validation
304
+
305
+ ```javascript
306
+ // Validate integrity with details
307
+ const integrity = await db.validateIntegrity({
308
+ checkData: true,
309
+ checkIndexes: true,
310
+ checkOffsets: true,
311
+ verbose: true
312
+ });
313
+
314
+ if (!integrity.isValid) {
315
+ console.log('Errors:', integrity.errors);
316
+ console.log('Warnings:', integrity.warnings);
317
+ }
318
+ ```
319
+
320
+ ### Detailed Statistics
321
+
322
+ ```javascript
323
+ const stats = await db.getStats();
324
+ console.log('File size:', stats.file.size);
325
+ console.log('Total records:', stats.summary.totalRecords);
326
+ console.log('Indexes:', stats.indexes.indexCount);
327
+ ```
328
+
329
+ ## 🧪 Tests
330
+
331
+ ```bash
332
+ npm test
333
+ ```
334
+
335
+ **Automatic Cleanup**: The test script automatically removes all test files after execution to keep the project directory clean.
336
+
337
+ **Manual Cleanup**: If you need to clean up test files manually:
338
+ ```bash
339
+ npm run test:clean
340
+ ```
341
+
342
+ **Available Test Scripts**:
343
+ - `npm test` - Run all tests with automatic cleanup
344
+ - `npm run test:watch` - Run tests in watch mode
345
+ - `npm run test:clean` - Clean up test files manually
346
+ - `npm run test:optimized` - Run optimized performance tests
347
+ - `npm run test:parallel` - Run tests in parallel
348
+ - `npm run test:fast` - Run fast tests without isolation
349
+
350
+ ## 📈 Performance
351
+
352
+ ### JSONL Features
353
+
354
+ - **Point reading**: Only reads necessary lines
355
+ - **In-memory indexes**: Fast search by indexed fields
356
+ - **No complete parsing**: Doesn't load entire file into memory
357
+ - **Large volume support**: Scales with millions of records
358
+
359
+ ### Comparison: JexiDB vs 1.x
360
+
361
+ | Feature | JexiDB | JexiDB 1.x |
362
+ |---------|---------------|------------|
363
+ | Safe truncation | ✅ | ❌ |
364
+ | Consistent offsets | ✅ | ❌ |
365
+ | Integrity validation | ✅ | ❌ |
366
+ | Isolated tests | ✅ | ❌ |
367
+ | No V8 dependency | ✅ | ❌ |
368
+ | Similar API | ✅ | ✅ |
369
+
370
+ ## 🔧 Utilities
371
+
372
+ ```javascript
373
+ const { utils } = require('jexidb');
374
+
375
+ // Validate JSONL file
376
+ const validation = await utils.validateJSONLFile('./data.jsonl');
377
+
378
+ // Convert JSON to JSONL (basic)
379
+ await utils.convertJSONToJSONL('./data.json', './data.jsonl');
380
+
381
+ // Convert JSONL to JSON
382
+ await utils.convertJSONLToJSON('./data.jsonl', './data.json');
383
+
384
+ // Create JexiDB database with automatic indexes
385
+ const result = await utils.createDatabaseFromJSON('./users.json', './users.jsonl', {
386
+ autoDetectIndexes: true,
387
+ autoIndexFields: ['id', 'email', 'name', 'username']
388
+ });
389
+
390
+ // Analyze JSON and suggest optimal indexes
391
+ const analysis = await utils.analyzeJSONForIndexes('./users.json', 100);
392
+ console.log('Recommended indexes:', analysis.suggestions.recommended);
393
+
394
+ // Migrate from JexiDB 1.x to JexiDB
395
+ await utils.migrateFromJexiDB('./jexidb-v1-database', './users.jsonl');
396
+ ```
397
+
398
+ ### 🔍 **How Utilities Work**
399
+
400
+ #### **1. Basic Conversion (No Indexes)**
401
+ ```javascript
402
+ // Only converts format - DOES NOT add indexes
403
+ await utils.convertJSONToJSONL('./data.json', './data.jsonl');
404
+ ```
405
+ - ✅ Converts JSON to JSONL
406
+ - ❌ **DOES NOT create indexes**
407
+ - ❌ **DOES NOT create JexiDB database**
408
+ - ✅ Pure JSONL file
409
+
410
+ #### **2. Database Creation with Automatic Indexes**
411
+ ```javascript
412
+ // Create complete JexiDB database with indexes
413
+ const result = await utils.createDatabaseFromJSON('./users.json', './users.jsonl', {
414
+ autoDetectIndexes: true,
415
+ autoIndexFields: ['id', 'email', 'name']
416
+ });
417
+
418
+ console.log(result);
419
+ // {
420
+ // success: true,
421
+ // recordCount: 1000,
422
+ // indexes: ['id', 'email', 'name'],
423
+ // dbPath: './users.jsonl'
424
+ // }
425
+ ```
426
+ - ✅ Converts JSON to JSONL
427
+ - ✅ **Creates indexes automatically**
428
+ - ✅ **Creates complete JexiDB database**
429
+ - ✅ File ready for use
430
+
431
+ #### **3. Intelligent Index Analysis**
432
+ ```javascript
433
+ // Analyze data and suggest optimal indexes
434
+ const analysis = await utils.analyzeJSONForIndexes('./users.json');
435
+
436
+ console.log('Recommended:', analysis.suggestions.recommended);
437
+ // [
438
+ // { field: 'id', type: 'number', coverage: 100, uniqueness: 100 },
439
+ // { field: 'email', type: 'string', coverage: 95, uniqueness: 98 }
440
+ // ]
441
+ ```
442
+
443
+
444
+
445
+ ## 🔄 Migration from JexiDB 1.x
446
+
447
+ ### Seamless Migration
448
+
449
+ JexiDB is **fully backward compatible** with JexiDB 1.x! You can use the same API:
450
+
451
+ ```javascript
452
+ // JexiDB 1.x code works unchanged in JexiDB
453
+ import { Database } from 'jexidb';
454
+
455
+ const db = new Database('./database.jdb', {
456
+ indexes: { id: 'number', name: 'string' }
457
+ });
458
+ await db.init();
459
+
460
+ // All JexiDB 1.x methods work:
461
+ await db.insert({ id: 1, name: 'John Doe' });
462
+ const results = await db.query({ name: 'John Doe' }, { caseInsensitive: true });
463
+ await db.update({ id: 1 }, { name: 'John Smith' });
464
+ await db.delete({ id: 1 });
465
+ await db.save();
466
+ ```
467
+
468
+ ### File Format Support
469
+
470
+ JexiDB supports both file formats:
471
+ - **`.jdb`** (preferred) - JexiDB's branded extension
472
+ - **`.jsonl`** (standard) - JSON Lines format
473
+
474
+ ```javascript
475
+ // Both work identically:
476
+ const db1 = new Database('./users.jdb', { indexes: { id: 'number' } });
477
+ const db2 = new Database('./users.jsonl', { indexes: { id: 'number' } });
478
+ ```
479
+
480
+ ### Key Improvements
481
+
482
+ | Feature | JexiDB 1.x | JexiDB |
483
+ |---------|------------|--------------|
484
+ | **API Compatibility** | Original | ✅ **100% Backward Compatible** |
485
+ | **Query Methods** | `db.query()` | ✅ `db.query()` + `db.find()` |
486
+ | **File Format** | `.jdb` (proprietary) | ✅ `.jdb` + `.jsonl` support |
487
+ | **Performance** | Basic | ✅ **10-100x faster** |
488
+ | **Memory Usage** | Higher | ✅ **25% less memory** |
489
+ | **Data Integrity** | Basic | ✅ **Advanced validation** |
490
+
491
+ ## 📝 Changelog
492
+
493
+ See [CHANGELOG.md](CHANGELOG.md) for complete change history.
494
+
495
+ ## 🤝 Contributing
496
+
497
+ 1. Fork the project
498
+ 2. Create a branch for your feature
499
+ 3. Commit your changes
500
+ 4. Push to the branch
501
+ 5. Open a Pull Request
502
+
503
+ ## 📄 License
504
+
505
+ MIT License - see [LICENSE](LICENSE) for details.
506
+
507
+ ---
508
+
509
+ ## 🎯 About JexiDB
510
+
511
+ JexiDB maintains the original JexiDB philosophy while fixing bugs and implementing a more robust architecture.
512
+
513
+ ### 🚀 Performance
514
+
515
+ **JexiDB** performance compared to version 1.x:
516
+
517
+ - **Find operations**: 103x faster
518
+ - **Update operations**: 26x faster
519
+ - **Insert operations**: 6-11x faster
520
+ - **Memory usage**: 25% less memory
521
+
522
+ <p align="center">
523
+ <img width="420" src="https://edenware.app/jexidb/images/jexi-mascot.webp" alt="JexiDB mascot" title="JexiDB mascot" />
524
+ </p>
@@ -606,7 +606,7 @@ class FileHandler {
606
606
  } catch (error) {
607
607
  if (error.code === 'ENOENT') {
608
608
  return {
609
- version: '2.0.0',
609
+ version: '2.0.1',
610
610
  // Keep version number for internal tracking
611
611
  created: new Date().toISOString(),
612
612
  lastModified: new Date().toISOString(),
@@ -36,8 +36,17 @@ class JSONLDatabase extends _events.EventEmitter {
36
36
  this.options = {
37
37
  batchSize: 100,
38
38
  // Batch size for inserts
39
+ create: true,
40
+ // Create database if it doesn't exist (default: true)
41
+ clear: false,
42
+ // Clear database on load if not empty (default: false)
39
43
  ...options
40
44
  };
45
+
46
+ // If clear is true, create should also be true
47
+ if (this.options.clear === true) {
48
+ this.options.create = true;
49
+ }
41
50
  this.isInitialized = false;
42
51
  this.offsets = [];
43
52
  this.indexOffset = 0;
@@ -80,10 +89,49 @@ class JSONLDatabase extends _events.EventEmitter {
80
89
  await _fs.promises.mkdir(dir, {
81
90
  recursive: true
82
91
  });
92
+
93
+ // Check if file exists before loading
94
+ const fileExists = await _fs.promises.access(this.filePath).then(() => true).catch(() => false);
95
+
96
+ // Handle clear option
97
+ if (this.options.clear && fileExists) {
98
+ await _fs.promises.writeFile(this.filePath, '');
99
+ this.offsets = [];
100
+ this.indexOffset = 0;
101
+ this.recordCount = 0;
102
+ console.log(`Database cleared: ${this.filePath}`);
103
+ this.isInitialized = true;
104
+ this.emit('init');
105
+ return;
106
+ }
107
+
108
+ // Handle create option
109
+ if (!fileExists) {
110
+ if (this.options.create) {
111
+ await _fs.promises.writeFile(this.filePath, '');
112
+ this.offsets = [];
113
+ this.indexOffset = 0;
114
+ this.recordCount = 0;
115
+ console.log(`Database created: ${this.filePath}`);
116
+ this.isInitialized = true;
117
+ this.emit('init');
118
+ return;
119
+ } else {
120
+ throw new Error(`Database file does not exist: ${this.filePath}`);
121
+ }
122
+ }
123
+
124
+ // Load existing database
83
125
  await this.loadDataWithOffsets();
84
126
  this.isInitialized = true;
85
127
  this.emit('init');
86
128
  } catch (error) {
129
+ // If create is false and file doesn't exist or is corrupted, throw error
130
+ if (!this.options.create) {
131
+ throw new Error(`Failed to load database: ${error.message}`);
132
+ }
133
+
134
+ // If create is true, initialize empty database
87
135
  this.recordCount = 0;
88
136
  this.offsets = [];
89
137
  this.indexOffset = 0;
@@ -171,9 +219,7 @@ class JSONLDatabase extends _events.EventEmitter {
171
219
  }
172
220
  this.recordCount = this.offsets.length;
173
221
  } catch (error) {
174
- this.recordCount = 0;
175
- this.offsets = [];
176
- this.indexOffset = 0;
222
+ throw error; // Re-throw to be handled by init()
177
223
  }
178
224
  }
179
225
  async loadLegacyFormat(lines) {
@@ -821,6 +867,22 @@ class JSONLDatabase extends _events.EventEmitter {
821
867
  };
822
868
  }
823
869
 
870
+ /**
871
+ * Compatibility method: readColumnIndex - gets unique values from indexed columns only
872
+ * Maintains compatibility with JexiDB v1 code
873
+ * @param {string} column - The column name to get unique values from
874
+ * @returns {Set} Set of unique values in the column (indexed columns only)
875
+ */
876
+ readColumnIndex(column) {
877
+ // Only works with indexed columns
878
+ if (this.indexes[column]) {
879
+ return new Set(this.indexes[column].keys());
880
+ }
881
+
882
+ // For non-indexed columns, throw error
883
+ throw new Error(`Column '${column}' is not indexed. Only indexed columns are supported.`);
884
+ }
885
+
824
886
  // Intelligent criteria matching for non-indexed fields
825
887
  matchesCriteria(record, criteria, options = {}) {
826
888
  const {
package/dist/index.js CHANGED
@@ -251,6 +251,16 @@ class JexiDBCompatibility extends _JSONLDatabase.default {
251
251
  }
252
252
  return result;
253
253
  }
254
+
255
+ /**
256
+ * Compatibility method: readColumnIndex - gets unique values from indexed columns only
257
+ * Maintains compatibility with JexiDB v1 code
258
+ * @param {string} column - The column name to get unique values from
259
+ * @returns {Set} Set of unique values in the column (indexed columns only)
260
+ */
261
+ readColumnIndex(column) {
262
+ return super.readColumnIndex(column);
263
+ }
254
264
  }
255
265
 
256
266
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jexidb",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "JexiDB - A fast and reliable local CRUD database for Electron apps with pure JavaScript JSONL architecture",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./src/index.js",
@@ -600,7 +600,7 @@ class FileHandler {
600
600
  } catch (error) {
601
601
  if (error.code === 'ENOENT') {
602
602
  return {
603
- version: '2.0.0', // Keep version number for internal tracking
603
+ version: '2.0.1', // Keep version number for internal tracking
604
604
  created: new Date().toISOString(),
605
605
  lastModified: new Date().toISOString(),
606
606
  recordCount: 0,
@@ -30,9 +30,16 @@ class JSONLDatabase extends EventEmitter {
30
30
 
31
31
  this.options = {
32
32
  batchSize: 100, // Batch size for inserts
33
+ create: true, // Create database if it doesn't exist (default: true)
34
+ clear: false, // Clear database on load if not empty (default: false)
33
35
  ...options
34
36
  };
35
37
 
38
+ // If clear is true, create should also be true
39
+ if (this.options.clear === true) {
40
+ this.options.create = true;
41
+ }
42
+
36
43
  this.isInitialized = false;
37
44
  this.offsets = [];
38
45
  this.indexOffset = 0;
@@ -77,12 +84,50 @@ class JSONLDatabase extends EventEmitter {
77
84
  const dir = path.dirname(this.filePath);
78
85
  await fs.mkdir(dir, { recursive: true });
79
86
 
87
+ // Check if file exists before loading
88
+ const fileExists = await fs.access(this.filePath).then(() => true).catch(() => false);
89
+
90
+ // Handle clear option
91
+ if (this.options.clear && fileExists) {
92
+ await fs.writeFile(this.filePath, '');
93
+ this.offsets = [];
94
+ this.indexOffset = 0;
95
+ this.recordCount = 0;
96
+ console.log(`Database cleared: ${this.filePath}`);
97
+ this.isInitialized = true;
98
+ this.emit('init');
99
+ return;
100
+ }
101
+
102
+ // Handle create option
103
+ if (!fileExists) {
104
+ if (this.options.create) {
105
+ await fs.writeFile(this.filePath, '');
106
+ this.offsets = [];
107
+ this.indexOffset = 0;
108
+ this.recordCount = 0;
109
+ console.log(`Database created: ${this.filePath}`);
110
+ this.isInitialized = true;
111
+ this.emit('init');
112
+ return;
113
+ } else {
114
+ throw new Error(`Database file does not exist: ${this.filePath}`);
115
+ }
116
+ }
117
+
118
+ // Load existing database
80
119
  await this.loadDataWithOffsets();
81
120
 
82
121
  this.isInitialized = true;
83
122
  this.emit('init');
84
123
 
85
124
  } catch (error) {
125
+ // If create is false and file doesn't exist or is corrupted, throw error
126
+ if (!this.options.create) {
127
+ throw new Error(`Failed to load database: ${error.message}`);
128
+ }
129
+
130
+ // If create is true, initialize empty database
86
131
  this.recordCount = 0;
87
132
  this.offsets = [];
88
133
  this.indexOffset = 0;
@@ -180,9 +225,7 @@ class JSONLDatabase extends EventEmitter {
180
225
  this.recordCount = this.offsets.length;
181
226
 
182
227
  } catch (error) {
183
- this.recordCount = 0;
184
- this.offsets = [];
185
- this.indexOffset = 0;
228
+ throw error; // Re-throw to be handled by init()
186
229
  }
187
230
  }
188
231
 
@@ -867,6 +910,22 @@ class JSONLDatabase extends EventEmitter {
867
910
  };
868
911
  }
869
912
 
913
+ /**
914
+ * Compatibility method: readColumnIndex - gets unique values from indexed columns only
915
+ * Maintains compatibility with JexiDB v1 code
916
+ * @param {string} column - The column name to get unique values from
917
+ * @returns {Set} Set of unique values in the column (indexed columns only)
918
+ */
919
+ readColumnIndex(column) {
920
+ // Only works with indexed columns
921
+ if (this.indexes[column]) {
922
+ return new Set(this.indexes[column].keys());
923
+ }
924
+
925
+ // For non-indexed columns, throw error
926
+ throw new Error(`Column '${column}' is not indexed. Only indexed columns are supported.`);
927
+ }
928
+
870
929
  // Intelligent criteria matching for non-indexed fields
871
930
  matchesCriteria(record, criteria, options = {}) {
872
931
  const { caseInsensitive = false } = options;
package/src/index.js CHANGED
@@ -227,6 +227,16 @@ class JexiDBCompatibility extends JSONLDatabase {
227
227
  }
228
228
  return result;
229
229
  }
230
+
231
+ /**
232
+ * Compatibility method: readColumnIndex - gets unique values from indexed columns only
233
+ * Maintains compatibility with JexiDB v1 code
234
+ * @param {string} column - The column name to get unique values from
235
+ * @returns {Set} Set of unique values in the column (indexed columns only)
236
+ */
237
+ readColumnIndex(column) {
238
+ return super.readColumnIndex(column);
239
+ }
230
240
  }
231
241
 
232
242
  /**