@aws-amplify/datastore 4.0.12 → 4.0.13-push-notification-dryrun.43

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.
Files changed (111) hide show
  1. package/lib/datastore/datastore.d.ts +29 -3
  2. package/lib/datastore/datastore.js +308 -147
  3. package/lib/datastore/datastore.js.map +1 -1
  4. package/lib/predicates/index.d.ts +77 -7
  5. package/lib/predicates/index.js +142 -122
  6. package/lib/predicates/index.js.map +1 -1
  7. package/lib/predicates/next.d.ts +51 -10
  8. package/lib/predicates/next.js +111 -91
  9. package/lib/predicates/next.js.map +1 -1
  10. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
  11. package/lib/storage/adapter/AsyncStorageAdapter.js +135 -532
  12. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  13. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  14. package/lib/storage/adapter/IndexedDBAdapter.d.ts +28 -29
  15. package/lib/storage/adapter/IndexedDBAdapter.js +490 -885
  16. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  17. package/lib/storage/adapter/StorageAdapterBase.d.ts +134 -0
  18. package/lib/storage/adapter/StorageAdapterBase.js +439 -0
  19. package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
  20. package/lib/storage/relationship.d.ts +9 -0
  21. package/lib/storage/relationship.js +9 -0
  22. package/lib/storage/relationship.js.map +1 -1
  23. package/lib/storage/storage.d.ts +1 -1
  24. package/lib/storage/storage.js +4 -3
  25. package/lib/storage/storage.js.map +1 -1
  26. package/lib/sync/index.d.ts +15 -1
  27. package/lib/sync/index.js +80 -13
  28. package/lib/sync/index.js.map +1 -1
  29. package/lib/sync/outbox.js +14 -7
  30. package/lib/sync/outbox.js.map +1 -1
  31. package/lib/sync/processors/mutation.d.ts +10 -1
  32. package/lib/sync/processors/mutation.js +33 -12
  33. package/lib/sync/processors/mutation.js.map +1 -1
  34. package/lib/sync/processors/subscription.d.ts +7 -1
  35. package/lib/sync/processors/subscription.js +196 -135
  36. package/lib/sync/processors/subscription.js.map +1 -1
  37. package/lib/sync/processors/sync.d.ts +1 -1
  38. package/lib/sync/processors/sync.js.map +1 -1
  39. package/lib/sync/utils.d.ts +66 -2
  40. package/lib/sync/utils.js +264 -16
  41. package/lib/sync/utils.js.map +1 -1
  42. package/lib/types.d.ts +9 -1
  43. package/lib/types.js.map +1 -1
  44. package/lib/util.d.ts +16 -0
  45. package/lib/util.js +31 -2
  46. package/lib/util.js.map +1 -1
  47. package/lib-esm/datastore/datastore.d.ts +29 -3
  48. package/lib-esm/datastore/datastore.js +310 -149
  49. package/lib-esm/datastore/datastore.js.map +1 -1
  50. package/lib-esm/predicates/index.d.ts +77 -7
  51. package/lib-esm/predicates/index.js +143 -123
  52. package/lib-esm/predicates/index.js.map +1 -1
  53. package/lib-esm/predicates/next.d.ts +51 -10
  54. package/lib-esm/predicates/next.js +111 -91
  55. package/lib-esm/predicates/next.js.map +1 -1
  56. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
  57. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +138 -535
  58. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  59. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  60. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +28 -29
  61. package/lib-esm/storage/adapter/IndexedDBAdapter.js +489 -884
  62. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  63. package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +134 -0
  64. package/lib-esm/storage/adapter/StorageAdapterBase.js +437 -0
  65. package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
  66. package/lib-esm/storage/relationship.d.ts +9 -0
  67. package/lib-esm/storage/relationship.js +9 -0
  68. package/lib-esm/storage/relationship.js.map +1 -1
  69. package/lib-esm/storage/storage.d.ts +1 -1
  70. package/lib-esm/storage/storage.js +4 -3
  71. package/lib-esm/storage/storage.js.map +1 -1
  72. package/lib-esm/sync/index.d.ts +15 -1
  73. package/lib-esm/sync/index.js +82 -15
  74. package/lib-esm/sync/index.js.map +1 -1
  75. package/lib-esm/sync/outbox.js +14 -7
  76. package/lib-esm/sync/outbox.js.map +1 -1
  77. package/lib-esm/sync/processors/mutation.d.ts +10 -1
  78. package/lib-esm/sync/processors/mutation.js +33 -12
  79. package/lib-esm/sync/processors/mutation.js.map +1 -1
  80. package/lib-esm/sync/processors/subscription.d.ts +7 -1
  81. package/lib-esm/sync/processors/subscription.js +197 -136
  82. package/lib-esm/sync/processors/subscription.js.map +1 -1
  83. package/lib-esm/sync/processors/sync.d.ts +1 -1
  84. package/lib-esm/sync/processors/sync.js.map +1 -1
  85. package/lib-esm/sync/utils.d.ts +66 -2
  86. package/lib-esm/sync/utils.js +261 -18
  87. package/lib-esm/sync/utils.js.map +1 -1
  88. package/lib-esm/types.d.ts +9 -1
  89. package/lib-esm/types.js.map +1 -1
  90. package/lib-esm/util.d.ts +16 -0
  91. package/lib-esm/util.js +32 -3
  92. package/lib-esm/util.js.map +1 -1
  93. package/package.json +12 -11
  94. package/src/datastore/datastore.ts +288 -159
  95. package/src/predicates/index.ts +145 -175
  96. package/src/predicates/next.ts +114 -81
  97. package/src/storage/adapter/AsyncStorageAdapter.ts +97 -563
  98. package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
  99. package/src/storage/adapter/IndexedDBAdapter.ts +318 -770
  100. package/src/storage/adapter/StorageAdapterBase.ts +545 -0
  101. package/src/storage/relationship.ts +9 -0
  102. package/src/storage/storage.ts +12 -9
  103. package/src/sync/index.ts +108 -20
  104. package/src/sync/outbox.ts +17 -11
  105. package/src/sync/processors/mutation.ts +35 -4
  106. package/src/sync/processors/subscription.ts +124 -10
  107. package/src/sync/processors/sync.ts +4 -1
  108. package/src/sync/utils.ts +285 -15
  109. package/src/types.ts +15 -2
  110. package/src/util.ts +40 -1
  111. package/CHANGELOG.md +0 -904
@@ -1,14 +1,10 @@
1
1
  import { ConsoleLogger as Logger } from '@aws-amplify/core';
2
2
  import * as idb from 'idb';
3
- import { ModelInstanceCreator } from '../../datastore/datastore';
4
- import { ModelPredicateCreator } from '../../predicates';
5
3
  import {
6
- InternalSchema,
7
4
  isPredicateObj,
8
5
  isPredicateGroup,
9
6
  ModelInstanceMetadata,
10
7
  ModelPredicate,
11
- NamespaceResolver,
12
8
  OpType,
13
9
  PaginationInput,
14
10
  PersistentModel,
@@ -16,24 +12,17 @@ import {
16
12
  PredicateObject,
17
13
  PredicatesGroup,
18
14
  QueryOne,
19
- RelationType,
20
15
  } from '../../types';
21
16
  import {
22
- getIndex,
23
- getIndexFromAssociation,
24
- isModelConstructor,
25
17
  isPrivateMode,
26
18
  traverseModel,
27
19
  validatePredicate,
28
20
  inMemoryPagination,
29
- NAMESPACES,
30
21
  keysEqual,
31
22
  getStorename,
32
- getIndexKeys,
33
- extractPrimaryKeyValues,
34
23
  isSafariCompatabilityMode,
35
24
  } from '../../util';
36
- import { Adapter } from './index';
25
+ import { StorageAdapterBase } from './StorageAdapterBase';
37
26
 
38
27
  const logger = new Logger('DataStore');
39
28
 
@@ -59,229 +48,135 @@ const logger = new Logger('DataStore');
59
48
  *
60
49
  */
61
50
  const MULTI_OR_CONDITION_SCAN_BREAKPOINT = 7;
51
+ //
52
+ const DB_VERSION = 3;
62
53
 
63
- const DB_NAME = 'amplify-datastore';
64
- class IndexedDBAdapter implements Adapter {
65
- private schema!: InternalSchema;
66
- private namespaceResolver!: NamespaceResolver;
67
- private modelInstanceCreator!: ModelInstanceCreator;
68
- private getModelConstructorByModelName?: (
69
- namsespaceName: NAMESPACES,
70
- modelName: string
71
- ) => PersistentModelConstructor<any>;
72
- private db!: idb.IDBPDatabase;
73
- private initPromise!: Promise<void>;
74
- private resolve!: (value?: any) => void;
75
- private reject!: (value?: any) => void;
76
- private dbName: string = DB_NAME;
54
+ class IndexedDBAdapter extends StorageAdapterBase {
55
+ protected db!: idb.IDBPDatabase;
77
56
  private safariCompatabilityMode: boolean = false;
78
57
 
79
- private getStorenameForModel(
80
- modelConstructor: PersistentModelConstructor<any>
81
- ) {
82
- const namespace = this.namespaceResolver(modelConstructor);
83
- const { name: modelName } = modelConstructor;
84
-
85
- return getStorename(namespace, modelName);
86
- }
87
-
88
- // Retrieves primary key values from a model
89
- private getIndexKeyValuesFromModel<T extends PersistentModel>(
90
- model: T
91
- ): string[] {
92
- const modelConstructor = Object.getPrototypeOf(model)
93
- .constructor as PersistentModelConstructor<T>;
94
- const namespaceName = this.namespaceResolver(modelConstructor);
95
-
96
- const keys = getIndexKeys(
97
- this.schema.namespaces[namespaceName],
98
- modelConstructor.name
99
- );
100
-
101
- return extractPrimaryKeyValues(model, keys);
58
+ // checks are called by StorageAdapterBase class
59
+ protected async preSetUpChecks() {
60
+ await this.checkPrivate();
61
+ await this.setSafariCompatabilityMode();
102
62
  }
103
63
 
104
- private async checkPrivate() {
105
- const isPrivate = await isPrivateMode().then(isPrivate => {
106
- return isPrivate;
107
- });
108
- if (isPrivate) {
109
- logger.error("IndexedDB not supported in this browser's private mode");
110
- return Promise.reject(
111
- "IndexedDB not supported in this browser's private mode"
112
- );
113
- } else {
114
- return Promise.resolve();
115
- }
64
+ protected async preOpCheck() {
65
+ await this.checkPrivate();
116
66
  }
117
67
 
118
68
  /**
119
- * Whether the browser's implementation of IndexedDB is coercing single-field
120
- * indexes to a scalar key.
69
+ * Initialize IndexedDB database
70
+ * Create new DB if one doesn't exist
71
+ * Upgrade outdated DB
121
72
  *
122
- * If this returns `true`, we need to treat indexes containing a single field
123
- * as scalars.
73
+ * Called by `StorageAdapterBase.setUp()`
124
74
  *
125
- * See PR description for reference:
126
- * https://github.com/aws-amplify/amplify-js/pull/10527
75
+ * @returns IDB Database instance
127
76
  */
128
- private async setSafariCompatabilityMode() {
129
- this.safariCompatabilityMode = await isSafariCompatabilityMode();
77
+ protected async initDb(): Promise<idb.IDBPDatabase> {
78
+ return await idb.openDB(this.dbName, DB_VERSION, {
79
+ upgrade: async (db, oldVersion, newVersion, txn) => {
80
+ // create new database
81
+ if (oldVersion === 0) {
82
+ Object.keys(this.schema.namespaces).forEach(namespaceName => {
83
+ const namespace = this.schema.namespaces[namespaceName];
84
+
85
+ Object.keys(namespace.models).forEach(modelName => {
86
+ const storeName = getStorename(namespaceName, modelName);
87
+ this.createObjectStoreForModel(
88
+ db,
89
+ namespaceName,
90
+ storeName,
91
+ modelName
92
+ );
93
+ });
94
+ });
130
95
 
131
- if (this.safariCompatabilityMode === true) {
132
- logger.debug('IndexedDB Adapter is running in Safari Compatability Mode');
133
- }
134
- }
96
+ return;
97
+ }
135
98
 
136
- private getNamespaceAndModelFromStorename(storeName: string) {
137
- const [namespaceName, ...modelNameArr] = storeName.split('_');
138
- return {
139
- namespaceName,
140
- modelName: modelNameArr.join('_'),
141
- };
142
- }
99
+ // migrate existing database to latest schema
100
+ if ((oldVersion === 1 || oldVersion === 2) && newVersion === 3) {
101
+ try {
102
+ for (const storeName of txn.objectStoreNames) {
103
+ const origStore = txn.objectStore(storeName);
143
104
 
144
- async setUp(
145
- theSchema: InternalSchema,
146
- namespaceResolver: NamespaceResolver,
147
- modelInstanceCreator: ModelInstanceCreator,
148
- getModelConstructorByModelName: (
149
- namsespaceName: NAMESPACES,
150
- modelName: string
151
- ) => PersistentModelConstructor<any>,
152
- sessionId?: string
153
- ) {
154
- await this.checkPrivate();
155
- await this.setSafariCompatabilityMode();
105
+ // rename original store
106
+ const tmpName = `tmp_${storeName}`;
107
+ origStore.name = tmpName;
156
108
 
157
- if (!this.initPromise) {
158
- this.initPromise = new Promise((res, rej) => {
159
- this.resolve = res;
160
- this.reject = rej;
161
- });
162
- } else {
163
- await this.initPromise;
164
- }
165
- if (sessionId) {
166
- this.dbName = `${DB_NAME}-${sessionId}`;
167
- }
168
- this.schema = theSchema;
169
- this.namespaceResolver = namespaceResolver;
170
- this.modelInstanceCreator = modelInstanceCreator;
171
- this.getModelConstructorByModelName = getModelConstructorByModelName;
172
-
173
- try {
174
- if (!this.db) {
175
- const VERSION = 3;
176
- this.db = await idb.openDB(this.dbName, VERSION, {
177
- upgrade: async (db, oldVersion, newVersion, txn) => {
178
- if (oldVersion === 0) {
179
- Object.keys(theSchema.namespaces).forEach(namespaceName => {
180
- const namespace = theSchema.namespaces[namespaceName];
181
-
182
- Object.keys(namespace.models).forEach(modelName => {
183
- const storeName = getStorename(namespaceName, modelName);
184
- this.createObjectStoreForModel(
185
- db,
186
- namespaceName,
187
- storeName,
188
- modelName
189
- );
190
- });
191
- });
109
+ const { namespaceName, modelName } =
110
+ this.getNamespaceAndModelFromStorename(storeName);
192
111
 
193
- return;
194
- }
112
+ const modelInCurrentSchema =
113
+ modelName in this.schema.namespaces[namespaceName].models;
114
+
115
+ if (!modelInCurrentSchema) {
116
+ // delete original
117
+ db.deleteObjectStore(tmpName);
118
+ continue;
119
+ }
195
120
 
196
- if ((oldVersion === 1 || oldVersion === 2) && newVersion === 3) {
197
- try {
198
- for (const storeName of txn.objectStoreNames) {
199
- const origStore = txn.objectStore(storeName);
121
+ const newStore = this.createObjectStoreForModel(
122
+ db,
123
+ namespaceName,
124
+ storeName,
125
+ modelName
126
+ );
200
127
 
201
- // rename original store
202
- const tmpName = `tmp_${storeName}`;
203
- origStore.name = tmpName;
128
+ let cursor = await origStore.openCursor();
129
+ let count = 0;
204
130
 
205
- const { namespaceName, modelName } =
206
- this.getNamespaceAndModelFromStorename(storeName);
131
+ // Copy data from original to new
132
+ while (cursor && cursor.value) {
133
+ // we don't pass key, since they are all new entries in the new store
134
+ await newStore.put(cursor.value);
207
135
 
208
- const modelInCurrentSchema =
209
- modelName in this.schema.namespaces[namespaceName].models;
136
+ cursor = await cursor.continue();
137
+ count++;
138
+ }
210
139
 
211
- if (!modelInCurrentSchema) {
212
- // delete original
213
- db.deleteObjectStore(tmpName);
214
- continue;
215
- }
140
+ // delete original
141
+ db.deleteObjectStore(tmpName);
216
142
 
217
- const newStore = this.createObjectStoreForModel(
143
+ logger.debug(`${count} ${storeName} records migrated`);
144
+ }
145
+
146
+ // add new models created after IndexedDB, but before migration
147
+ // this case may happen when a user has not opened an app for
148
+ // some time and a new model is added during that time
149
+ Object.keys(this.schema.namespaces).forEach(namespaceName => {
150
+ const namespace = this.schema.namespaces[namespaceName];
151
+ const objectStoreNames = new Set(txn.objectStoreNames);
152
+
153
+ Object.keys(namespace.models)
154
+ .map(modelName => {
155
+ return [modelName, getStorename(namespaceName, modelName)];
156
+ })
157
+ .filter(([, storeName]) => !objectStoreNames.has(storeName))
158
+ .forEach(([modelName, storeName]) => {
159
+ this.createObjectStoreForModel(
218
160
  db,
219
161
  namespaceName,
220
162
  storeName,
221
163
  modelName
222
164
  );
223
-
224
- let cursor = await origStore.openCursor();
225
- let count = 0;
226
-
227
- // Copy data from original to new
228
- while (cursor && cursor.value) {
229
- // we don't pass key, since they are all new entries in the new store
230
- await newStore.put(cursor.value);
231
-
232
- cursor = await cursor.continue();
233
- count++;
234
- }
235
-
236
- // delete original
237
- db.deleteObjectStore(tmpName);
238
-
239
- logger.debug(`${count} ${storeName} records migrated`);
240
- }
241
-
242
- // add new models created after IndexedDB, but before migration
243
- // this case may happen when a user has not opened an app for
244
- // some time and a new model is added during that time
245
- Object.keys(theSchema.namespaces).forEach(namespaceName => {
246
- const namespace = theSchema.namespaces[namespaceName];
247
- const objectStoreNames = new Set(txn.objectStoreNames);
248
-
249
- Object.keys(namespace.models)
250
- .map(modelName => {
251
- return [
252
- modelName,
253
- getStorename(namespaceName, modelName),
254
- ];
255
- })
256
- .filter(([, storeName]) => !objectStoreNames.has(storeName))
257
- .forEach(([modelName, storeName]) => {
258
- this.createObjectStoreForModel(
259
- db,
260
- namespaceName,
261
- storeName,
262
- modelName
263
- );
264
- });
265
165
  });
266
- } catch (error) {
267
- logger.error('Error migrating IndexedDB data', error);
268
- txn.abort();
269
- throw error;
270
- }
271
-
272
- return;
273
- }
274
- },
275
- });
166
+ });
167
+ } catch (error) {
168
+ logger.error('Error migrating IndexedDB data', error);
169
+ txn.abort();
170
+ throw error;
171
+ }
276
172
 
277
- this.resolve();
278
- }
279
- } catch (error) {
280
- this.reject(error);
281
- }
173
+ return;
174
+ }
175
+ },
176
+ });
282
177
  }
283
178
 
284
- private async _get<T>(
179
+ protected async _get<T>(
285
180
  storeOrStoreName: idb.IDBPObjectStore | string,
286
181
  keyArr: string[]
287
182
  ): Promise<T> {
@@ -297,7 +192,17 @@ class IndexedDBAdapter implements Adapter {
297
192
 
298
193
  const result = await index.get(this.canonicalKeyPath(keyArr));
299
194
 
300
- return result;
195
+ return <T>result;
196
+ }
197
+
198
+ async clear(): Promise<void> {
199
+ await this.checkPrivate();
200
+
201
+ this.db?.close();
202
+ await idb.deleteDB(this.dbName);
203
+
204
+ this.db = undefined!;
205
+ this.initPromise = undefined!;
301
206
  }
302
207
 
303
208
  async save<T extends PersistentModel>(
@@ -305,77 +210,30 @@ class IndexedDBAdapter implements Adapter {
305
210
  condition?: ModelPredicate<T>
306
211
  ): Promise<[T, OpType.INSERT | OpType.UPDATE][]> {
307
212
  await this.checkPrivate();
308
- const modelConstructor = Object.getPrototypeOf(model)
309
- .constructor as PersistentModelConstructor<T>;
310
- const storeName = this.getStorenameForModel(modelConstructor);
311
- const namespaceName = this.namespaceResolver(modelConstructor);
312
213
 
313
- const connectedModels = traverseModel(
314
- modelConstructor.name,
315
- model,
316
- this.schema.namespaces[namespaceName],
317
- this.modelInstanceCreator,
318
- this.getModelConstructorByModelName!
319
- );
320
-
321
- const set = new Set<string>();
322
- const connectionStoreNames = Object.values(connectedModels).map(
323
- ({ modelName, item, instance }) => {
324
- const storeName = getStorename(namespaceName, modelName);
325
- set.add(storeName);
326
- const keys = getIndexKeys(
327
- this.schema.namespaces[namespaceName],
328
- modelName
329
- );
330
- return { storeName, item, instance, keys };
331
- }
332
- );
214
+ const { storeName, set, connectionStoreNames, modelKeyValues } =
215
+ this.saveMetadata(model);
333
216
 
334
217
  const tx = this.db.transaction(
335
218
  [storeName, ...Array.from(set.values())],
336
219
  'readwrite'
337
220
  );
338
- const store = tx.objectStore(storeName);
339
-
340
- const keyValues = this.getIndexKeyValuesFromModel(model);
341
-
342
- const fromDB = await this._get(store, keyValues);
343
221
 
344
- if (condition && fromDB) {
345
- const predicates = ModelPredicateCreator.getPredicates(condition);
346
- const { predicates: predicateObjs, type } = predicates || {};
347
-
348
- const isValid = validatePredicate(
349
- fromDB as any,
350
- type as any,
351
- predicateObjs as any
352
- );
353
-
354
- if (!isValid) {
355
- const msg = 'Conditional update failed';
356
- logger.error(msg, { model: fromDB, condition: predicateObjs });
222
+ const store = tx.objectStore(storeName);
223
+ const fromDB = await this._get(store, modelKeyValues);
357
224
 
358
- throw new Error(msg);
359
- }
360
- }
225
+ this.validateSaveCondition(condition, fromDB);
361
226
 
362
227
  const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
363
228
  for await (const resItem of connectionStoreNames) {
364
229
  const { storeName, item, instance, keys } = resItem;
365
230
  const store = tx.objectStore(storeName);
366
231
 
367
- const itemKeyValues = keys.map(key => {
368
- const value = item[key];
369
- return value;
370
- });
232
+ const itemKeyValues: string[] = keys.map(key => item[key]);
371
233
 
372
234
  const fromDB = <T>await this._get(store, itemKeyValues);
373
- const opType: OpType =
374
- fromDB === undefined ? OpType.INSERT : OpType.UPDATE;
235
+ const opType: OpType = fromDB ? OpType.UPDATE : OpType.INSERT;
375
236
 
376
- const modelKeyValues = this.getIndexKeyValuesFromModel(model);
377
-
378
- // Even if the parent is an INSERT, the child might not be, so we need to get its key
379
237
  if (
380
238
  keysEqual(itemKeyValues, modelKeyValues) ||
381
239
  opType === OpType.INSERT
@@ -387,62 +245,38 @@ class IndexedDBAdapter implements Adapter {
387
245
  result.push([instance, opType]);
388
246
  }
389
247
  }
390
-
391
248
  await tx.done;
392
249
 
393
250
  return result;
394
251
  }
395
252
 
396
- private async load<T>(
397
- namespaceName: NAMESPACES,
398
- srcModelName: string,
399
- records: T[]
400
- ): Promise<T[]> {
401
- const namespace = this.schema.namespaces[namespaceName];
402
- const relations = namespace.relationships![srcModelName].relationTypes;
403
- const connectionStoreNames = relations.map(({ modelName }) => {
404
- return getStorename(namespaceName, modelName);
405
- });
406
- const modelConstructor = this.getModelConstructorByModelName!(
407
- namespaceName,
408
- srcModelName
409
- );
410
-
411
- if (connectionStoreNames.length === 0) {
412
- return records.map(record =>
413
- this.modelInstanceCreator(modelConstructor, record)
414
- );
415
- }
416
-
417
- return records.map(record =>
418
- this.modelInstanceCreator(modelConstructor, record)
419
- );
420
- }
421
-
422
253
  async query<T extends PersistentModel>(
423
254
  modelConstructor: PersistentModelConstructor<T>,
424
255
  predicate?: ModelPredicate<T>,
425
256
  pagination?: PaginationInput<T>
426
257
  ): Promise<T[]> {
427
258
  await this.checkPrivate();
428
- const storeName = this.getStorenameForModel(modelConstructor);
429
- const namespaceName = this.namespaceResolver(
430
- modelConstructor
431
- ) as NAMESPACES;
432
-
433
- const predicates =
434
- predicate && ModelPredicateCreator.getPredicates(predicate);
435
- const keyPath = getIndexKeys(
436
- this.schema.namespaces[namespaceName],
437
- modelConstructor.name
438
- );
439
- const queryByKey =
440
- predicates && this.keyValueFromPredicate(predicates, keyPath);
441
-
442
- const hasSort = pagination && pagination.sort;
443
- const hasPagination = pagination && pagination.limit;
259
+ const {
260
+ storeName,
261
+ namespaceName,
262
+ queryByKey,
263
+ predicates,
264
+ hasSort,
265
+ hasPagination,
266
+ } = this.queryMetadata(modelConstructor, predicate, pagination);
444
267
 
445
268
  const records: T[] = (await (async () => {
269
+ //
270
+ // NOTE: @svidgen explored removing this and letting query() take care of automatic
271
+ // index leveraging. This would eliminate some amount of very similar code.
272
+ // But, getAll is slightly slower than get()
273
+ //
274
+ // On Chrome:
275
+ // ~700ms vs ~1175ms per 10k reads.
276
+ //
277
+ // You can (and should) check my work here:
278
+ // https://gist.github.com/svidgen/74e55d573b19c3e5432b1b5bdf0f4d96
279
+ //
446
280
  if (queryByKey) {
447
281
  const record = await this.getByKey(storeName, queryByKey);
448
282
  return record ? [record] : [];
@@ -468,40 +302,194 @@ class IndexedDBAdapter implements Adapter {
468
302
  return await this.load(namespaceName, modelConstructor.name, records);
469
303
  }
470
304
 
471
- private async getByKey<T extends PersistentModel>(
472
- storeName: string,
473
- keyValue: string[]
474
- ): Promise<T> {
475
- return <T>await this._get(storeName, keyValue);
305
+ async queryOne<T extends PersistentModel>(
306
+ modelConstructor: PersistentModelConstructor<T>,
307
+ firstOrLast: QueryOne = QueryOne.FIRST
308
+ ): Promise<T | undefined> {
309
+ await this.checkPrivate();
310
+ const storeName = this.getStorenameForModel(modelConstructor);
311
+
312
+ const cursor = await this.db
313
+ .transaction([storeName], 'readonly')
314
+ .objectStore(storeName)
315
+ .openCursor(undefined, firstOrLast === QueryOne.FIRST ? 'next' : 'prev');
316
+
317
+ const result = cursor ? <T>cursor.value : undefined;
318
+
319
+ return result && this.modelInstanceCreator(modelConstructor, result);
476
320
  }
477
321
 
478
- private async getAll<T extends PersistentModel>(
479
- storeName: string
480
- ): Promise<T[]> {
481
- return await this.db.getAll(storeName);
322
+ async batchSave<T extends PersistentModel>(
323
+ modelConstructor: PersistentModelConstructor<any>,
324
+ items: ModelInstanceMetadata[]
325
+ ): Promise<[T, OpType][]> {
326
+ await this.checkPrivate();
327
+
328
+ if (items.length === 0) {
329
+ return [];
330
+ }
331
+
332
+ const modelName = modelConstructor.name;
333
+ const namespaceName = this.namespaceResolver(modelConstructor);
334
+ const storeName = this.getStorenameForModel(modelConstructor);
335
+ const result: [T, OpType][] = [];
336
+
337
+ const txn = this.db.transaction(storeName, 'readwrite');
338
+ const store = txn.store;
339
+
340
+ for (const item of items) {
341
+ const model = this.modelInstanceCreator(modelConstructor, item);
342
+
343
+ const connectedModels = traverseModel(
344
+ modelName,
345
+ model,
346
+ this.schema.namespaces[namespaceName],
347
+ this.modelInstanceCreator,
348
+ this.getModelConstructorByModelName!
349
+ );
350
+
351
+ const keyValues = this.getIndexKeyValuesFromModel(model);
352
+ const { _deleted } = item;
353
+
354
+ const index = store.index('byPk');
355
+
356
+ const key = await index.getKey(this.canonicalKeyPath(keyValues));
357
+
358
+ if (!_deleted) {
359
+ const { instance } = connectedModels.find(({ instance }) => {
360
+ const instanceKeyValues = this.getIndexKeyValuesFromModel(instance);
361
+ return keysEqual(instanceKeyValues, keyValues);
362
+ })!;
363
+
364
+ result.push([
365
+ <T>(<unknown>instance),
366
+ key ? OpType.UPDATE : OpType.INSERT,
367
+ ]);
368
+ await store.put(instance, key);
369
+ } else {
370
+ result.push([<T>(<unknown>item), OpType.DELETE]);
371
+
372
+ if (key) {
373
+ await store.delete(key);
374
+ }
375
+ }
376
+ }
377
+
378
+ await txn.done;
379
+
380
+ return result;
482
381
  }
483
382
 
484
- private keyValueFromPredicate<T extends PersistentModel>(
485
- predicates: PredicatesGroup<T>,
486
- keyPath: string[]
487
- ): string[] | undefined {
488
- const { predicates: predicateObjs } = predicates;
383
+ protected async deleteItem<T extends PersistentModel>(
384
+ deleteQueue: {
385
+ storeName: string;
386
+ items: T[] | IDBValidKey[];
387
+ }[]
388
+ ) {
389
+ const connectionStoreNames = deleteQueue!.map(({ storeName }) => {
390
+ return storeName;
391
+ });
392
+
393
+ const tx = this.db.transaction([...connectionStoreNames], 'readwrite');
394
+ for await (const deleteItem of deleteQueue!) {
395
+ const { storeName, items } = deleteItem;
396
+ const store = tx.objectStore(storeName);
489
397
 
490
- if (predicateObjs.length !== keyPath.length) {
491
- return;
398
+ for await (const item of items) {
399
+ if (item) {
400
+ let key: IDBValidKey | undefined;
401
+
402
+ if (typeof item === 'object') {
403
+ const keyValues = this.getIndexKeyValuesFromModel(item as T);
404
+ key = await store
405
+ .index('byPk')
406
+ .getKey(this.canonicalKeyPath(keyValues));
407
+ } else {
408
+ const itemKey = item.toString();
409
+ key = await store.index('byPk').getKey(itemKey);
410
+ }
411
+
412
+ if (key !== undefined) {
413
+ await store.delete(key);
414
+ }
415
+ }
416
+ }
492
417
  }
418
+ }
493
419
 
494
- const keyValues = [] as any[];
420
+ //#region platform-specific helper methods
495
421
 
496
- for (const key of keyPath) {
497
- const predicateObj = predicateObjs.find(
498
- p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
499
- ) as PredicateObject<T>;
422
+ private async checkPrivate() {
423
+ const isPrivate = await isPrivateMode().then(isPrivate => {
424
+ return isPrivate;
425
+ });
426
+ if (isPrivate) {
427
+ logger.error("IndexedDB not supported in this browser's private mode");
428
+ return Promise.reject(
429
+ "IndexedDB not supported in this browser's private mode"
430
+ );
431
+ } else {
432
+ return Promise.resolve();
433
+ }
434
+ }
500
435
 
501
- predicateObj && keyValues.push(predicateObj.operand);
436
+ /**
437
+ * Whether the browser's implementation of IndexedDB is coercing single-field
438
+ * indexes to a scalar key.
439
+ *
440
+ * If this returns `true`, we need to treat indexes containing a single field
441
+ * as scalars.
442
+ *
443
+ * See PR description for reference:
444
+ * https://github.com/aws-amplify/amplify-js/pull/10527
445
+ */
446
+ private async setSafariCompatabilityMode() {
447
+ this.safariCompatabilityMode = await isSafariCompatabilityMode();
448
+
449
+ if (this.safariCompatabilityMode === true) {
450
+ logger.debug('IndexedDB Adapter is running in Safari Compatability Mode');
502
451
  }
452
+ }
453
+
454
+ private getNamespaceAndModelFromStorename(storeName: string) {
455
+ const [namespaceName, ...modelNameArr] = storeName.split('_');
456
+ return {
457
+ namespaceName,
458
+ modelName: modelNameArr.join('_'),
459
+ };
460
+ }
461
+
462
+ private createObjectStoreForModel(
463
+ db: idb.IDBPDatabase,
464
+ namespaceName: string,
465
+ storeName: string,
466
+ modelName: string
467
+ ): idb.IDBPObjectStore {
468
+ const store = db.createObjectStore(storeName, {
469
+ autoIncrement: true,
470
+ });
471
+
472
+ const { indexes } =
473
+ this.schema.namespaces[namespaceName].relationships![modelName];
474
+
475
+ indexes.forEach(([idxName, keyPath, options]) => {
476
+ store.createIndex(idxName, keyPath, options);
477
+ });
478
+
479
+ return store;
480
+ }
481
+
482
+ private async getByKey<T extends PersistentModel>(
483
+ storeName: string,
484
+ keyValue: string[]
485
+ ): Promise<T> {
486
+ return <T>await this._get(storeName, keyValue);
487
+ }
503
488
 
504
- return keyValues.length === keyPath.length ? keyValues : undefined;
489
+ private async getAll<T extends PersistentModel>(
490
+ storeName: string
491
+ ): Promise<T[]> {
492
+ return await this.db.getAll(storeName);
505
493
  }
506
494
 
507
495
  /**
@@ -530,12 +518,12 @@ class IndexedDBAdapter implements Adapter {
530
518
  for (const name of store.indexNames) {
531
519
  const idx = store.index(name);
532
520
  const keypath = Array.isArray(idx.keyPath) ? idx.keyPath : [idx.keyPath];
533
- const matchingPredicateValues: string[] = [];
521
+ const matchingPredicateValues: (string | number)[] = [];
534
522
 
535
523
  for (const field of keypath) {
536
524
  const p = predicateIndex.get(field);
537
- if (p) {
538
- matchingPredicateValues.push(String(p.operand));
525
+ if (p && p.operand !== null && p.operand !== undefined) {
526
+ matchingPredicateValues.push(p.operand);
539
527
  } else {
540
528
  break;
541
529
  }
@@ -769,447 +757,6 @@ class IndexedDBAdapter implements Adapter {
769
757
  return result;
770
758
  }
771
759
 
772
- async queryOne<T extends PersistentModel>(
773
- modelConstructor: PersistentModelConstructor<T>,
774
- firstOrLast: QueryOne = QueryOne.FIRST
775
- ): Promise<T | undefined> {
776
- await this.checkPrivate();
777
- const storeName = this.getStorenameForModel(modelConstructor);
778
-
779
- const cursor = await this.db
780
- .transaction([storeName], 'readonly')
781
- .objectStore(storeName)
782
- .openCursor(undefined, firstOrLast === QueryOne.FIRST ? 'next' : 'prev');
783
-
784
- const result = cursor ? <T>cursor.value : undefined;
785
-
786
- return result && this.modelInstanceCreator(modelConstructor, result);
787
- }
788
-
789
- async delete<T extends PersistentModel>(
790
- modelOrModelConstructor: T | PersistentModelConstructor<T>,
791
- condition?: ModelPredicate<T>
792
- ): Promise<[T[], T[]]> {
793
- await this.checkPrivate();
794
- const deleteQueue: { storeName: string; items: T[] }[] = [];
795
-
796
- if (isModelConstructor(modelOrModelConstructor)) {
797
- const modelConstructor =
798
- modelOrModelConstructor as PersistentModelConstructor<T>;
799
- const nameSpace = this.namespaceResolver(modelConstructor) as NAMESPACES;
800
-
801
- const storeName = this.getStorenameForModel(modelConstructor);
802
-
803
- const models = await this.query(modelConstructor, condition);
804
- const relations =
805
- this.schema.namespaces![nameSpace].relationships![modelConstructor.name]
806
- .relationTypes;
807
-
808
- if (condition !== undefined) {
809
- await this.deleteTraverse(
810
- relations,
811
- models,
812
- modelConstructor.name,
813
- nameSpace,
814
- deleteQueue
815
- );
816
-
817
- await this.deleteItem(deleteQueue);
818
-
819
- const deletedModels = deleteQueue.reduce(
820
- (acc, { items }) => acc.concat(items),
821
- <T[]>[]
822
- );
823
-
824
- return [models, deletedModels];
825
- } else {
826
- await this.deleteTraverse(
827
- relations,
828
- models,
829
- modelConstructor.name,
830
- nameSpace,
831
- deleteQueue
832
- );
833
-
834
- // Delete all
835
- await this.db
836
- .transaction([storeName], 'readwrite')
837
- .objectStore(storeName)
838
- .clear();
839
-
840
- const deletedModels = deleteQueue.reduce(
841
- (acc, { items }) => acc.concat(items),
842
- <T[]>[]
843
- );
844
-
845
- return [models, deletedModels];
846
- }
847
- } else {
848
- const model = modelOrModelConstructor as T;
849
-
850
- const modelConstructor = Object.getPrototypeOf(model)
851
- .constructor as PersistentModelConstructor<T>;
852
- const namespaceName = this.namespaceResolver(
853
- modelConstructor
854
- ) as NAMESPACES;
855
-
856
- const storeName = this.getStorenameForModel(modelConstructor);
857
-
858
- if (condition) {
859
- const tx = this.db.transaction([storeName], 'readwrite');
860
- const store = tx.objectStore(storeName);
861
- const keyValues = this.getIndexKeyValuesFromModel(model);
862
-
863
- const fromDB = await this._get(store, keyValues);
864
-
865
- if (fromDB === undefined) {
866
- const msg = 'Model instance not found in storage';
867
- logger.warn(msg, { model });
868
-
869
- return [[model], []];
870
- }
871
-
872
- const predicates = ModelPredicateCreator.getPredicates(condition);
873
- const { predicates: predicateObjs, type } =
874
- predicates as PredicatesGroup<T>;
875
-
876
- const isValid = validatePredicate(fromDB as T, type, predicateObjs);
877
-
878
- if (!isValid) {
879
- const msg = 'Conditional update failed';
880
- logger.error(msg, { model: fromDB, condition: predicateObjs });
881
-
882
- throw new Error(msg);
883
- }
884
- await tx.done;
885
-
886
- const relations =
887
- this.schema.namespaces[namespaceName].relationships![
888
- modelConstructor.name
889
- ].relationTypes;
890
-
891
- await this.deleteTraverse(
892
- relations,
893
- [model],
894
- modelConstructor.name,
895
- namespaceName,
896
- deleteQueue
897
- );
898
- } else {
899
- const relations =
900
- this.schema.namespaces[namespaceName].relationships![
901
- modelConstructor.name
902
- ].relationTypes;
903
-
904
- await this.deleteTraverse(
905
- relations,
906
- [model],
907
- modelConstructor.name,
908
- namespaceName,
909
- deleteQueue
910
- );
911
- }
912
-
913
- await this.deleteItem(deleteQueue);
914
-
915
- const deletedModels = deleteQueue.reduce(
916
- (acc, { items }) => acc.concat(items),
917
- <T[]>[]
918
- );
919
-
920
- return [[model], deletedModels];
921
- }
922
- }
923
-
924
- private async deleteItem<T extends PersistentModel>(
925
- deleteQueue?: {
926
- storeName: string;
927
- items: T[] | IDBValidKey[];
928
- }[]
929
- ) {
930
- const connectionStoreNames = deleteQueue!.map(({ storeName }) => {
931
- return storeName;
932
- });
933
-
934
- const tx = this.db.transaction([...connectionStoreNames], 'readwrite');
935
- for await (const deleteItem of deleteQueue!) {
936
- const { storeName, items } = deleteItem;
937
- const store = tx.objectStore(storeName);
938
-
939
- for await (const item of items) {
940
- if (item) {
941
- let key: IDBValidKey | undefined;
942
-
943
- if (typeof item === 'object') {
944
- const keyValues = this.getIndexKeyValuesFromModel(item as T);
945
- key = await store
946
- .index('byPk')
947
- .getKey(this.canonicalKeyPath(keyValues));
948
- } else {
949
- const itemKey = item.toString();
950
- key = await store.index('byPk').getKey(itemKey);
951
- }
952
-
953
- if (key !== undefined) {
954
- await store.delete(key);
955
- }
956
- }
957
- }
958
- }
959
- }
960
-
961
- private async deleteTraverse<T extends PersistentModel>(
962
- relations: RelationType[],
963
- models: T[],
964
- srcModel: string,
965
- nameSpace: NAMESPACES,
966
- deleteQueue: { storeName: string; items: T[] }[]
967
- ): Promise<void> {
968
- for await (const rel of relations) {
969
- const {
970
- relationType,
971
- modelName,
972
- targetName,
973
- targetNames,
974
- associatedWith,
975
- } = rel;
976
-
977
- const storeName = getStorename(nameSpace, modelName);
978
-
979
- switch (relationType) {
980
- case 'HAS_ONE':
981
- for await (const model of models) {
982
- const hasOneIndex = 'byPk';
983
-
984
- if (targetNames?.length) {
985
- // CPK codegen
986
- const values = targetNames
987
- .filter(targetName => model[targetName] ?? false)
988
- .map(targetName => model[targetName]);
989
-
990
- if (values.length === 0) break;
991
-
992
- const recordToDelete = <T>(
993
- await this.db
994
- .transaction(storeName, 'readwrite')
995
- .objectStore(storeName)
996
- .index(hasOneIndex)
997
- .get(this.canonicalKeyPath(values))
998
- );
999
-
1000
- await this.deleteTraverse(
1001
- this.schema.namespaces[nameSpace].relationships![modelName]
1002
- .relationTypes,
1003
- recordToDelete ? [recordToDelete] : [],
1004
- modelName,
1005
- nameSpace,
1006
- deleteQueue
1007
- );
1008
- break;
1009
- } else {
1010
- // PRE-CPK codegen
1011
- let index;
1012
- let values: string[];
1013
-
1014
- if (targetName && targetName in model) {
1015
- index = hasOneIndex;
1016
- const value = model[targetName];
1017
- if (value === null) break;
1018
- values = [value];
1019
- } else {
1020
- // backwards compatability for older versions of codegen that did not emit targetName for HAS_ONE relations
1021
- // TODO: can we deprecate this? it's been ~2 years since codegen started including targetName for HAS_ONE
1022
- // If we deprecate, we'll need to re-gen the MIPR in __tests__/schema.ts > newSchema
1023
- // otherwise some unit tests will fail
1024
- index = getIndex(
1025
- this.schema.namespaces[nameSpace].relationships![modelName]
1026
- .relationTypes,
1027
- srcModel
1028
- );
1029
- values = this.getIndexKeyValuesFromModel(model);
1030
- }
1031
-
1032
- if (!values || !index) break;
1033
-
1034
- const recordToDelete = <T>(
1035
- await this.db
1036
- .transaction(storeName, 'readwrite')
1037
- .objectStore(storeName)
1038
- .index(index)
1039
- .get(this.canonicalKeyPath(values))
1040
- );
1041
-
1042
- // instantiate models before passing to deleteTraverse
1043
- // necessary for extracting PK values via getIndexKeyValuesFromModel
1044
- const modelsToDelete = recordToDelete
1045
- ? await this.load(nameSpace, modelName, [recordToDelete])
1046
- : [];
1047
-
1048
- await this.deleteTraverse(
1049
- this.schema.namespaces[nameSpace].relationships![modelName]
1050
- .relationTypes,
1051
- modelsToDelete,
1052
- modelName,
1053
- nameSpace,
1054
- deleteQueue
1055
- );
1056
- }
1057
- }
1058
- break;
1059
- case 'HAS_MANY':
1060
- for await (const model of models) {
1061
- const index =
1062
- // explicit bi-directional @hasMany and @manyToMany
1063
- getIndex(
1064
- this.schema.namespaces[nameSpace].relationships![modelName]
1065
- .relationTypes,
1066
- srcModel
1067
- ) ||
1068
- // uni and/or implicit @hasMany
1069
- getIndexFromAssociation(
1070
- this.schema.namespaces[nameSpace].relationships![modelName]
1071
- .indexes,
1072
- associatedWith!
1073
- );
1074
- const keyValues = this.getIndexKeyValuesFromModel(model);
1075
-
1076
- const childRecords = await this.db
1077
- .transaction(storeName, 'readwrite')
1078
- .objectStore(storeName)
1079
- .index(index as string)
1080
- .getAll(this.canonicalKeyPath(keyValues));
1081
-
1082
- // instantiate models before passing to deleteTraverse
1083
- // necessary for extracting PK values via getIndexKeyValuesFromModel
1084
- const childModels = await this.load(
1085
- nameSpace,
1086
- modelName,
1087
- childRecords
1088
- );
1089
-
1090
- await this.deleteTraverse(
1091
- this.schema.namespaces[nameSpace].relationships![modelName]
1092
- .relationTypes,
1093
- childModels,
1094
- modelName,
1095
- nameSpace,
1096
- deleteQueue
1097
- );
1098
- }
1099
- break;
1100
- case 'BELONGS_TO':
1101
- // Intentionally blank
1102
- break;
1103
- default:
1104
- throw new Error(`Invalid relation type ${relationType}`);
1105
- break;
1106
- }
1107
- }
1108
-
1109
- deleteQueue.push({
1110
- storeName: getStorename(nameSpace, srcModel),
1111
- items: models.map(record =>
1112
- this.modelInstanceCreator(
1113
- this.getModelConstructorByModelName!(nameSpace, srcModel),
1114
- record
1115
- )
1116
- ),
1117
- });
1118
- }
1119
-
1120
- async clear(): Promise<void> {
1121
- await this.checkPrivate();
1122
-
1123
- this.db?.close();
1124
-
1125
- await idb.deleteDB(this.dbName);
1126
-
1127
- this.db = undefined!;
1128
- this.initPromise = undefined!;
1129
- }
1130
-
1131
- async batchSave<T extends PersistentModel>(
1132
- modelConstructor: PersistentModelConstructor<any>,
1133
- items: ModelInstanceMetadata[]
1134
- ): Promise<[T, OpType][]> {
1135
- if (items.length === 0) {
1136
- return [];
1137
- }
1138
-
1139
- await this.checkPrivate();
1140
-
1141
- const result: [T, OpType][] = [];
1142
-
1143
- const storeName = this.getStorenameForModel(modelConstructor);
1144
-
1145
- const txn = this.db.transaction(storeName, 'readwrite');
1146
- const store = txn.store;
1147
-
1148
- for (const item of items) {
1149
- const namespaceName = this.namespaceResolver(modelConstructor);
1150
- const modelName = modelConstructor.name;
1151
- const model = this.modelInstanceCreator(modelConstructor, item);
1152
-
1153
- const connectedModels = traverseModel(
1154
- modelName,
1155
- model,
1156
- this.schema.namespaces[namespaceName],
1157
- this.modelInstanceCreator,
1158
- this.getModelConstructorByModelName!
1159
- );
1160
-
1161
- const keyValues = this.getIndexKeyValuesFromModel(model);
1162
- const { _deleted } = item;
1163
-
1164
- const index = store.index('byPk');
1165
-
1166
- const key = await index.getKey(this.canonicalKeyPath(keyValues));
1167
-
1168
- if (!_deleted) {
1169
- const { instance } = connectedModels.find(({ instance }) => {
1170
- const instanceKeyValues = this.getIndexKeyValuesFromModel(instance);
1171
- return keysEqual(instanceKeyValues, keyValues);
1172
- })!;
1173
-
1174
- result.push([
1175
- <T>(<unknown>instance),
1176
- key ? OpType.UPDATE : OpType.INSERT,
1177
- ]);
1178
- await store.put(instance, key);
1179
- } else {
1180
- result.push([<T>(<unknown>item), OpType.DELETE]);
1181
-
1182
- if (key) {
1183
- await store.delete(key);
1184
- }
1185
- }
1186
- }
1187
-
1188
- await txn.done;
1189
-
1190
- return result;
1191
- }
1192
-
1193
- private createObjectStoreForModel(
1194
- db: idb.IDBPDatabase,
1195
- namespaceName: string,
1196
- storeName: string,
1197
- modelName: string
1198
- ) {
1199
- const store = db.createObjectStore(storeName, {
1200
- autoIncrement: true,
1201
- });
1202
-
1203
- const { indexes } =
1204
- this.schema.namespaces[namespaceName].relationships![modelName];
1205
-
1206
- indexes.forEach(([idxName, keyPath, options]) => {
1207
- store.createIndex(idxName, keyPath, options);
1208
- });
1209
-
1210
- return store;
1211
- }
1212
-
1213
760
  /**
1214
761
  * Checks the given path against the browser's IndexedDB implementation for
1215
762
  * necessary compatibility transformations, applying those transforms if needed.
@@ -1218,12 +765,13 @@ class IndexedDBAdapter implements Adapter {
1218
765
  * @returns An array or string, depending on and given key,
1219
766
  * that is ensured to be compatible with the IndexedDB implementation's nuances.
1220
767
  */
1221
- private canonicalKeyPath = (keyArr: string[]) => {
768
+ private canonicalKeyPath = (keyArr: (string | number)[]) => {
1222
769
  if (this.safariCompatabilityMode) {
1223
770
  return keyArr.length > 1 ? keyArr : keyArr[0];
1224
771
  }
1225
772
  return keyArr;
1226
773
  };
774
+ //#endregion
1227
775
  }
1228
776
 
1229
777
  export default new IndexedDBAdapter();