idb-ts 3.15.0 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.cjs CHANGED
@@ -219,6 +219,34 @@ class QueryBuilder {
219
219
  this.groupField = field;
220
220
  return this;
221
221
  }
222
+ clone() {
223
+ const copy = new QueryBuilder(this.db, this.storeName, this.transaction);
224
+ copy.clauses = [...this.clauses];
225
+ copy.orderField = this.orderField;
226
+ copy.orderDirection = this.orderDirection;
227
+ copy.limitCount = this.limitCount;
228
+ copy.offsetCount = this.offsetCount;
229
+ copy.indexName = this.indexName;
230
+ copy.rangeStart = this.rangeStart;
231
+ copy.rangeEnd = this.rangeEnd;
232
+ copy.groupField = this.groupField;
233
+ copy.pendingConnector = this.pendingConnector;
234
+ return copy;
235
+ }
236
+ reset() {
237
+ this.clauses = [];
238
+ this.orderField = undefined;
239
+ this.orderDirection = 'asc';
240
+ this.limitCount = undefined;
241
+ this.offsetCount = undefined;
242
+ this.indexName = undefined;
243
+ this.rangeStart = undefined;
244
+ this.rangeEnd = undefined;
245
+ this.currentField = undefined;
246
+ this.groupField = undefined;
247
+ this.pendingConnector = 'and';
248
+ return this;
249
+ }
222
250
  loadCandidates() {
223
251
  return tslib.__awaiter(this, void 0, void 0, function* () {
224
252
  const store = this.transaction
@@ -235,6 +263,12 @@ class QueryBuilder {
235
263
  }
236
264
  createReadRequest(store) {
237
265
  if (!this.indexName) {
266
+ if (this.rangeStart !== undefined || this.rangeEnd !== undefined) {
267
+ throw new Error('range() requires useIndex(): key ranges are applied at the ' +
268
+ 'IndexedDB layer through an index. Either call useIndex(indexName) ' +
269
+ 'before range(), or express the bounds as in-memory filters via ' +
270
+ 'where(field).between(start, end) / gte() / lte().');
271
+ }
238
272
  return store.getAll();
239
273
  }
240
274
  if (!store.indexNames.contains(this.indexName)) {
@@ -431,12 +465,65 @@ class QueryBuilder {
431
465
  const INTERNAL_CREATED_AT_FIELD = '__idb_createdAt';
432
466
  const INTERNAL_UPDATED_AT_FIELD = '__idb_updatedAt';
433
467
  class KeyGenerators {
468
+ static getRandomValues(array) {
469
+ var _a;
470
+ if (typeof globalThis !== 'undefined' && ((_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.getRandomValues)) {
471
+ globalThis.crypto.getRandomValues(array);
472
+ return array;
473
+ }
474
+ try {
475
+ const { randomBytes } = require('crypto');
476
+ const bytes = randomBytes(array.length);
477
+ return new Uint8Array(bytes);
478
+ }
479
+ catch (_b) {
480
+ for (let i = 0; i < array.length; i++) {
481
+ array[i] = Math.random() * 256 | 0;
482
+ }
483
+ return array;
484
+ }
485
+ }
486
+ static uuidV4() {
487
+ var _a;
488
+ try {
489
+ if (typeof globalThis !== 'undefined' && ((_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.randomUUID)) {
490
+ return globalThis.crypto.randomUUID();
491
+ }
492
+ }
493
+ catch (_b) {
494
+ }
495
+ try {
496
+ const { randomUUID } = require('crypto');
497
+ return randomUUID();
498
+ }
499
+ catch (_c) {
500
+ }
501
+ const bytes = new Uint8Array(16);
502
+ this.getRandomValues(bytes);
503
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
504
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
505
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
506
+ return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}`;
507
+ }
508
+ static uuidV6() {
509
+ const bytes = new Uint8Array(16);
510
+ this.getRandomValues(bytes);
511
+ const now = Date.now();
512
+ const timeHi = Math.floor(now / 1000) & 0xffffffff;
513
+ const timeLo = (now % 1000) * 4096;
514
+ bytes[0] = (timeHi >>> 24) & 0xff;
515
+ bytes[1] = (timeHi >>> 16) & 0xff;
516
+ bytes[2] = (timeHi >>> 8) & 0xff;
517
+ bytes[3] = timeHi & 0xff;
518
+ bytes[4] = (timeLo >>> 8) & 0xff;
519
+ bytes[5] = timeLo & 0xff;
520
+ bytes[6] = (bytes[6] & 0x0f) | 0x60;
521
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
522
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
523
+ return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}`;
524
+ }
434
525
  static uuid() {
435
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
436
- const r = (Math.random() * 16) | 0;
437
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
438
- return v.toString(16);
439
- });
526
+ return this.uuidV4();
440
527
  }
441
528
  static timestamp() {
442
529
  return Date.now();
@@ -460,6 +547,13 @@ function KeyPath(options) {
460
547
  }
461
548
  function CompositeKeyPath(fields, options) {
462
549
  return (target) => {
550
+ if (options === null || options === void 0 ? void 0 : options.generator) {
551
+ throw new Error(`Key generators are not supported for composite keys on class ${target.name}. ` +
552
+ 'Provide all key fields explicitly before calling create().');
553
+ }
554
+ if (options === null || options === void 0 ? void 0 : options.autoIncrement) {
555
+ throw new Error(`autoIncrement is not supported for composite keys on class ${target.name}.`);
556
+ }
463
557
  const metadata = {
464
558
  fields: fields,
465
559
  options: options,
@@ -489,6 +583,17 @@ function Validate(predicate, message) {
489
583
  Reflect.defineMetadata('validators', nextRules, constructor);
490
584
  };
491
585
  }
586
+ function Calculated(compute) {
587
+ return (target, propertyKey) => {
588
+ const constructor = target.constructor;
589
+ const existing = Reflect.getMetadata('calculated', constructor) || [];
590
+ const nextFields = [
591
+ ...existing,
592
+ { field: propertyKey, compute },
593
+ ];
594
+ Reflect.defineMetadata('calculated', nextFields, constructor);
595
+ };
596
+ }
492
597
  function RetentionPolicy(options) {
493
598
  return (target) => {
494
599
  var _a, _b;
@@ -518,6 +623,127 @@ function DataClass(options = {}) {
518
623
  Reflect.defineMetadata('version', version, target);
519
624
  };
520
625
  }
626
+ class EntitySchema {
627
+ constructor(className, storeName, version, keyPath, autoIncrement, indexes) {
628
+ this.className = className;
629
+ this.storeName = storeName;
630
+ this.version = version;
631
+ this.keyPath = keyPath;
632
+ this.autoIncrement = autoIncrement;
633
+ this.indexes = indexes;
634
+ }
635
+ static fromClass(cls) {
636
+ var _a;
637
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
638
+ const rawIndexes = (Reflect.getMetadata('indexes', cls) || []);
639
+ const indexes = rawIndexes.map((entry) => {
640
+ var _a;
641
+ return typeof entry === 'string'
642
+ ? { name: entry, options: { unique: false } }
643
+ : { name: entry.field, options: (_a = entry.options) !== null && _a !== void 0 ? _a : { unique: false } };
644
+ });
645
+ return new EntitySchema(cls.name, cls.name.toLowerCase(), Reflect.getMetadata('version', cls) || 1, keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.fields, ((_a = keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.autoIncrement) === true, indexes);
646
+ }
647
+ hasIndex(name) {
648
+ return this.indexes.some((index) => index.name === name);
649
+ }
650
+ }
651
+ class SchemaMigrator {
652
+ constructor(schemas, logDebug, logWarn) {
653
+ this.schemas = schemas;
654
+ this.logDebug = logDebug;
655
+ this.logWarn = logWarn;
656
+ }
657
+ get targetVersion() {
658
+ return Math.max(1, ...this.schemas.map((schema) => schema.version));
659
+ }
660
+ applyTo(db, transaction) {
661
+ this.schemas.forEach((schema) => {
662
+ if (!db.objectStoreNames.contains(schema.storeName)) {
663
+ this.createStore(db, schema);
664
+ }
665
+ else if (transaction) {
666
+ this.syncStore(transaction.objectStore(schema.storeName), schema);
667
+ }
668
+ });
669
+ this.reportOrphanedStores(db);
670
+ }
671
+ detectFixableDrift(db) {
672
+ const drift = [];
673
+ const existing = this.schemas.filter((schema) => db.objectStoreNames.contains(schema.storeName));
674
+ this.schemas.forEach((schema) => {
675
+ if (!db.objectStoreNames.contains(schema.storeName)) {
676
+ drift.push(`missing object store '${schema.storeName}'`);
677
+ }
678
+ });
679
+ if (existing.length) {
680
+ const transaction = db.transaction(existing.map((schema) => schema.storeName), 'readonly');
681
+ existing.forEach((schema) => {
682
+ const store = transaction.objectStore(schema.storeName);
683
+ schema.indexes.forEach(({ name }) => {
684
+ if (!store.indexNames.contains(name)) {
685
+ drift.push(`missing index '${name}' on '${schema.storeName}'`);
686
+ }
687
+ });
688
+ Array.from(store.indexNames).forEach((name) => {
689
+ if (!schema.hasIndex(name)) {
690
+ drift.push(`stale index '${name}' on '${schema.storeName}'`);
691
+ }
692
+ });
693
+ });
694
+ }
695
+ return drift;
696
+ }
697
+ createStore(db, schema) {
698
+ const options = {};
699
+ if (schema.keyPath !== undefined) {
700
+ options.keyPath = schema.keyPath;
701
+ }
702
+ if (schema.autoIncrement) {
703
+ options.autoIncrement = true;
704
+ }
705
+ const store = db.createObjectStore(schema.storeName, options);
706
+ schema.indexes.forEach(({ name, options: indexOptions }) => {
707
+ store.createIndex(name, name, indexOptions);
708
+ });
709
+ this.logDebug(`Created object store '${schema.storeName}' (entity version ${schema.version})`);
710
+ }
711
+ syncStore(store, schema) {
712
+ if (!this.keyPathMatches(store, schema)) {
713
+ this.logWarn(`Key path of object store '${schema.storeName}' differs from the ` +
714
+ `declaration on ${schema.className}. IndexedDB cannot change a ` +
715
+ 'store\'s key path in place; migrate the data to a new entity or ' +
716
+ 'delete the database to apply this change.');
717
+ }
718
+ schema.indexes.forEach(({ name, options }) => {
719
+ if (!store.indexNames.contains(name)) {
720
+ store.createIndex(name, name, options);
721
+ this.logDebug(`Added index '${name}' to '${schema.storeName}'`);
722
+ }
723
+ });
724
+ Array.from(store.indexNames).forEach((name) => {
725
+ if (!schema.hasIndex(name)) {
726
+ store.deleteIndex(name);
727
+ this.logDebug(`Removed stale index '${name}' from '${schema.storeName}'`);
728
+ }
729
+ });
730
+ }
731
+ keyPathMatches(store, schema) {
732
+ const normalise = (keyPath) => (Array.isArray(keyPath) ? keyPath.join(',') : (keyPath !== null && keyPath !== void 0 ? keyPath : ''));
733
+ return (normalise(store.keyPath) === normalise(schema.keyPath) &&
734
+ store.autoIncrement === schema.autoIncrement);
735
+ }
736
+ reportOrphanedStores(db) {
737
+ const declared = new Set(this.schemas.map((schema) => schema.storeName));
738
+ Array.from(db.objectStoreNames).forEach((storeName) => {
739
+ if (!declared.has(storeName)) {
740
+ this.logWarn(`Object store '${storeName}' has no registered entity class. ` +
741
+ 'Its data is preserved; register the entity again or delete the ' +
742
+ 'store manually if it is no longer needed.');
743
+ }
744
+ });
745
+ }
746
+ }
521
747
  class Database {
522
748
  constructor(dbName, classes, printEnabled = false) {
523
749
  this.db = null;
@@ -540,7 +766,9 @@ class Database {
540
766
  throw new Error('All classes should be decorated with @DataClass.');
541
767
  }
542
768
  this.classes = classes;
543
- this.dbVersion = this.calculateDatabaseVersion();
769
+ this.schemas = classes.map((cls) => EntitySchema.fromClass(cls));
770
+ this.migrator = new SchemaMigrator(this.schemas, this.printDebug, this.printError);
771
+ this.dbVersion = this.migrator.targetVersion;
544
772
  this.retentionPolicies = this.classes
545
773
  .map((cls) => {
546
774
  const policy = Reflect.getMetadata('retention_policy', cls);
@@ -555,9 +783,52 @@ class Database {
555
783
  })
556
784
  .filter((policy) => policy !== null);
557
785
  }
558
- calculateDatabaseVersion() {
559
- const versions = this.classes.map((cls) => Reflect.getMetadata('version', cls) || 1);
560
- return Math.max(...versions);
786
+ readPersistedVersion() {
787
+ return tslib.__awaiter(this, void 0, void 0, function* () {
788
+ var _a;
789
+ const databasesFn = indexedDB.databases;
790
+ if (typeof databasesFn === 'function') {
791
+ try {
792
+ const infos = yield databasesFn.call(indexedDB);
793
+ const info = infos.find((entry) => entry.name === this.dbName);
794
+ return (_a = info === null || info === void 0 ? void 0 : info.version) !== null && _a !== void 0 ? _a : 0;
795
+ }
796
+ catch (_b) {
797
+ }
798
+ }
799
+ return new Promise((resolve) => {
800
+ const request = indexedDB.open(this.dbName);
801
+ request.onupgradeneeded = () => {
802
+ var _a;
803
+ (_a = request.transaction) === null || _a === void 0 ? void 0 : _a.abort();
804
+ resolve(0);
805
+ };
806
+ request.onsuccess = () => {
807
+ const version = request.result.version;
808
+ request.result.close();
809
+ resolve(version);
810
+ };
811
+ request.onerror = () => resolve(0);
812
+ });
813
+ });
814
+ }
815
+ openConnection(version) {
816
+ return new Promise((resolve, reject) => {
817
+ const request = indexedDB.open(this.dbName, version);
818
+ request.onupgradeneeded = (event) => {
819
+ var _a;
820
+ this.printDebug(`Database upgrade from version ${event.oldVersion} to ${(_a = event.newVersion) !== null && _a !== void 0 ? _a : version}`);
821
+ this.migrator.applyTo(request.result, request.transaction);
822
+ };
823
+ request.onblocked = () => {
824
+ this.printDebug(`Opening '${this.dbName}' at version ${version} is blocked by another open connection.`);
825
+ };
826
+ request.onsuccess = () => resolve(request.result);
827
+ request.onerror = () => {
828
+ this.printError('Error initializing database:', request.error);
829
+ reject(request.error);
830
+ };
831
+ });
561
832
  }
562
833
  static build(dbName, classes) {
563
834
  return tslib.__awaiter(this, void 0, void 0, function* () {
@@ -569,77 +840,25 @@ class Database {
569
840
  }
570
841
  initDB() {
571
842
  return tslib.__awaiter(this, void 0, void 0, function* () {
572
- return new Promise((resolve, reject) => {
573
- const request = indexedDB.open(this.dbName, this.dbVersion);
574
- request.onupgradeneeded = (event) => {
575
- const db = request.result;
576
- const oldVersion = event.oldVersion;
577
- const newVersion = event.newVersion || this.dbVersion;
578
- this.printDebug(`Database upgrade from version ${oldVersion} to ${newVersion}`);
579
- this.classes.forEach((cls) => {
580
- var _a;
581
- const keyPathMetadata = Reflect.getMetadata('keypath', cls);
582
- const indexFields = Reflect.getMetadata('indexes', cls) || [];
583
- const classVersion = Reflect.getMetadata('version', cls) || 1;
584
- const storeName = cls.name.toLowerCase();
585
- if (classVersion > oldVersion) {
586
- if (!db.objectStoreNames.contains(storeName)) {
587
- this.printDebug(`Creating object store: ${storeName} (version ${classVersion})`);
588
- const storeOptions = {};
589
- if (keyPathMetadata) {
590
- storeOptions.keyPath = keyPathMetadata.fields;
591
- if ((_a = keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.autoIncrement) {
592
- storeOptions.autoIncrement = true;
593
- }
594
- }
595
- const store = db.createObjectStore(storeName, storeOptions);
596
- indexFields.forEach((indexField) => {
597
- var _a;
598
- const indexName = typeof indexField === 'string'
599
- ? indexField
600
- : indexField.field;
601
- const indexOptions = typeof indexField === 'string'
602
- ? { unique: false }
603
- : ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
604
- if (!store.indexNames.contains(indexName)) {
605
- store.createIndex(indexName, indexName, indexOptions);
606
- }
607
- });
608
- }
609
- else {
610
- this.printDebug(`Updating object store: ${storeName} (version ${classVersion})`);
611
- const transaction = request.transaction;
612
- if (transaction) {
613
- const store = transaction.objectStore(storeName);
614
- indexFields.forEach((indexField) => {
615
- var _a;
616
- const indexName = typeof indexField === 'string'
617
- ? indexField
618
- : indexField.field;
619
- const indexOptions = typeof indexField === 'string'
620
- ? { unique: false }
621
- : ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
622
- if (!store.indexNames.contains(indexName)) {
623
- this.printDebug(`Adding index: ${indexName} to ${storeName}`);
624
- store.createIndex(indexName, indexName, indexOptions);
625
- }
626
- });
627
- }
628
- }
629
- }
630
- });
631
- };
632
- request.onsuccess = () => {
633
- this.db = request.result;
634
- this.printDebug(`Database initialized (version ${this.dbVersion}) with object stores for: ${this.classes.map((cls) => `${cls.name}(v${Reflect.getMetadata('version', cls) || 1})`).join(', ')}`);
635
- this.startRetentionCleanup();
636
- resolve();
637
- };
638
- request.onerror = () => {
639
- this.printError('Error initializing database:', request.error);
640
- reject(request.error);
641
- };
642
- });
843
+ const persistedVersion = yield this.readPersistedVersion();
844
+ if (persistedVersion > this.dbVersion) {
845
+ this.printDebug(`Declared schema version ${this.dbVersion} is lower than the ` +
846
+ `persisted database version ${persistedVersion}. IndexedDB cannot ` +
847
+ `downgrade; opening at version ${persistedVersion}.`);
848
+ }
849
+ let db = yield this.openConnection(Math.max(this.dbVersion, persistedVersion));
850
+ const drift = this.migrator.detectFixableDrift(db);
851
+ if (drift.length > 0) {
852
+ this.printDebug(`Schema drift detected without a version bump: ${drift.join('; ')}. ` +
853
+ `Reopening at version ${db.version + 1} to reconcile.`);
854
+ const nextVersion = db.version + 1;
855
+ db.close();
856
+ db = yield this.openConnection(nextVersion);
857
+ }
858
+ this.db = db;
859
+ this.dbVersion = db.version;
860
+ this.printDebug(`Database initialized (version ${this.dbVersion}) with object stores for: ${this.schemas.map((schema) => `${schema.className}(v${schema.version})`).join(', ')}`);
861
+ this.startRetentionCleanup();
643
862
  });
644
863
  }
645
864
  generateEntityRepositories() {
@@ -751,11 +970,17 @@ class Database {
751
970
  });
752
971
  }
753
972
  createEntityRepository(cls, transaction) {
754
- const self = this;
755
973
  const creationTimestampField = INTERNAL_CREATED_AT_FIELD;
756
974
  const updateTimestampField = INTERNAL_UPDATED_AT_FIELD;
757
975
  const validators = (Reflect.getMetadata('validators', cls) ||
758
976
  []);
977
+ const calculatedFields = (Reflect.getMetadata('calculated', cls) ||
978
+ []);
979
+ const applyCalculatedFields = (item) => {
980
+ calculatedFields.forEach(({ field, compute }) => {
981
+ item[field] = compute(item);
982
+ });
983
+ };
759
984
  const validateItem = (item) => {
760
985
  const failures = [];
761
986
  validators.forEach((rule) => {
@@ -786,7 +1011,10 @@ class Database {
786
1011
  }
787
1012
  switch (generator) {
788
1013
  case 'uuid':
789
- return KeyGenerators.uuid();
1014
+ case 'uuidv4':
1015
+ return KeyGenerators.uuidV4();
1016
+ case 'uuidv6':
1017
+ return KeyGenerators.uuidV6();
790
1018
  case 'timestamp':
791
1019
  return KeyGenerators.timestamp();
792
1020
  case 'random':
@@ -860,11 +1088,11 @@ class Database {
860
1088
  }
861
1089
  };
862
1090
  return {
863
- query() {
864
- if (!self.db)
1091
+ query: () => {
1092
+ if (!this.db)
865
1093
  throw new Error('Database not initialized.');
866
1094
  const storeName = cls.name.toLowerCase();
867
- return new QueryBuilder(self.db, storeName, transaction);
1095
+ return new QueryBuilder(this.db, storeName, transaction);
868
1096
  },
869
1097
  create: (item) => tslib.__awaiter(this, void 0, void 0, function* () {
870
1098
  var _a;
@@ -881,6 +1109,7 @@ class Database {
881
1109
  }
882
1110
  }
883
1111
  }
1112
+ applyCalculatedFields(item);
884
1113
  validateItem(item);
885
1114
  applyTimestampFields(item);
886
1115
  return this.performOperation(cls.name, 'readwrite', (store) => {
@@ -908,6 +1137,7 @@ class Database {
908
1137
  }, transaction);
909
1138
  }),
910
1139
  update: (item) => tslib.__awaiter(this, void 0, void 0, function* () {
1140
+ applyCalculatedFields(item);
911
1141
  validateItem(item);
912
1142
  return this.performOperation(cls.name, 'readwrite', (store) => {
913
1143
  return updateStoredItem(store, item).then(() => {
@@ -1015,6 +1245,18 @@ class Database {
1015
1245
  });
1016
1246
  }, transaction);
1017
1247
  }),
1248
+ getKeys: () => tslib.__awaiter(this, void 0, void 0, function* () {
1249
+ return this.performOperation(cls.name, 'readonly', (store) => {
1250
+ const request = store.getAllKeys();
1251
+ return new Promise((resolve, reject) => {
1252
+ request.onsuccess = () => {
1253
+ this.printDebug(`Keys for ${cls.name}:`, request.result);
1254
+ resolve(request.result);
1255
+ };
1256
+ request.onerror = () => reject(request.error);
1257
+ });
1258
+ }, transaction);
1259
+ }),
1018
1260
  exists: (key) => tslib.__awaiter(this, void 0, void 0, function* () {
1019
1261
  return this.performOperation(cls.name, 'readonly', (store) => {
1020
1262
  const request = store.count(key);
@@ -1129,6 +1371,107 @@ class Database {
1129
1371
  }
1130
1372
  });
1131
1373
  }
1374
+ exportDatabase() {
1375
+ return tslib.__awaiter(this, void 0, void 0, function* () {
1376
+ const dump = {};
1377
+ for (const cls of this.classes) {
1378
+ const repository = this.entityRepositories.get(cls.name);
1379
+ if (repository) {
1380
+ dump[cls.name] = yield repository.list();
1381
+ }
1382
+ }
1383
+ return dump;
1384
+ });
1385
+ }
1386
+ importDatabase(dump_1) {
1387
+ return tslib.__awaiter(this, arguments, void 0, function* (dump, options = {}) {
1388
+ for (const [entityName, records] of Object.entries(dump)) {
1389
+ const entityClass = this.classes.find((cls) => cls.name === entityName);
1390
+ if (!entityClass) {
1391
+ this.printDebug(`Skipping import for unregistered entity '${entityName}'.`);
1392
+ continue;
1393
+ }
1394
+ if (!Array.isArray(records)) {
1395
+ this.printDebug(`Skipping import for '${entityName}': expected an array of records.`);
1396
+ continue;
1397
+ }
1398
+ yield this.performOperation(entityName, 'readwrite', (store) => {
1399
+ return new Promise((resolve, reject) => {
1400
+ const writeRecords = () => {
1401
+ let pending = records.length;
1402
+ if (!pending) {
1403
+ resolve();
1404
+ return;
1405
+ }
1406
+ records.forEach((record) => {
1407
+ const request = store.put(record);
1408
+ request.onsuccess = () => {
1409
+ pending -= 1;
1410
+ if (!pending)
1411
+ resolve();
1412
+ };
1413
+ request.onerror = () => reject(request.error);
1414
+ });
1415
+ };
1416
+ if (options.clear) {
1417
+ const clearRequest = store.clear();
1418
+ clearRequest.onsuccess = () => writeRecords();
1419
+ clearRequest.onerror = () => reject(clearRequest.error);
1420
+ return;
1421
+ }
1422
+ writeRecords();
1423
+ });
1424
+ });
1425
+ this.printDebug(`Imported ${records.length} record(s) into '${entityName}'.`);
1426
+ }
1427
+ });
1428
+ }
1429
+ pushTo(adapter) {
1430
+ return tslib.__awaiter(this, void 0, void 0, function* () {
1431
+ for (const cls of this.classes) {
1432
+ const repository = this.entityRepositories.get(cls.name);
1433
+ if (!repository) {
1434
+ continue;
1435
+ }
1436
+ const records = yield repository.list();
1437
+ yield adapter.push(cls.name, records);
1438
+ this.printDebug(`Pushed ${records.length} record(s) of '${cls.name}' to adapter.`);
1439
+ }
1440
+ });
1441
+ }
1442
+ pullFrom(adapter) {
1443
+ return tslib.__awaiter(this, void 0, void 0, function* () {
1444
+ for (const cls of this.classes) {
1445
+ const records = yield adapter.pull(cls.name);
1446
+ if (records === undefined) {
1447
+ continue;
1448
+ }
1449
+ if (!Array.isArray(records)) {
1450
+ this.printDebug(`Skipping pull for '${cls.name}': adapter did not return an array.`);
1451
+ continue;
1452
+ }
1453
+ yield this.performOperation(cls.name, 'readwrite', (store) => {
1454
+ return new Promise((resolve, reject) => {
1455
+ let pending = records.length;
1456
+ if (!pending) {
1457
+ resolve();
1458
+ return;
1459
+ }
1460
+ records.forEach((record) => {
1461
+ const request = store.put(record);
1462
+ request.onsuccess = () => {
1463
+ pending -= 1;
1464
+ if (!pending)
1465
+ resolve();
1466
+ };
1467
+ request.onerror = () => reject(request.error);
1468
+ });
1469
+ });
1470
+ });
1471
+ this.printDebug(`Pulled ${records.length} record(s) of '${cls.name}' from adapter.`);
1472
+ }
1473
+ });
1474
+ }
1132
1475
  close() {
1133
1476
  if (this.retentionTimer) {
1134
1477
  clearInterval(this.retentionTimer);
@@ -1159,6 +1502,7 @@ class Database {
1159
1502
  }
1160
1503
  }
1161
1504
 
1505
+ exports.Calculated = Calculated;
1162
1506
  exports.CompositeKeyPath = CompositeKeyPath;
1163
1507
  exports.DataClass = DataClass;
1164
1508
  exports.Database = Database;