jexidb 2.0.2 → 2.0.3
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 +109 -0
- package/README.md +119 -7
- package/dist/JSONLDatabase.js +240 -101
- package/docs/API.md +390 -0
- package/docs/EXAMPLES.md +177 -0
- package/docs/MIGRATION.md +295 -0
- package/docs/README.md +184 -0
- package/examples/auto-save-example.js +158 -0
- package/examples/cjs-usage.cjs +82 -0
- package/examples/close-vs-delete-example.js +71 -0
- package/examples/esm-usage.js +113 -0
- package/examples/example-columns.idx.jdb +0 -0
- package/examples/example-columns.jdb +9 -0
- package/examples/example-options.idx.jdb +0 -0
- package/examples/example-options.jdb +0 -0
- package/examples/example-users.idx.jdb +0 -0
- package/examples/example-users.jdb +5 -0
- package/examples/simple-test.js +55 -0
- package/package.json +5 -2
- package/src/JSONLDatabase.js +245 -102
package/src/JSONLDatabase.js
CHANGED
|
@@ -28,10 +28,28 @@ class JSONLDatabase extends EventEmitter {
|
|
|
28
28
|
this.filePath = filePath;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Enhanced configuration with intelligent defaults
|
|
31
32
|
this.options = {
|
|
32
|
-
|
|
33
|
+
// Original options
|
|
34
|
+
batchSize: 50, // Reduced from 100 for faster response
|
|
33
35
|
create: true, // Create database if it doesn't exist (default: true)
|
|
34
36
|
clear: false, // Clear database on load if not empty (default: false)
|
|
37
|
+
|
|
38
|
+
// Auto-save intelligent configuration
|
|
39
|
+
autoSave: true, // Enable auto-save by default
|
|
40
|
+
autoSaveThreshold: 50, // Flush when buffer reaches 50 records
|
|
41
|
+
autoSaveInterval: 5000, // Flush every 5 seconds
|
|
42
|
+
forceSaveOnClose: true, // Always save when closing
|
|
43
|
+
|
|
44
|
+
// Performance configuration
|
|
45
|
+
adaptiveBatchSize: true, // Adjust batch size based on usage
|
|
46
|
+
minBatchSize: 10, // Minimum batch size for flush
|
|
47
|
+
maxBatchSize: 200, // Maximum batch size for performance
|
|
48
|
+
|
|
49
|
+
// Memory management
|
|
50
|
+
maxMemoryUsage: 'auto', // Calculate automatically or use fixed value
|
|
51
|
+
maxFlushChunkBytes: 8 * 1024 * 1024, // 8MB default
|
|
52
|
+
|
|
35
53
|
...options
|
|
36
54
|
};
|
|
37
55
|
|
|
@@ -40,6 +58,11 @@ class JSONLDatabase extends EventEmitter {
|
|
|
40
58
|
this.options.create = true;
|
|
41
59
|
}
|
|
42
60
|
|
|
61
|
+
// Auto-save timer and state
|
|
62
|
+
this.autoSaveTimer = null;
|
|
63
|
+
this.lastFlushTime = null;
|
|
64
|
+
this.lastAutoSaveTime = Date.now();
|
|
65
|
+
|
|
43
66
|
this.isInitialized = false;
|
|
44
67
|
this.offsets = [];
|
|
45
68
|
this.indexOffset = 0;
|
|
@@ -288,11 +311,30 @@ class JSONLDatabase extends EventEmitter {
|
|
|
288
311
|
|
|
289
312
|
// Convert back to Map objects
|
|
290
313
|
for (const [field, indexMap] of Object.entries(savedIndexes)) {
|
|
291
|
-
if
|
|
314
|
+
// Initialize index if it doesn't exist
|
|
315
|
+
if (!this.indexes[field]) {
|
|
292
316
|
this.indexes[field] = new Map();
|
|
293
|
-
|
|
294
|
-
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this.indexes[field] = new Map();
|
|
320
|
+
for (const [value, indices] of Object.entries(indexMap)) {
|
|
321
|
+
// Convert value back to original type based on field configuration
|
|
322
|
+
let convertedValue = value;
|
|
323
|
+
if (this.indexes[field] && this.indexes[field].constructor === Map) {
|
|
324
|
+
// Try to convert based on field type
|
|
325
|
+
if (field === 'id' || field.includes('id') || field.includes('Id')) {
|
|
326
|
+
convertedValue = parseInt(value, 10);
|
|
327
|
+
} else if (typeof value === 'string' && !isNaN(parseFloat(value))) {
|
|
328
|
+
// Try to convert numeric strings back to numbers
|
|
329
|
+
const num = parseFloat(value);
|
|
330
|
+
if (Number.isInteger(num)) {
|
|
331
|
+
convertedValue = parseInt(value, 10);
|
|
332
|
+
} else {
|
|
333
|
+
convertedValue = num;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
295
336
|
}
|
|
337
|
+
this.indexes[field].set(convertedValue, new Set(indices));
|
|
296
338
|
}
|
|
297
339
|
}
|
|
298
340
|
|
|
@@ -354,7 +396,7 @@ class JSONLDatabase extends EventEmitter {
|
|
|
354
396
|
}
|
|
355
397
|
}
|
|
356
398
|
|
|
357
|
-
// ORIGINAL STRATEGY: Buffer in memory + batch write
|
|
399
|
+
// ORIGINAL STRATEGY: Buffer in memory + batch write with intelligent auto-save
|
|
358
400
|
async insert(data) {
|
|
359
401
|
if (!this.isInitialized) {
|
|
360
402
|
throw new Error('Database not initialized');
|
|
@@ -378,25 +420,80 @@ class JSONLDatabase extends EventEmitter {
|
|
|
378
420
|
// Add to index immediately for searchability
|
|
379
421
|
this.addToIndex(record, this.recordCount - 1);
|
|
380
422
|
|
|
381
|
-
//
|
|
382
|
-
if (this.
|
|
383
|
-
|
|
423
|
+
// Intelligent auto-save logic
|
|
424
|
+
if (this.options.autoSave) {
|
|
425
|
+
// Auto-save based on threshold
|
|
426
|
+
if (this.insertionBuffer.length >= this.options.autoSaveThreshold) {
|
|
427
|
+
await this.flush();
|
|
428
|
+
this.emit('buffer-full');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Auto-save based on time interval
|
|
432
|
+
if (!this.autoSaveTimer) {
|
|
433
|
+
this.autoSaveTimer = setTimeout(async () => {
|
|
434
|
+
if (this.insertionBuffer.length > 0) {
|
|
435
|
+
await this.flush();
|
|
436
|
+
this.emit('auto-save-timer');
|
|
437
|
+
}
|
|
438
|
+
this.autoSaveTimer = null;
|
|
439
|
+
}, this.options.autoSaveInterval);
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
// Manual mode: flush only when buffer is full
|
|
443
|
+
if (this.insertionBuffer.length >= this.insertionStats.batchSize) {
|
|
444
|
+
await this.flushInsertionBuffer();
|
|
445
|
+
}
|
|
384
446
|
}
|
|
385
447
|
|
|
386
448
|
this.shouldSave = true;
|
|
387
449
|
|
|
388
|
-
// Save immediately if autoSave is enabled
|
|
389
|
-
if (this.options.autoSave && this.shouldSave) {
|
|
390
|
-
await this.save();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
450
|
// Emit insert event
|
|
394
451
|
this.emit('insert', record, this.recordCount - 1);
|
|
395
452
|
|
|
396
453
|
return record; // Return immediately (ORIGINAL STRATEGY)
|
|
397
454
|
}
|
|
398
455
|
|
|
399
|
-
//
|
|
456
|
+
// PUBLIC METHOD: Flush buffer to disk
|
|
457
|
+
async flush() {
|
|
458
|
+
if (!this.isInitialized) {
|
|
459
|
+
throw new Error('Database not initialized');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (this.insertionBuffer.length > 0) {
|
|
463
|
+
const flushCount = this.insertionBuffer.length;
|
|
464
|
+
await this.flushInsertionBuffer();
|
|
465
|
+
this.lastFlushTime = Date.now();
|
|
466
|
+
this.emit('buffer-flush', flushCount);
|
|
467
|
+
return flushCount;
|
|
468
|
+
}
|
|
469
|
+
return 0;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// PUBLIC METHOD: Force save - always saves regardless of buffer size
|
|
473
|
+
async forceSave() {
|
|
474
|
+
if (!this.isInitialized) {
|
|
475
|
+
throw new Error('Database not initialized');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
await this.flush();
|
|
479
|
+
await this.save();
|
|
480
|
+
this.emit('save-complete');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// PUBLIC METHOD: Get buffer status information
|
|
484
|
+
getBufferStatus() {
|
|
485
|
+
return {
|
|
486
|
+
pendingCount: this.insertionBuffer.length,
|
|
487
|
+
bufferSize: this.options.batchSize,
|
|
488
|
+
lastFlush: this.lastFlushTime,
|
|
489
|
+
lastAutoSave: this.lastAutoSaveTime,
|
|
490
|
+
shouldFlush: this.insertionBuffer.length >= this.options.autoSaveThreshold,
|
|
491
|
+
autoSaveEnabled: this.options.autoSave,
|
|
492
|
+
autoSaveTimer: this.autoSaveTimer ? 'active' : 'inactive'
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ULTRA-OPTIMIZED STRATEGY: Bulk flush with minimal I/O (chunked to avoid OOM)
|
|
400
497
|
async flushInsertionBuffer() {
|
|
401
498
|
if (this.insertionBuffer.length === 0) {
|
|
402
499
|
return;
|
|
@@ -413,45 +510,54 @@ class JSONLDatabase extends EventEmitter {
|
|
|
413
510
|
currentOffset = 0;
|
|
414
511
|
}
|
|
415
512
|
|
|
416
|
-
//
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
513
|
+
// Write in chunks to avoid allocating a huge buffer/string at once
|
|
514
|
+
const maxChunkBytes = this.options.maxFlushChunkBytes || 8 * 1024 * 1024; // 8MB default
|
|
515
|
+
let chunkParts = [];
|
|
516
|
+
let chunkBytes = 0;
|
|
517
|
+
|
|
518
|
+
// We'll push offsets directly to avoid creating a separate large array
|
|
519
|
+
const pendingOffsets = [];
|
|
520
|
+
|
|
421
521
|
for (let i = 0; i < this.insertionBuffer.length; i++) {
|
|
422
522
|
const record = this.insertionBuffer[i];
|
|
423
|
-
|
|
424
|
-
// Records are already indexed in insert/insertMany methods
|
|
425
|
-
// No need to index again here
|
|
426
|
-
|
|
427
|
-
// Serialize record (batch operation)
|
|
428
523
|
const line = JSON.stringify(record) + '\n';
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
currentOffset +=
|
|
524
|
+
const lineBytes = Buffer.byteLength(line, 'utf8');
|
|
525
|
+
|
|
526
|
+
// Track offset for this record
|
|
527
|
+
pendingOffsets.push(currentOffset);
|
|
528
|
+
currentOffset += lineBytes;
|
|
529
|
+
|
|
530
|
+
// If one line is larger than chunk size, write the current chunk and then this line alone
|
|
531
|
+
if (lineBytes > maxChunkBytes) {
|
|
532
|
+
if (chunkParts.length > 0) {
|
|
533
|
+
await fs.appendFile(this.filePath, chunkParts.join(''));
|
|
534
|
+
chunkParts.length = 0;
|
|
535
|
+
chunkBytes = 0;
|
|
536
|
+
}
|
|
537
|
+
await fs.appendFile(this.filePath, line);
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// If adding this line would exceed the chunk size, flush current chunk first
|
|
542
|
+
if (chunkBytes + lineBytes > maxChunkBytes) {
|
|
543
|
+
await fs.appendFile(this.filePath, chunkParts.join(''));
|
|
544
|
+
chunkParts.length = 0;
|
|
545
|
+
chunkBytes = 0;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
chunkParts.push(line);
|
|
549
|
+
chunkBytes += lineBytes;
|
|
434
550
|
}
|
|
435
551
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// Batch update offsets
|
|
444
|
-
this.offsets.push(...offsets);
|
|
445
|
-
|
|
446
|
-
// Record count is already updated in insert/insertMany methods
|
|
447
|
-
// No need to update it again here
|
|
448
|
-
|
|
449
|
-
// Clear the insertion buffer
|
|
552
|
+
if (chunkParts.length > 0) {
|
|
553
|
+
await fs.appendFile(this.filePath, chunkParts.join(''));
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Update offsets and clear buffer
|
|
557
|
+
this.offsets.push(...pendingOffsets);
|
|
450
558
|
this.insertionBuffer.length = 0;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
this.shouldSave = true;
|
|
454
|
-
|
|
559
|
+
this.shouldSave = true; // Mark that we need to save (offset line will be added by save())
|
|
560
|
+
|
|
455
561
|
} catch (error) {
|
|
456
562
|
console.error('Error flushing insertion buffer:', error);
|
|
457
563
|
throw new Error(`Failed to flush insertion buffer: ${error.message}`);
|
|
@@ -478,19 +584,21 @@ class JSONLDatabase extends EventEmitter {
|
|
|
478
584
|
matchingIndices = this.queryIndex(indexedCriteria);
|
|
479
585
|
}
|
|
480
586
|
|
|
481
|
-
// If no indexed fields
|
|
482
|
-
if (
|
|
587
|
+
// If no indexed fields, start with all records
|
|
588
|
+
if (indexedFields.length === 0) {
|
|
483
589
|
matchingIndices = Array.from({ length: this.recordCount }, (_, i) => i);
|
|
590
|
+
} else if (matchingIndices.length === 0) {
|
|
591
|
+
// If we have indexed fields but no matches, return empty array
|
|
592
|
+
return [];
|
|
484
593
|
}
|
|
485
594
|
|
|
486
595
|
if (matchingIndices.length === 0) {
|
|
487
596
|
return [];
|
|
488
597
|
}
|
|
489
598
|
|
|
490
|
-
// Step 2: Collect results from
|
|
599
|
+
// Step 2: Collect results from disk (existing records)
|
|
491
600
|
const results = [];
|
|
492
601
|
|
|
493
|
-
// First, get results from disk (existing records)
|
|
494
602
|
for (const index of matchingIndices) {
|
|
495
603
|
if (index < this.offsets.length) {
|
|
496
604
|
const offset = this.offsets[index];
|
|
@@ -507,54 +615,16 @@ class JSONLDatabase extends EventEmitter {
|
|
|
507
615
|
}
|
|
508
616
|
}
|
|
509
617
|
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
// Use the same queryIndex logic for buffer records
|
|
514
|
-
for (const [field, fieldCriteria] of Object.entries(indexedFields.reduce((acc, field) => {
|
|
515
|
-
acc[field] = criteria[field];
|
|
516
|
-
return acc;
|
|
517
|
-
}, {}))) {
|
|
518
|
-
const indexMap = this.indexes[field];
|
|
519
|
-
if (indexMap) {
|
|
520
|
-
if (typeof fieldCriteria === 'object' && !Array.isArray(fieldCriteria)) {
|
|
521
|
-
// Handle operators like 'in'
|
|
522
|
-
for (const [operator, operatorValue] of Object.entries(fieldCriteria)) {
|
|
523
|
-
if (operator === 'in' && Array.isArray(operatorValue)) {
|
|
524
|
-
for (const searchValue of operatorValue) {
|
|
525
|
-
const indexSet = indexMap.get(searchValue);
|
|
526
|
-
if (indexSet) {
|
|
527
|
-
for (const index of indexSet) {
|
|
528
|
-
if (index >= this.recordCount - this.insertionBuffer.length) {
|
|
529
|
-
bufferIndices.add(index);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
} else {
|
|
540
|
-
// No indexed fields, include all buffer records
|
|
618
|
+
// Step 3: Add results from buffer (new records) if buffer is not empty
|
|
619
|
+
if (this.insertionBuffer.length > 0) {
|
|
620
|
+
// Check each buffer record against criteria
|
|
541
621
|
for (let i = 0; i < this.insertionBuffer.length; i++) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const bufferOffset = bufferIndex - (this.recordCount - this.insertionBuffer.length);
|
|
549
|
-
if (bufferOffset >= 0 && bufferOffset < this.insertionBuffer.length) {
|
|
550
|
-
const record = this.insertionBuffer[bufferOffset];
|
|
551
|
-
|
|
552
|
-
// Check non-indexed fields
|
|
553
|
-
if (nonIndexedFields.length === 0 || this.matchesCriteria(record, nonIndexedFields.reduce((acc, field) => {
|
|
554
|
-
acc[field] = criteria[field];
|
|
555
|
-
return acc;
|
|
556
|
-
}, {}))) {
|
|
557
|
-
results.push(record);
|
|
622
|
+
const record = this.insertionBuffer[i];
|
|
623
|
+
if (record && !record._deleted) {
|
|
624
|
+
// Check if record matches all criteria
|
|
625
|
+
if (this.matchesCriteria(record, criteria)) {
|
|
626
|
+
results.push(record);
|
|
627
|
+
}
|
|
558
628
|
}
|
|
559
629
|
}
|
|
560
630
|
}
|
|
@@ -870,19 +940,32 @@ class JSONLDatabase extends EventEmitter {
|
|
|
870
940
|
}
|
|
871
941
|
|
|
872
942
|
async close() {
|
|
943
|
+
// Clear auto-save timer
|
|
944
|
+
if (this.autoSaveTimer) {
|
|
945
|
+
clearTimeout(this.autoSaveTimer);
|
|
946
|
+
this.autoSaveTimer = null;
|
|
947
|
+
}
|
|
948
|
+
|
|
873
949
|
// Flush any pending inserts first
|
|
874
950
|
if (this.insertionBuffer.length > 0) {
|
|
875
|
-
await this.
|
|
951
|
+
await this.flush();
|
|
876
952
|
}
|
|
877
953
|
|
|
878
|
-
if
|
|
954
|
+
// Force save on close if enabled
|
|
955
|
+
if (this.options.forceSaveOnClose && this.shouldSave) {
|
|
956
|
+
await this.save();
|
|
957
|
+
this.emit('close-save-complete');
|
|
958
|
+
} else if (this.shouldSave) {
|
|
879
959
|
await this.save();
|
|
880
960
|
}
|
|
961
|
+
|
|
881
962
|
if (this.fileHandle) {
|
|
882
963
|
await this.fileHandle.close();
|
|
883
964
|
this.fileHandle = null;
|
|
884
965
|
}
|
|
966
|
+
|
|
885
967
|
this.isInitialized = false;
|
|
968
|
+
this.emit('close');
|
|
886
969
|
}
|
|
887
970
|
|
|
888
971
|
get length() {
|
|
@@ -899,7 +982,16 @@ class JSONLDatabase extends EventEmitter {
|
|
|
899
982
|
memoryUsage: 0, // No buffer in memory - on-demand reading
|
|
900
983
|
fileHandle: this.fileHandle ? 'open' : 'closed',
|
|
901
984
|
insertionBufferSize: this.insertionBuffer.length,
|
|
902
|
-
batchSize: this.insertionStats.batchSize
|
|
985
|
+
batchSize: this.insertionStats.batchSize,
|
|
986
|
+
// Auto-save information
|
|
987
|
+
autoSave: {
|
|
988
|
+
enabled: this.options.autoSave,
|
|
989
|
+
threshold: this.options.autoSaveThreshold,
|
|
990
|
+
interval: this.options.autoSaveInterval,
|
|
991
|
+
timerActive: this.autoSaveTimer ? true : false,
|
|
992
|
+
lastFlush: this.lastFlushTime,
|
|
993
|
+
lastAutoSave: this.lastAutoSaveTime
|
|
994
|
+
}
|
|
903
995
|
};
|
|
904
996
|
}
|
|
905
997
|
|
|
@@ -910,6 +1002,37 @@ class JSONLDatabase extends EventEmitter {
|
|
|
910
1002
|
};
|
|
911
1003
|
}
|
|
912
1004
|
|
|
1005
|
+
// PUBLIC METHOD: Configure performance settings
|
|
1006
|
+
configurePerformance(settings) {
|
|
1007
|
+
if (settings.batchSize !== undefined) {
|
|
1008
|
+
this.options.batchSize = Math.max(this.options.minBatchSize,
|
|
1009
|
+
Math.min(this.options.maxBatchSize, settings.batchSize));
|
|
1010
|
+
this.insertionStats.batchSize = this.options.batchSize;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (settings.autoSaveThreshold !== undefined) {
|
|
1014
|
+
this.options.autoSaveThreshold = settings.autoSaveThreshold;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (settings.autoSaveInterval !== undefined) {
|
|
1018
|
+
this.options.autoSaveInterval = settings.autoSaveInterval;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
this.emit('performance-configured', this.options);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// PUBLIC METHOD: Get performance configuration
|
|
1025
|
+
getPerformanceConfig() {
|
|
1026
|
+
return {
|
|
1027
|
+
batchSize: this.options.batchSize,
|
|
1028
|
+
autoSaveThreshold: this.options.autoSaveThreshold,
|
|
1029
|
+
autoSaveInterval: this.options.autoSaveInterval,
|
|
1030
|
+
adaptiveBatchSize: this.options.adaptiveBatchSize,
|
|
1031
|
+
minBatchSize: this.options.minBatchSize,
|
|
1032
|
+
maxBatchSize: this.options.maxBatchSize
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
913
1036
|
/**
|
|
914
1037
|
* Compatibility method: readColumnIndex - gets unique values from indexed columns only
|
|
915
1038
|
* Maintains compatibility with JexiDB v1 code
|
|
@@ -1053,11 +1176,31 @@ class JSONLDatabase extends EventEmitter {
|
|
|
1053
1176
|
}
|
|
1054
1177
|
|
|
1055
1178
|
async destroy() {
|
|
1179
|
+
// destroy() agora é equivalente a close() - fecha instância, mantém arquivo
|
|
1056
1180
|
await this.close();
|
|
1057
|
-
await fs.unlink(this.filePath);
|
|
1058
1181
|
this.emit('destroy');
|
|
1059
1182
|
}
|
|
1060
1183
|
|
|
1184
|
+
async deleteDatabase() {
|
|
1185
|
+
await this.close();
|
|
1186
|
+
await fs.unlink(this.filePath);
|
|
1187
|
+
|
|
1188
|
+
// Also remove index file if it exists
|
|
1189
|
+
try {
|
|
1190
|
+
const indexPath = this.filePath.replace('.jdb', '.idx.jdb');
|
|
1191
|
+
await fs.unlink(indexPath);
|
|
1192
|
+
} catch (e) {
|
|
1193
|
+
// Index file might not exist
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
this.emit('delete-database');
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Alias for deleteDatabase
|
|
1200
|
+
async removeDatabase() {
|
|
1201
|
+
return this.deleteDatabase();
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1061
1204
|
async findOne(criteria = {}) {
|
|
1062
1205
|
const results = await this.find(criteria);
|
|
1063
1206
|
return results.length > 0 ? results[0] : null;
|