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