jexidb 1.0.2 → 1.0.4

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
@@ -35,6 +35,8 @@ const db = new Database('path/to/database.jdb', { // file will be created if it
35
35
  }
36
36
  });
37
37
  ```
38
+ You can [learn a bit more about these options at this link](https://github.com/EdenwareApps/jexidb/tree/main/test#readme).
39
+
38
40
 
39
41
  ### Initializing the Database
40
42
 
package/dist/Database.cjs CHANGED
@@ -7,8 +7,7 @@ exports.Database = void 0;
7
7
  var _events = require("events");
8
8
  var _FileHandler = _interopRequireDefault(require("./FileHandler.mjs"));
9
9
  var _IndexManager = _interopRequireDefault(require("./IndexManager.mjs"));
10
- var _Simple = _interopRequireDefault(require("./serializers/Simple.mjs"));
11
- var _Advanced = _interopRequireDefault(require("./serializers/Advanced.mjs"));
10
+ var _Serializer = _interopRequireDefault(require("./Serializer.mjs"));
12
11
  var _fs = _interopRequireDefault(require("fs"));
13
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
14
13
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
@@ -45,7 +44,7 @@ function AsyncGenerator(e) { var r, t; function resume(r, t) { try { var n = e[r
45
44
  AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function () { return this; }, AsyncGenerator.prototype.next = function (e) { return this._invoke("next", e); }, AsyncGenerator.prototype["throw"] = function (e) { return this._invoke("throw", e); }, AsyncGenerator.prototype["return"] = function (e) { return this._invoke("return", e); };
46
45
  function _OverloadYield(e, d) { this.v = e, this.k = d; }
47
46
  var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
48
- function Database(filePath) {
47
+ function Database(file) {
49
48
  var _this2;
50
49
  var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
51
50
  _classCallCheck(this, Database);
@@ -60,13 +59,10 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
60
59
  compressIndex: false,
61
60
  maxMemoryUsage: 64 * 1024 // 64KB
62
61
  }, opts);
62
+ _this2.offsets = [];
63
63
  _this2.shouldSave = false;
64
- if (_this2.opts.v8 || _this2.opts.compress || _this2.opts.compressIndex) {
65
- _this2.serializer = new _Advanced["default"](_this2.opts);
66
- } else {
67
- _this2.serializer = new _Simple["default"](_this2.opts);
68
- }
69
- _this2.fileHandler = new _FileHandler["default"](filePath);
64
+ _this2.serializer = new _Serializer["default"](_this2.opts);
65
+ _this2.fileHandler = new _FileHandler["default"](file);
70
66
  _this2.indexManager = new _IndexManager["default"](_this2.opts);
71
67
  _this2.indexOffset = 0;
72
68
  _this2.writeBuffer = [];
@@ -165,7 +161,7 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
165
161
  case 38:
166
162
  _context.prev = 38;
167
163
  _context.t0 = _context["catch"](9);
168
- if (!this.offsets) {
164
+ if (Array.isArray(this.offsets)) {
169
165
  this.offsets = [];
170
166
  }
171
167
  this.indexOffset = 0;
@@ -204,18 +200,30 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
204
200
  }
205
201
  throw new Error('Database is destroyed');
206
202
  case 2:
207
- if (!this.saving) {
203
+ if (this.initialized) {
208
204
  _context2.next = 4;
209
205
  break;
210
206
  }
207
+ throw new Error('Database not initialized');
208
+ case 4:
209
+ if (!this.saving) {
210
+ _context2.next = 6;
211
+ break;
212
+ }
211
213
  return _context2.abrupt("return", new Promise(function (resolve) {
212
214
  return _this4.once('save', resolve);
213
215
  }));
214
- case 4:
216
+ case 6:
215
217
  this.saving = true;
216
- _context2.next = 7;
218
+ _context2.next = 9;
217
219
  return this.flush();
218
- case 7:
220
+ case 9:
221
+ if (this.shouldSave) {
222
+ _context2.next = 11;
223
+ break;
224
+ }
225
+ return _context2.abrupt("return");
226
+ case 11:
219
227
  this.emit('before-save');
220
228
  index = Object.assign({
221
229
  data: {}
@@ -226,12 +234,14 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
226
234
  }
227
235
  }
228
236
  offsets = this.offsets.slice(0);
229
- _context2.next = 13;
237
+ _context2.next = 17;
230
238
  return this.serializer.serialize(index, {
231
- compress: this.opts.compressIndex
239
+ compress: this.opts.compressIndex,
240
+ linebreak: true
232
241
  });
233
- case 13:
242
+ case 17:
234
243
  indexString = _context2.sent;
244
+ // force linebreak here to allow 'init' to read last line as offsets correctly
235
245
  for (_field in this.indexManager.index.data) {
236
246
  for (_term in this.indexManager.index.data[_field]) {
237
247
  this.indexManager.index.data[_field][_term] = new Set(index.data[_field][_term]); // set back to set because of serialization
@@ -239,22 +249,15 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
239
249
  }
240
250
  offsets.push(this.indexOffset);
241
251
  offsets.push(this.indexOffset + indexString.length);
242
- _context2.next = 19;
252
+ // save offsets as JSON always to prevent linebreaks on last line, which breaks 'init()'
253
+ _context2.next = 23;
243
254
  return this.serializer.serialize(offsets, {
244
- compress: this.opts.compressIndex,
255
+ json: true,
256
+ compress: false,
245
257
  linebreak: false
246
258
  });
247
- case 19:
248
- offsetsString = _context2.sent;
249
- if (!this.shouldTruncate) {
250
- _context2.next = 24;
251
- break;
252
- }
253
- _context2.next = 23;
254
- return this.fileHandler.truncate(this.indexOffset);
255
259
  case 23:
256
- this.shouldTruncate = false;
257
- case 24:
260
+ offsetsString = _context2.sent;
258
261
  this.writeBuffer.push(indexString);
259
262
  this.writeBuffer.push(offsetsString);
260
263
  _context2.next = 28;
@@ -390,17 +393,24 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
390
393
  }
391
394
  throw new Error('Database is destroyed');
392
395
  case 2:
393
- _context5.next = 4;
396
+ if (this.initialized) {
397
+ _context5.next = 5;
398
+ break;
399
+ }
400
+ _context5.next = 5;
401
+ return this.init();
402
+ case 5:
403
+ if (this.shouldTruncate) {
404
+ this.writeBuffer.push(this.indexOffset);
405
+ this.shouldTruncate = false;
406
+ }
407
+ _context5.next = 8;
394
408
  return this.serializer.serialize(data, {
395
409
  compress: this.opts.compress
396
410
  });
397
- case 4:
411
+ case 8:
398
412
  line = _context5.sent;
399
413
  // using Buffer for offsets accuracy
400
- if (this.shouldTruncate) {
401
- this.writeBuffer.push(this.indexOffset);
402
- this.shouldTruncate = false;
403
- }
404
414
  position = this.offsets.length;
405
415
  this.offsets.push(this.indexOffset);
406
416
  this.indexOffset += line.length;
@@ -408,14 +418,14 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
408
418
  this.emit('insert', data, position);
409
419
  this.writeBuffer.push(line);
410
420
  if (!(!this.flushing && this.currentWriteBufferSize() > this.opts.maxMemoryUsage)) {
411
- _context5.next = 15;
421
+ _context5.next = 18;
412
422
  break;
413
423
  }
414
- _context5.next = 15;
424
+ _context5.next = 18;
415
425
  return this.flush();
416
- case 15:
426
+ case 18:
417
427
  this.shouldSave = true;
418
- case 16:
428
+ case 19:
419
429
  case "end":
420
430
  return _context5.stop();
421
431
  }
@@ -442,16 +452,18 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
442
452
  key: "flush",
443
453
  value: function flush() {
444
454
  var _this7 = this;
445
- if (this.flushing) return this.flushing;
446
- return new Promise(function (resolve, reject) {
455
+ if (this.flushing) {
456
+ return this.flushing;
457
+ }
458
+ return this.flushing = new Promise(function (resolve, reject) {
447
459
  if (_this7.destroyed) return reject(new Error('Database is destroyed'));
448
460
  if (!_this7.writeBuffer.length) return resolve();
449
461
  var err;
450
- _this7.flushing = _this7._flush()["catch"](function (e) {
462
+ _this7._flush()["catch"](function (e) {
451
463
  return err = e;
452
464
  })["finally"](function () {
453
- _this7.flushing = false;
454
465
  err ? reject(err) : resolve();
466
+ _this7.flushing = false;
455
467
  });
456
468
  });
457
469
  }
@@ -464,7 +476,7 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
464
476
  while (1) switch (_context6.prev = _context6.next) {
465
477
  case 0:
466
478
  _context6.next = 2;
467
- return _fs["default"].promises.open(this.fileHandler.filePath, 'a');
479
+ return _fs["default"].promises.open(this.fileHandler.file, 'a');
468
480
  case 2:
469
481
  fd = _context6.sent;
470
482
  _context6.prev = 3;
@@ -488,7 +500,7 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
488
500
  return this.fileHandler.truncate(this.writeBuffer.shift());
489
501
  case 12:
490
502
  _context6.next = 14;
491
- return _fs["default"].promises.open(this.fileHandler.filePath, 'a');
503
+ return _fs["default"].promises.open(this.fileHandler.file, 'a');
492
504
  case 14:
493
505
  fd = _context6.sent;
494
506
  return _context6.abrupt("continue", 4);
@@ -547,23 +559,32 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
547
559
  }
548
560
  throw new Error('Database is destroyed');
549
561
  case 2:
562
+ if (_this.initialized) {
563
+ _context8.next = 5;
564
+ break;
565
+ }
566
+ _context8.next = 5;
567
+ return _awaitAsyncGenerator(_this.init());
568
+ case 5:
550
569
  _context8.t0 = _this.shouldSave;
551
570
  if (!_context8.t0) {
552
- _context8.next = 6;
571
+ _context8.next = 9;
553
572
  break;
554
573
  }
555
- _context8.next = 6;
574
+ _context8.next = 9;
556
575
  return _awaitAsyncGenerator(_this.save()["catch"](console.error));
557
- case 6:
576
+ case 9:
558
577
  if (!(_this.indexOffset === 0)) {
559
- _context8.next = 8;
578
+ _context8.next = 11;
560
579
  break;
561
580
  }
562
581
  return _context8.abrupt("return");
563
- case 8:
582
+ case 11:
564
583
  if (!Array.isArray(map)) {
565
- if (map && _typeof(map) === 'object') {
566
- map = _this.indexManager.query(map, options.matchAny);
584
+ if (map instanceof Set) {
585
+ map = _toConsumableArray(map);
586
+ } else if (map && _typeof(map) === 'object') {
587
+ map = _toConsumableArray(_this.indexManager.query(map, options.matchAny));
567
588
  } else {
568
589
  map = _toConsumableArray(Array(_this.offsets.length).keys());
569
590
  }
@@ -581,15 +602,15 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
581
602
  }
582
603
  m = 0;
583
604
  _i = 0, _partitionedRanges = partitionedRanges;
584
- case 14:
605
+ case 17:
585
606
  if (!(_i < _partitionedRanges.length)) {
586
- _context8.next = 31;
607
+ _context8.next = 34;
587
608
  break;
588
609
  }
589
610
  _ranges = _partitionedRanges[_i];
590
- _context8.next = 18;
611
+ _context8.next = 21;
591
612
  return _awaitAsyncGenerator(_this.fileHandler.readRanges(_ranges));
592
- case 18:
613
+ case 21:
593
614
  lines = _context8.sent;
594
615
  _loop = /*#__PURE__*/_regeneratorRuntime().mark(function _loop() {
595
616
  var err, entry;
@@ -621,27 +642,27 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
621
642
  }, _loop);
622
643
  });
623
644
  _context8.t1 = _regeneratorRuntime().keys(lines);
624
- case 21:
645
+ case 24:
625
646
  if ((_context8.t2 = _context8.t1()).done) {
626
- _context8.next = 28;
647
+ _context8.next = 31;
627
648
  break;
628
649
  }
629
650
  _line = _context8.t2.value;
630
- return _context8.delegateYield(_loop(), "t3", 24);
631
- case 24:
651
+ return _context8.delegateYield(_loop(), "t3", 27);
652
+ case 27:
632
653
  if (!_context8.t3) {
633
- _context8.next = 26;
654
+ _context8.next = 29;
634
655
  break;
635
656
  }
636
- return _context8.abrupt("continue", 21);
637
- case 26:
638
- _context8.next = 21;
657
+ return _context8.abrupt("continue", 24);
658
+ case 29:
659
+ _context8.next = 24;
639
660
  break;
640
- case 28:
661
+ case 31:
641
662
  _i++;
642
- _context8.next = 14;
663
+ _context8.next = 17;
643
664
  break;
644
- case 31:
665
+ case 34:
645
666
  case "end":
646
667
  return _context8.stop();
647
668
  }
@@ -671,21 +692,28 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
671
692
  }
672
693
  throw new Error('Database is destroyed');
673
694
  case 3:
695
+ if (this.initialized) {
696
+ _context9.next = 6;
697
+ break;
698
+ }
699
+ _context9.next = 6;
700
+ return this.init();
701
+ case 6:
674
702
  _context9.t0 = this.shouldSave;
675
703
  if (!_context9.t0) {
676
- _context9.next = 7;
704
+ _context9.next = 10;
677
705
  break;
678
706
  }
679
- _context9.next = 7;
707
+ _context9.next = 10;
680
708
  return this.save()["catch"](console.error);
681
- case 7:
709
+ case 10:
682
710
  if (!Array.isArray(criteria)) {
683
- _context9.next = 16;
711
+ _context9.next = 19;
684
712
  break;
685
713
  }
686
- _context9.next = 10;
714
+ _context9.next = 13;
687
715
  return this.readLines(criteria);
688
- case 10:
716
+ case 13:
689
717
  results = _context9.sent;
690
718
  if (options.orderBy) {
691
719
  _options$orderBy$spli = options.orderBy.split(' '), _options$orderBy$spli2 = _slicedToArray(_options$orderBy$spli, 2), field = _options$orderBy$spli2[0], _options$orderBy$spli3 = _options$orderBy$spli2[1], direction = _options$orderBy$spli3 === void 0 ? 'asc' : _options$orderBy$spli3;
@@ -699,22 +727,22 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
699
727
  results = results.slice(0, options.limit);
700
728
  }
701
729
  return _context9.abrupt("return", results);
702
- case 16:
703
- _context9.next = 18;
730
+ case 19:
731
+ _context9.next = 21;
704
732
  return this.indexManager.query(criteria, options.matchAny);
705
- case 18:
733
+ case 21:
706
734
  matchingLines = _context9.sent;
707
735
  if (!(!matchingLines || !matchingLines.size)) {
708
- _context9.next = 21;
736
+ _context9.next = 24;
709
737
  break;
710
738
  }
711
739
  return _context9.abrupt("return", []);
712
- case 21:
713
- _context9.next = 23;
740
+ case 24:
741
+ _context9.next = 26;
714
742
  return this.query(_toConsumableArray(matchingLines), options);
715
- case 23:
743
+ case 26:
716
744
  return _context9.abrupt("return", _context9.sent);
717
- case 24:
745
+ case 27:
718
746
  case "end":
719
747
  return _context9.stop();
720
748
  }
@@ -747,47 +775,58 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
747
775
  while (1) switch (_context11.prev = _context11.next) {
748
776
  case 0:
749
777
  options = _args11.length > 2 && _args11[2] !== undefined ? _args11[2] : {};
778
+ if (this.shouldTruncate) {
779
+ this.writeBuffer.push(this.indexOffset);
780
+ this.shouldTruncate = false;
781
+ }
750
782
  if (!this.destroyed) {
751
- _context11.next = 3;
783
+ _context11.next = 4;
752
784
  break;
753
785
  }
754
786
  throw new Error('Database is destroyed');
755
- case 3:
756
- _context11.t0 = this.shouldSave;
757
- if (!_context11.t0) {
787
+ case 4:
788
+ if (this.initialized) {
758
789
  _context11.next = 7;
759
790
  break;
760
791
  }
761
792
  _context11.next = 7;
762
- return this.save()["catch"](console.error);
793
+ return this.init();
763
794
  case 7:
764
- _context11.next = 9;
795
+ _context11.t0 = this.shouldSave;
796
+ if (!_context11.t0) {
797
+ _context11.next = 11;
798
+ break;
799
+ }
800
+ _context11.next = 11;
801
+ return this.save()["catch"](console.error);
802
+ case 11:
803
+ _context11.next = 13;
765
804
  return this.indexManager.query(criteria, options.matchAny);
766
- case 9:
805
+ case 13:
767
806
  matchingLines = _context11.sent;
768
807
  if (!(!matchingLines || !matchingLines.size)) {
769
- _context11.next = 12;
808
+ _context11.next = 16;
770
809
  break;
771
810
  }
772
811
  return _context11.abrupt("return", []);
773
- case 12:
812
+ case 16:
774
813
  ranges = this.getRanges(_toConsumableArray(matchingLines));
775
814
  validMatchingLines = new Set(ranges.map(function (r) {
776
815
  return r.index;
777
816
  }));
778
817
  if (validMatchingLines.size) {
779
- _context11.next = 16;
818
+ _context11.next = 20;
780
819
  break;
781
820
  }
782
821
  return _context11.abrupt("return", []);
783
- case 16:
784
- _context11.next = 18;
822
+ case 20:
823
+ _context11.next = 22;
785
824
  return this.readLines(_toConsumableArray(validMatchingLines), ranges);
786
- case 18:
825
+ case 22:
787
826
  entries = _context11.sent;
788
827
  lines = [];
789
828
  _iterator = _createForOfIteratorHelper(entries);
790
- _context11.prev = 21;
829
+ _context11.prev = 25;
791
830
  _loop2 = /*#__PURE__*/_regeneratorRuntime().mark(function _loop2() {
792
831
  var entry, err, updated, ret;
793
832
  return _regeneratorRuntime().wrap(function _loop2$(_context10) {
@@ -809,27 +848,27 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
809
848
  }, _loop2);
810
849
  });
811
850
  _iterator.s();
812
- case 24:
851
+ case 28:
813
852
  if ((_step = _iterator.n()).done) {
814
- _context11.next = 28;
853
+ _context11.next = 32;
815
854
  break;
816
855
  }
817
- return _context11.delegateYield(_loop2(), "t1", 26);
818
- case 26:
819
- _context11.next = 24;
856
+ return _context11.delegateYield(_loop2(), "t1", 30);
857
+ case 30:
858
+ _context11.next = 28;
820
859
  break;
821
- case 28:
822
- _context11.next = 33;
860
+ case 32:
861
+ _context11.next = 37;
823
862
  break;
824
- case 30:
825
- _context11.prev = 30;
826
- _context11.t2 = _context11["catch"](21);
863
+ case 34:
864
+ _context11.prev = 34;
865
+ _context11.t2 = _context11["catch"](25);
827
866
  _iterator.e(_context11.t2);
828
- case 33:
829
- _context11.prev = 33;
867
+ case 37:
868
+ _context11.prev = 37;
830
869
  _iterator.f();
831
- return _context11.finish(33);
832
- case 36:
870
+ return _context11.finish(37);
871
+ case 40:
833
872
  offsets = [];
834
873
  byteOffset = 0, k = 0;
835
874
  this.offsets.forEach(function (n, i) {
@@ -843,20 +882,20 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
843
882
  });
844
883
  this.offsets = offsets;
845
884
  this.indexOffset += byteOffset;
846
- _context11.next = 43;
885
+ _context11.next = 47;
847
886
  return this.fileHandler.replaceLines(ranges, lines);
848
- case 43:
887
+ case 47:
849
888
  _toConsumableArray(validMatchingLines).forEach(function (lineNumber, i) {
850
889
  _this8.indexManager.dryRemove(lineNumber);
851
890
  _this8.indexManager.add(entries[i], lineNumber);
852
891
  });
853
892
  this.shouldSave = true;
854
893
  return _context11.abrupt("return", entries);
855
- case 46:
894
+ case 50:
856
895
  case "end":
857
896
  return _context11.stop();
858
897
  }
859
- }, _callee9, this, [[21, 30, 33, 36]]);
898
+ }, _callee9, this, [[25, 34, 37, 40]]);
860
899
  }));
861
900
  function update(_x5, _x6) {
862
901
  return _update.apply(this, arguments);
@@ -879,37 +918,48 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
879
918
  while (1) switch (_context12.prev = _context12.next) {
880
919
  case 0:
881
920
  options = _args12.length > 1 && _args12[1] !== undefined ? _args12[1] : {};
921
+ if (this.shouldTruncate) {
922
+ this.writeBuffer.push(this.indexOffset);
923
+ this.shouldTruncate = false;
924
+ }
882
925
  if (!this.destroyed) {
883
- _context12.next = 3;
926
+ _context12.next = 4;
884
927
  break;
885
928
  }
886
929
  throw new Error('Database is destroyed');
887
- case 3:
888
- _context12.t0 = this.shouldSave;
889
- if (!_context12.t0) {
930
+ case 4:
931
+ if (this.initialized) {
890
932
  _context12.next = 7;
891
933
  break;
892
934
  }
893
935
  _context12.next = 7;
894
- return this.save()["catch"](console.error);
936
+ return this.init();
895
937
  case 7:
896
- _context12.next = 9;
938
+ _context12.t0 = this.shouldSave;
939
+ if (!_context12.t0) {
940
+ _context12.next = 11;
941
+ break;
942
+ }
943
+ _context12.next = 11;
944
+ return this.save()["catch"](console.error);
945
+ case 11:
946
+ _context12.next = 13;
897
947
  return this.indexManager.query(criteria, options.matchAny);
898
- case 9:
948
+ case 13:
899
949
  matchingLines = _context12.sent;
900
950
  if (!(!matchingLines || !matchingLines.size)) {
901
- _context12.next = 12;
951
+ _context12.next = 16;
902
952
  break;
903
953
  }
904
954
  return _context12.abrupt("return", 0);
905
- case 12:
955
+ case 16:
906
956
  ranges = this.getRanges(_toConsumableArray(matchingLines));
907
957
  validMatchingLines = new Set(ranges.map(function (r) {
908
958
  return r.index;
909
959
  }));
910
- _context12.next = 16;
960
+ _context12.next = 20;
911
961
  return this.fileHandler.replaceLines(ranges, []);
912
- case 16:
962
+ case 20:
913
963
  offsets = [];
914
964
  byteOffset = 0, k = 0;
915
965
  this.offsets.forEach(function (n, i) {
@@ -926,7 +976,7 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
926
976
  this.indexManager.remove(_toConsumableArray(validMatchingLines));
927
977
  this.shouldSave = true;
928
978
  return _context12.abrupt("return", ranges.length);
929
- case 24:
979
+ case 28:
930
980
  case "end":
931
981
  return _context12.stop();
932
982
  }
@@ -972,7 +1022,8 @@ var Database = exports.Database = /*#__PURE__*/function (_EventEmitter) {
972
1022
  }, {
973
1023
  key: "length",
974
1024
  get: function get() {
975
- return this.offsets.length;
1025
+ var _this$offsets;
1026
+ return (this === null || this === void 0 || (_this$offsets = this.offsets) === null || _this$offsets === void 0 ? void 0 : _this$offsets.length) || 0;
976
1027
  }
977
1028
  }, {
978
1029
  key: "index",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jexidb",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "JexiDB is a pure JS NPM library for managing data on disk using JSONL efficiently, without the need for a server.",
5
5
  "main": "./dist/Database.cjs",
6
6
  "module": "./src/Database.mjs",
@@ -11,8 +11,8 @@
11
11
  }
12
12
  },
13
13
  "scripts": {
14
- "test": "node test/test.mjs && exit 1",
15
- "build": "npx babel src/Database.mjs --plugins @babel/plugin-transform-async-generator-functions --out-file-extension .cjs --out-dir dist"
14
+ "test": "node --expose-gc test/test.mjs && exit 1",
15
+ "prepare": "npx babel src/Database.mjs --plugins @babel/plugin-transform-async-generator-functions --out-file-extension .cjs --out-dir dist"
16
16
  },
17
17
  "author": "EdenwareApps",
18
18
  "license": "MIT",
package/src/Database.mjs CHANGED
@@ -1,12 +1,11 @@
1
1
  import { EventEmitter } from 'events'
2
2
  import FileHandler from './FileHandler.mjs'
3
3
  import IndexManager from './IndexManager.mjs'
4
- import Serializer from './serializers/Simple.mjs'
5
- import AdvancedSerializer from './serializers/Advanced.mjs'
4
+ import Serializer from './Serializer.mjs'
6
5
  import fs from 'fs'
7
6
 
8
7
  export class Database extends EventEmitter {
9
- constructor(filePath, opts={}) {
8
+ constructor(file, opts={}) {
10
9
  super()
11
10
  this.opts = Object.assign({
12
11
  v8: false,
@@ -16,13 +15,10 @@ export class Database extends EventEmitter {
16
15
  compressIndex: false,
17
16
  maxMemoryUsage: 64 * 1024 // 64KB
18
17
  }, opts)
18
+ this.offsets = []
19
19
  this.shouldSave = false
20
- if(this.opts.v8 || this.opts.compress || this.opts.compressIndex) {
21
- this.serializer = new AdvancedSerializer(this.opts)
22
- } else {
23
- this.serializer = new Serializer(this.opts)
24
- }
25
- this.fileHandler = new FileHandler(filePath)
20
+ this.serializer = new Serializer(this.opts)
21
+ this.fileHandler = new FileHandler(file)
26
22
  this.indexManager = new IndexManager(this.opts)
27
23
  this.indexOffset = 0
28
24
  this.writeBuffer = []
@@ -60,7 +56,7 @@ export class Database extends EventEmitter {
60
56
  const index = await this.serializer.deserialize(indexLine, {compress: this.opts.compressIndex})
61
57
  index && this.indexManager.load(index)
62
58
  } catch (e) {
63
- if(!this.offsets) {
59
+ if(Array.isArray(this.offsets)) {
64
60
  this.offsets = []
65
61
  }
66
62
  this.indexOffset = 0
@@ -76,9 +72,11 @@ export class Database extends EventEmitter {
76
72
 
77
73
  async save() {
78
74
  if(this.destroyed) throw new Error('Database is destroyed')
75
+ if(!this.initialized) throw new Error('Database not initialized')
79
76
  if(this.saving) return new Promise(resolve => this.once('save', resolve))
80
77
  this.saving = true
81
78
  await this.flush()
79
+ if (!this.shouldSave) return
82
80
  this.emit('before-save')
83
81
  const index = Object.assign({data: {}}, this.indexManager.index)
84
82
  for(const field in this.indexManager.index.data) {
@@ -87,7 +85,7 @@ export class Database extends EventEmitter {
87
85
  }
88
86
  }
89
87
  const offsets = this.offsets.slice(0)
90
- const indexString = await this.serializer.serialize(index, {compress: this.opts.compressIndex})
88
+ const indexString = await this.serializer.serialize(index, {compress: this.opts.compressIndex, linebreak: true}) // force linebreak here to allow 'init' to read last line as offsets correctly
91
89
  for(const field in this.indexManager.index.data) {
92
90
  for(const term in this.indexManager.index.data[field]) {
93
91
  this.indexManager.index.data[field][term] = new Set(index.data[field][term]) // set back to set because of serialization
@@ -95,11 +93,8 @@ export class Database extends EventEmitter {
95
93
  }
96
94
  offsets.push(this.indexOffset)
97
95
  offsets.push(this.indexOffset + indexString.length)
98
- const offsetsString = await this.serializer.serialize(offsets, {compress: this.opts.compressIndex, linebreak: false})
99
- if (this.shouldTruncate) {
100
- await this.fileHandler.truncate(this.indexOffset)
101
- this.shouldTruncate = false
102
- }
96
+ // save offsets as JSON always to prevent linebreaks on last line, which breaks 'init()'
97
+ const offsetsString = await this.serializer.serialize(offsets, {json: true, compress: false, linebreak: false})
103
98
  this.writeBuffer.push(indexString)
104
99
  this.writeBuffer.push(offsetsString)
105
100
  await this.flush() // write the index and offsets
@@ -122,7 +117,7 @@ export class Database extends EventEmitter {
122
117
  }
123
118
  return
124
119
  }
125
- let end = this.offsets[n + 1] || this.indexOffset || Number.MAX_SAFE_INTEGER
120
+ let end = (this.offsets[n + 1] || this.indexOffset || Number.MAX_SAFE_INTEGER)
126
121
  return [this.offsets[n], end]
127
122
  }
128
123
 
@@ -147,11 +142,12 @@ export class Database extends EventEmitter {
147
142
 
148
143
  async insert(data) {
149
144
  if(this.destroyed) throw new Error('Database is destroyed')
150
- const line = await this.serializer.serialize(data, {compress: this.opts.compress}) // using Buffer for offsets accuracy
145
+ if(!this.initialized) await this.init()
151
146
  if (this.shouldTruncate) {
152
147
  this.writeBuffer.push(this.indexOffset)
153
148
  this.shouldTruncate = false
154
149
  }
150
+ const line = await this.serializer.serialize(data, {compress: this.opts.compress}) // using Buffer for offsets accuracy
155
151
  const position = this.offsets.length
156
152
  this.offsets.push(this.indexOffset)
157
153
  this.indexOffset += line.length
@@ -170,20 +166,22 @@ export class Database extends EventEmitter {
170
166
  }
171
167
 
172
168
  flush() {
173
- if(this.flushing) return this.flushing
174
- return new Promise((resolve, reject) => {
169
+ if(this.flushing) {
170
+ return this.flushing
171
+ }
172
+ return this.flushing = new Promise((resolve, reject) => {
175
173
  if(this.destroyed) return reject(new Error('Database is destroyed'))
176
174
  if(!this.writeBuffer.length) return resolve()
177
175
  let err
178
- this.flushing = this._flush().catch(e => err = e).finally(() => {
179
- this.flushing = false
176
+ this._flush().catch(e => err = e).finally(() => {
180
177
  err ? reject(err) : resolve()
178
+ this.flushing = false
181
179
  })
182
180
  })
183
181
  }
184
182
 
185
183
  async _flush() {
186
- let fd = await fs.promises.open(this.fileHandler.filePath, 'a')
184
+ let fd = await fs.promises.open(this.fileHandler.file, 'a')
187
185
  try {
188
186
  while(this.writeBuffer.length) {
189
187
  let data
@@ -191,7 +189,7 @@ export class Database extends EventEmitter {
191
189
  if(pos === 0) {
192
190
  await fd.close()
193
191
  await this.fileHandler.truncate(this.writeBuffer.shift())
194
- fd = await fs.promises.open(this.fileHandler.filePath, 'a')
192
+ fd = await fs.promises.open(this.fileHandler.file, 'a')
195
193
  continue
196
194
  } else if(pos === -1) {
197
195
  data = Buffer.concat(this.writeBuffer)
@@ -212,11 +210,14 @@ export class Database extends EventEmitter {
212
210
 
213
211
  async *walk(map, options={}) {
214
212
  if(this.destroyed) throw new Error('Database is destroyed')
213
+ if(!this.initialized) await this.init()
215
214
  this.shouldSave && await this.save().catch(console.error)
216
215
  if(this.indexOffset === 0) return
217
216
  if(!Array.isArray(map)) {
218
- if(map && typeof map === 'object') {
219
- map = this.indexManager.query(map, options.matchAny)
217
+ if (map instanceof Set) {
218
+ map = [...map]
219
+ } else if(map && typeof map === 'object') {
220
+ map = [...this.indexManager.query(map, options.matchAny)]
220
221
  } else {
221
222
  map = [...Array(this.offsets.length).keys()]
222
223
  }
@@ -250,6 +251,7 @@ export class Database extends EventEmitter {
250
251
 
251
252
  async query(criteria, options={}) {
252
253
  if(this.destroyed) throw new Error('Database is destroyed')
254
+ if(!this.initialized) await this.init()
253
255
  this.shouldSave && await this.save().catch(console.error)
254
256
  if(Array.isArray(criteria)) {
255
257
  let results = await this.readLines(criteria)
@@ -275,7 +277,12 @@ export class Database extends EventEmitter {
275
277
  }
276
278
 
277
279
  async update(criteria, data, options={}) {
280
+ if (this.shouldTruncate) {
281
+ this.writeBuffer.push(this.indexOffset)
282
+ this.shouldTruncate = false
283
+ }
278
284
  if(this.destroyed) throw new Error('Database is destroyed')
285
+ if(!this.initialized) await this.init()
279
286
  this.shouldSave && await this.save().catch(console.error)
280
287
  const matchingLines = await this.indexManager.query(criteria, options.matchAny)
281
288
  if (!matchingLines || !matchingLines.size) {
@@ -317,7 +324,12 @@ export class Database extends EventEmitter {
317
324
  }
318
325
 
319
326
  async delete(criteria, options={}) {
327
+ if (this.shouldTruncate) {
328
+ this.writeBuffer.push(this.indexOffset)
329
+ this.shouldTruncate = false
330
+ }
320
331
  if(this.destroyed) throw new Error('Database is destroyed')
332
+ if(!this.initialized) await this.init()
321
333
  this.shouldSave && await this.save().catch(console.error)
322
334
  const matchingLines = await this.indexManager.query(criteria, options.matchAny)
323
335
  if (!matchingLines || !matchingLines.size) {
@@ -355,7 +367,7 @@ export class Database extends EventEmitter {
355
367
  }
356
368
 
357
369
  get length() {
358
- return this.offsets.length
370
+ return this?.offsets?.length || 0
359
371
  }
360
372
 
361
373
  get index() {
@@ -2,21 +2,21 @@ import fs from 'fs'
2
2
  import pLimit from 'p-limit'
3
3
 
4
4
  export default class FileHandler {
5
- constructor(filePath) {
6
- this.filePath = filePath
5
+ constructor(file) {
6
+ this.file = file
7
7
  }
8
8
 
9
9
  async truncate(offset) {
10
10
  try {
11
- await fs.promises.access(this.filePath, fs.constants.F_OK)
12
- await fs.promises.truncate(this.filePath, offset)
11
+ await fs.promises.access(this.file, fs.constants.F_OK)
12
+ await fs.promises.truncate(this.file, offset)
13
13
  } catch (err) {
14
- await fs.promises.writeFile(this.filePath, '')
14
+ await fs.promises.writeFile(this.file, '')
15
15
  }
16
16
  }
17
17
 
18
18
  async readRange(start, end) {
19
- let fd = await fs.promises.open(this.filePath, 'r')
19
+ let fd = await fs.promises.open(this.file, 'r')
20
20
  const length = end - start
21
21
  let buffer = Buffer.alloc(length)
22
22
  const { bytesRead } = await fd.read(buffer, 0, length, start).catch(console.error)
@@ -27,7 +27,7 @@ export default class FileHandler {
27
27
 
28
28
  async readRanges(ranges, mapper) {
29
29
  const lines = {}, limit = pLimit(4)
30
- const fd = await fs.promises.open(this.filePath, 'r')
30
+ const fd = await fs.promises.open(this.file, 'r')
31
31
  try {
32
32
  const tasks = ranges.map(r => {
33
33
  return async () => {
@@ -50,9 +50,9 @@ export default class FileHandler {
50
50
 
51
51
  async replaceLines(ranges, lines) {
52
52
  let closed
53
- const tmpFile = this.filePath + '.tmp'
53
+ const tmpFile = this.file + '.tmp'
54
54
  const writer = await fs.promises.open(tmpFile, 'w+')
55
- const reader = await fs.promises.open(this.filePath, 'r')
55
+ const reader = await fs.promises.open(this.file, 'r')
56
56
  try {
57
57
  let i = 0, start = 0
58
58
  for (const r of ranges) {
@@ -74,7 +74,7 @@ export default class FileHandler {
74
74
  await reader.close()
75
75
  await writer.close()
76
76
  closed = true
77
- await fs.promises.copyFile(tmpFile, this.filePath)
77
+ await fs.promises.copyFile(tmpFile, this.file)
78
78
  } catch (e) {
79
79
  console.error('Error replacing lines:', e)
80
80
  } finally {
@@ -90,11 +90,11 @@ export default class FileHandler {
90
90
  }
91
91
 
92
92
  writeDataSync(data) {
93
- fs.writeFileSync(this.filePath, data, { flag: 'a' })
93
+ fs.writeFileSync(this.file, data, { flag: 'a' })
94
94
  }
95
95
 
96
96
  async readLastLine() {
97
- const reader = await fs.promises.open(this.filePath, 'r')
97
+ const reader = await fs.promises.open(this.file, 'r')
98
98
  try {
99
99
  const { size } = await reader.stat()
100
100
  if (size < 1) throw 'empty file'
@@ -6,9 +6,6 @@ export default class Serializer extends EventEmitter {
6
6
  constructor(opts = {}) {
7
7
  super()
8
8
  this.opts = Object.assign({}, opts)
9
- this.linebreak = Buffer.from([0x0A])
10
- this.delimiter = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF])
11
- this.defaultBuffer = Buffer.alloc(4096)
12
9
  this.brotliOptions = {
13
10
  params: {
14
11
  [zlib.constants.BROTLI_PARAM_QUALITY]: 4
@@ -19,56 +16,54 @@ export default class Serializer extends EventEmitter {
19
16
  async serialize(data, opts={}) {
20
17
  let line
21
18
  let header = 0x00 // 1 byte de header
22
- const useV8 = this.opts.v8 || opts.v8 === true
23
- const compress = this.opts.compress || opts.compress === true
19
+ const useV8 = (this.opts.v8 && opts.json !== true) || opts.v8 === true
20
+ const compress = (this.opts.compress && opts.compress !== false) || opts.compress === true
21
+ const addLinebreak = opts.linebreak !== false || (!useV8 && !compress && opts.linebreak !== false)
24
22
  if (useV8) {
25
23
  header |= 0x02 // set V8
26
24
  line = v8.serialize(data)
27
25
  } else {
28
- if(compress) {
29
- line = Buffer.from(JSON.stringify(data), 'utf-8')
30
- } else {
31
- return Buffer.from(JSON.stringify(data) + (opts.linebreak !== false ? '\n' : ''), 'utf-8')
32
- }
26
+ let json = JSON.stringify(data)
27
+ line = Buffer.from(json, 'utf-8')
33
28
  }
34
29
  if (compress) {
35
30
  let err
36
- const buffer = await this.compress(line).catch(e => err = e)
37
- if(!err) {
31
+ const compressionType = useV8 ? 'deflate' : 'brotli'
32
+ const buffer = await this.compress(line, compressionType).catch(e => err = e)
33
+ if(!err && buffer.length && buffer.length < line.length) {
38
34
  header |= 0x01
39
35
  line = buffer
40
36
  }
41
37
  }
42
- const totalLength = 1 + line.length + (opts.linebreak !== false ? 1 : 0)
38
+ const totalLength = 1 + line.length + (addLinebreak ? 1 : 0)
43
39
  const result = Buffer.alloc(totalLength)
44
40
  result[0] = header
45
- line.copy(result, 1)
46
- if (opts.linebreak !== false) {
47
- result[totalLength - 1] = 0x0A
41
+ line.copy(result, 1, 0, line.length)
42
+ if(addLinebreak) {
43
+ result[result.length - 1] = 0x0A
48
44
  }
49
45
  return result
50
46
  }
51
47
 
52
- async deserialize(data, opts={}) {
53
- let line
48
+ async deserialize(data) {
49
+ let line, isCompressed, isV8
54
50
  const header = data.readUInt8(0)
55
51
  const valid = header === 0x00 || header === 0x01 || header === 0x02 || header === 0x03
56
- let isCompressed, isV8, decompresssed
57
52
  if(valid) {
58
53
  isCompressed = (header & 0x01) === 0x01
59
54
  isV8 = (header & 0x02) === 0x02
60
55
  line = data.subarray(1) // remove byte header
61
56
  } else {
62
57
  isCompressed = isV8 = false
63
- line = data
58
+ try {
59
+ return JSON.parse(data.toString('utf-8').trim())
60
+ } catch (e) {
61
+ throw new Error('Failed to deserialize legacy JSON data')
62
+ }
64
63
  }
65
64
  if (isCompressed) {
66
- let err
67
- const buffer = await this.decompress(line).catch(e => err = e)
68
- if(!err) {
69
- decompresssed = true
70
- line = buffer
71
- }
65
+ const compressionType = isV8 ? 'deflate' : 'brotli'
66
+ line = await this.decompress(line, compressionType).catch(e => err = e)
72
67
  }
73
68
  if (isV8) {
74
69
  try {
@@ -80,41 +75,43 @@ export default class Serializer extends EventEmitter {
80
75
  try {
81
76
  return JSON.parse(line.toString('utf-8').trim())
82
77
  } catch (e) {
83
- console.error('Failed to deserialize', header, line.toString('utf-8').trim())
84
78
  throw new Error('Failed to deserialize JSON data')
85
79
  }
86
80
  }
87
81
  }
88
82
 
89
- compress(data) {
83
+ compress(data, type) {
90
84
  return new Promise((resolve, reject) => {
91
- zlib.brotliCompress(data, this.brotliOptions, (err, buffer) => {
85
+ const callback = (err, buffer) => {
92
86
  if (err) {
93
87
  reject(err)
94
88
  } else {
95
89
  resolve(buffer)
96
90
  }
97
- })
91
+ }
92
+ if(type === 'brotli') {
93
+ zlib.brotliCompress(data, this.brotliOptions, callback)
94
+ } else {
95
+ zlib.deflate(data, callback)
96
+ }
98
97
  })
99
98
  }
100
99
 
101
- decompress(data) {
100
+ decompress(data, type) {
102
101
  return new Promise((resolve, reject) => {
103
- zlib.brotliDecompress(data, (err, buffer) => {
102
+ const callback = (err, buffer) => {
104
103
  if (err) {
105
104
  reject(err)
106
105
  } else {
107
106
  resolve(buffer)
108
107
  }
109
- })
108
+ }
109
+ if(type === 'brotli') {
110
+ zlib.brotliDecompress(data, callback)
111
+ } else {
112
+ zlib.inflate(data, callback)
113
+ }
110
114
  })
111
115
  }
112
-
113
- async safeDeserialize(json) {
114
- try {
115
- return await this.deserialize(json)
116
- } catch (e) {
117
- return null
118
- }
119
- }
116
+
120
117
  }
package/test/README.md ADDED
@@ -0,0 +1,13 @@
1
+ ## Test Results
2
+ The following are the results of the automated tests conducted on my PC for different serialization formats and compression methods.
3
+
4
+ | Format | Size (bytes) | Time elapsed (ms) |
5
+ |-------------------------------|--------------|--------------------|
6
+ | JSON | 1117 | 21 |
7
+ | V8 Serialization | 1124 | 12 &#127942; |
8
+ | JSON with Brotli Compression | 1038 &#127942; | 20 |
9
+ | V8 with Brotli Compression | 1052 | 15 |
10
+
11
+ When using V8 for serialization, be aware that serialized data may not deserialize correctly across different versions of V8 (which powers Chromium, Node.js, and Electron), leading to potential data loss or corruption. This issue is specific to V8 serialization; however, when using JSON, the data remains universal and compatible across environments.
12
+
13
+ To avoid this issue, always use your V8 database with the same version of Node.js.
Binary file
Binary file
Binary file
package/test/test-v8.jdb CHANGED
Binary file
package/test/test.mjs CHANGED
@@ -50,13 +50,8 @@ const runTests = async (id, name, format, opts) => {
50
50
  // Path to the test file
51
51
  const testFilePath = path.join(__dirname, 'test-' + name + '.jdb');
52
52
 
53
- // Function to clear the test file before each run
54
- const clearTestFile = () => {
55
- fs.writeFileSync(testFilePath, '', { encoding: null });
56
- }
57
-
58
53
  console.log('Battle #' + id + ' (' + format + ') is starting...\n');
59
- clearTestFile(); // Clear the file before starting the tests
54
+ fs.writeFileSync(testFilePath, '', { encoding: null }); // Clear the file before starting the tests
60
55
  const start = Date.now()
61
56
  const db = new Database(testFilePath, opts); // Instantiate the database
62
57
 
@@ -97,7 +92,6 @@ const runTests = async (id, name, format, opts) => {
97
92
 
98
93
  // 5. Test data deletion
99
94
  await db.delete({ name: character.name + ' Updated' });
100
-
101
95
  results = await db.query({ id: { '<=': 2 } });
102
96
  const pass5 = results.length === 1;
103
97
  const pass6 = results[0].name === 'Sub-Zero';
@@ -108,43 +102,65 @@ const runTests = async (id, name, format, opts) => {
108
102
  // End the battle and log the result
109
103
  if(pass1 && pass2 && pass4 && pass5 && pass6) {
110
104
  let err, elapsed = Date.now() - start;
111
- elapsed = elapsed < 1000 ? elapsed + 'ms' : (elapsed / 1000).toFixed(3) + 's';
112
105
  const { size } = await fs.promises.stat(testFilePath);
113
- benchmarks[format] = { elapsed, size };
106
+ if(!benchmarks[format]) {
107
+ benchmarks[format] = { elapsed, size }
108
+ } else {
109
+ benchmarks[format].elapsed = (elapsed + benchmarks[format].elapsed)
110
+ benchmarks[format].size = (size + benchmarks[format].size)
111
+ }
114
112
  console.log(`\nBattle #${id} ended: All tests with format "${format}" ran successfully! Fatality avoided this time.\n\n`);
113
+ global.gc()
115
114
  } else {
116
115
  benchmarks[format] = { elapsed: 'Error', size: 'Error' };
116
+ global.gc();
117
117
  throw `\nBattle #${id} ended: Some tests failed with format "${format}"! Time to train harder.\n\n`;
118
118
  }
119
119
  }
120
120
 
121
121
  async function runAllTests() {
122
- let err
123
- await runTests(1, 'json', 'JSON', {
124
- indexes: {id: 'number', name: 'string'},
125
- v8: false,
126
- compress: false,
127
- compressIndex: false
128
- }).catch(e => err = e)
129
- await runTests(2, 'v8', 'V8 serialization', {
130
- indexes: {id: 'number', name: 'string'},
131
- v8: true,
132
- compress: false,
133
- compressIndex: false
134
- }).catch(e => err = e)
135
- await runTests(3, 'json-compressed', 'JSON with Brotli compression', {
136
- indexes: {id: 'number', name: 'string'},
137
- v8: false,
138
- compress: false,
139
- compressIndex: true
140
- }).catch(e => err = e)
141
- await runTests(3, 'v8-compressed', 'V8 with Brotli compression', {
142
- indexes: {id: 'number', name: 'string'},
143
- v8: true,
144
- compress: false,
145
- compressIndex: true
146
- }).catch(e => err = e)
122
+ const depth = 100
123
+ let err, i = 1
124
+ let tests = [
125
+ ['json', 'JSON', { indexes: { id: 'number', name: 'string' }, v8: false, compress: false, compressIndex: false }],
126
+ ['v8', 'V8 serialization', { indexes: { id: 'number', name: 'string' }, v8: true, compress: false, compressIndex: false }],
127
+ ['json-compressed', 'JSON with Brotli compression', { indexes: { id: 'number', name: 'string' }, v8: false, compress: false, compressIndex: true }],
128
+ ['v8-compressed', 'V8 with Deflate compression', { indexes: { id: 'number', name: 'string' }, v8: true, compress: false, compressIndex: true }]
129
+ ]
130
+ tests = Array(depth).fill(tests).flat()
131
+ tests = tests.map(value => ({ value, sort: Math.random() })).sort((a, b) => a.sort - b.sort).map(({ value }) => value)
132
+ for(const test of tests) {
133
+ await runTests(i++, test[0], test[1], test[2]).catch(e => {
134
+ benchmarks[test[1]] = { elapsed: 'Error', size: 'Error' };
135
+ console.error(e)
136
+ err = e
137
+ })
138
+ }
139
+ const winners = {}
140
+ for (const [format, result] of Object.entries(benchmarks)) {
141
+ if (result.elapsed !== 'Error' && result.size !== 'Error') {
142
+ if (typeof(winners.elapsed) === 'undefined' || result.elapsed < winners.elapsed) {
143
+ winners.elapsed = result.elapsed
144
+ winners.format = format
145
+ }
146
+ if (typeof(winners.size) === 'undefined' || result.size < winners.size) {
147
+ winners.size = result.size
148
+ winners.format = format
149
+ }
150
+ }
151
+ }
152
+ for(const format in benchmarks) {
153
+ for(const prop of ['elapsed', 'size']) {
154
+ if(benchmarks[format][prop] === winners[prop]) {
155
+ benchmarks[format][prop] += ' \uD83C\uDFC6'
156
+ }
157
+ }
158
+ }
159
+ console.log('Benchmarks results after '+ tests.length +' battles:')
147
160
  console.table(benchmarks)
161
+ // setInterval(() => {}, 1000)
162
+ // global.Database = Database
163
+ // global.__dirname = __dirname
148
164
  process.exit(err ? 1 : 0)
149
165
  }
150
166
 
@@ -1,21 +0,0 @@
1
- export default class Serializer {
2
-
3
- constructor(opts={}) {
4
- this.opts = Object.assign({}, opts)
5
- }
6
-
7
- async serialize(data, opts={}) {
8
- return Buffer.from(JSON.stringify(data) + (opts.linebreak !== false ? '\n' : ''), 'utf-8')
9
- }
10
-
11
- async deserialize(data, opts={}) {
12
- const line = data.toString('utf-8')
13
- try {
14
- return JSON.parse(line)
15
- } catch (e) {
16
- console.error('Failed to deserialize', line)
17
- throw new Error('Failed to deserialize JSON data')
18
- }
19
- }
20
-
21
- }