jexidb 1.1.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Edenware.app
3
+ Copyright (c) 2024 Edenware
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,128 +1,524 @@
1
- <p align="center">
2
- <img width="270" src="https://edenware.app/jexidb/images/jexidb-logo-icon.jpg" alt="JexiDB logo" title="JexiDB logo" />
3
- </p>
1
+ # JexiDB - Pure JavaScript JSONL Database
4
2
 
5
- ## Overview
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.
6
4
 
7
- JexiDB is a lightweight, standalone JavaScript database manager that stores data on disk using either JSON format or V8 serialization. It supports indexing and querying capabilities for efficient data operations. Ideal for local Node.js projects as well as apps built with Electron or NW.js. Written in pure JavaScript, it requires no compilation and is compatible with both CommonJS and ESM modules.
5
+ ## πŸš€ Features
8
6
 
9
- ## Installation
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
10
15
 
11
- To install JexiDB, you can use npm:
16
+ ## πŸ“¦ Installation
12
17
 
13
18
  ```bash
14
- npm install EdenwareApps/jexidb
19
+ npm install jexidb
15
20
  ```
16
21
 
17
- ## Usage
18
-
19
- ### Creating a Database Instance
20
-
21
- To create a new instance of the database, you need to provide a file path where the database will be stored and an optional configuration object for indexes.
22
+ ## πŸš€ Quick Start
22
23
 
23
24
  ```javascript
24
- // const { Database } = require('jexidb'); // commonjs
25
-
26
- import { Database } from 'jexidb'; // ESM
25
+ // import { Database } from 'jexidb'
26
+ const { Database } = require('jexidb');
27
27
 
28
- const db = new Database('path/to/database.jdb', { // file will be created if it does not already exist
29
- v8: false, //
30
- create: true, // create the file if it does not exist (default is true)
31
- compress: false, // set to true to compress each entry
32
- compressIndex: false, // set to true to compress the index only
33
- indexes: { // keys to use in queries, only those key values ​​are kept in memory, so fewer specified keys lead to improved performance
28
+ // Create database with indexes (supports both .jdb and .jsonl)
29
+ const db = new Database('./users.jdb', {
30
+ indexes: {
34
31
  id: 'number',
35
- name: 'string'
36
- }
32
+ email: 'string',
33
+ age: 'number'
34
+ },
35
+ autoSave: true,
36
+ validateOnInit: true
37
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();
38
73
  ```
39
- You can [learn a bit more about these options at this link](https://github.com/EdenwareApps/jexidb/tree/main/test#readme).
40
74
 
75
+ ## πŸ“š API Reference
76
+
77
+ ### Constructor
41
78
 
42
- ### Initializing the Database
79
+ ```javascript
80
+ const db = new Database(filePath, options);
81
+ ```
43
82
 
44
- Before using the database, you need to initialize it. This will load the existing data and indexes from the file.
83
+ **Parameters:**
84
+ - `filePath` (string): Path to the main file (.jdb)
85
+ - `options` (object): Configuration options
45
86
 
87
+ **Options:**
46
88
  ```javascript
47
- await db.init();
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
+ }
48
95
  ```
49
- Only the values ​​specified as indexes are kept in memory for faster queries. JexiDB will never load the entire file into memory.
50
96
 
97
+ ### Main Methods
98
+
99
+ #### `init()`
100
+ Initializes the database.
51
101
 
52
- ### Inserting Data
102
+ #### `insert(data)`
103
+ Inserts a record.
53
104
 
54
- You can insert data into the database by using the `insert` method. The data should be an object that contains the defined indexes. All object values will be saved into database.
105
+ #### `insertMany(dataArray)`
106
+ Inserts multiple records.
55
107
 
108
+ #### `find(criteria, options)` / `query(criteria, options)`
109
+ Searches records with optional criteria. Both methods work identically.
110
+
111
+ **Supported operators:**
56
112
  ```javascript
57
- await db.insert({ id: 1, name: 'John Doe' });
58
- await db.insert({ id: 2, name: 'Jane Doe', anyArbitraryField: '1' });
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
+ }
59
138
  ```
60
139
 
61
- ### Querying Data
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.
62
155
 
63
- The `query` method allows you to retrieve data based on specific criteria. You can specify criteria for multiple fields.
156
+ #### `delete(criteria, options)`
157
+ Removes records.
64
158
 
159
+ **Delete options:**
65
160
  ```javascript
66
- const results = await db.query({ name: 'John Doe' }, { caseInsensitive: true });
67
- console.log(results); // [{ id: 1, name: 'John Doe' }]
161
+ {
162
+ physical: false, // Physically remove instead of marking as deleted
163
+ limit: 1 // Limit number of records to delete
164
+ }
68
165
  ```
69
166
 
70
- Note: For now the query should be limited to using the fields specified as 'indexes' when instantiating the class.
167
+ #### `count(criteria, options)`
168
+ Counts records.
169
+
170
+ #### `save()`
171
+ Saves pending changes.
172
+
173
+ #### `destroy()`
174
+ Destroys the database.
71
175
 
72
- #### Querying with Conditions
176
+ #### `validateIntegrity(options)`
177
+ Validates database integrity.
73
178
 
74
- You can use conditions to perform more complex queries:
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:
75
188
 
76
189
  ```javascript
77
- const results = await db.query({ id: { '>': 1 } });
78
- console.log(results); // [{ id: 2, name: 'Jane Doe' }]
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
+ }
79
203
  ```
80
204
 
81
- ### Updating Data
205
+ ### Properties
206
+
207
+ #### `length`
208
+ Total number of records.
209
+
210
+ #### `indexStats`
211
+ Index statistics.
82
212
 
83
- To update existing records, use the `update` method with the criteria to find the records and the new data.
213
+ ### Events
84
214
 
85
215
  ```javascript
86
- await db.update({ id: 1 }, { name: 'John Smith' });
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"}
87
275
  ```
88
276
 
89
- ### Deleting Data
277
+ ## πŸ” Advanced Examples
90
278
 
91
- You can delete records that match certain criteria using the `delete` method.
279
+ ### Complex Search
92
280
 
93
281
  ```javascript
94
- const deletedCount = await db.delete({ name: 'Jane Doe' });
95
- console.log(`Deleted ${deletedCount} record(s).`);
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
+ });
96
291
  ```
97
292
 
98
- ### Iterating Through Records
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
+ ```
99
302
 
100
- You can iterate through records in the database using the `walk` method, which returns an async generator.
303
+ ### Integrity Validation
101
304
 
102
305
  ```javascript
103
- for await (const record of db.walk()) {
104
- console.log(record);
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);
105
317
  }
106
318
  ```
107
319
 
108
- ### Saving Changes
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
109
358
 
110
- After making any changes to the database, you need to save them using the `save` method. This will persist the changes to disk.
359
+ ### Comparison: JexiDB vs 1.x
111
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)**
112
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 });
113
465
  await db.save();
114
466
  ```
115
467
 
116
- ## Conclusion
468
+ ### File Format Support
117
469
 
118
- JexiDB provides a simple yet powerful way to store and manage data in JavaScript applications. With its indexing and querying features, you can build efficient data-driven applications.
470
+ JexiDB supports both file formats:
471
+ - **`.jdb`** (preferred) - JexiDB's branded extension
472
+ - **`.jsonl`** (standard) - JSON Lines format
119
473
 
120
- <p align="center">
121
- <img width="380" src="https://edenware.app/jexidb/images/jexidb-mascot3.jpg" alt="JexiDB mascot" title="JexiDB mascot" />
122
- </p>
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
123
496
 
124
- # Contributing
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
125
502
 
126
- Please, feel free to contribute to the project by opening a discussion under Issues section or sending your PR.
503
+ ## πŸ“„ License
127
504
 
128
- If you find this library useful, please consider making a donation of any amount via [PayPal by clicking here](https://www.paypal.com/donate/?item_name=megacubo.tv&cmd=_donations&business=efox.web%40gmail.com) to help the developer continue to dedicate himself to the project. ❀
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>