@aws-amplify/datastore 3.12.6-next.20 → 3.12.6-next.32

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 (144) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/lib/authModeStrategies/multiAuthStrategy.js +13 -2
  3. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  4. package/lib/datastore/datastore.js +648 -344
  5. package/lib/datastore/datastore.js.map +1 -1
  6. package/lib/predicates/index.js +12 -2
  7. package/lib/predicates/index.js.map +1 -1
  8. package/lib/storage/adapter/AsyncStorageAdapter.js +354 -203
  9. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  10. package/lib/storage/adapter/AsyncStorageDatabase.js +65 -28
  11. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  12. package/lib/storage/adapter/IndexedDBAdapter.js +444 -271
  13. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  14. package/lib/storage/storage.js +93 -28
  15. package/lib/storage/storage.js.map +1 -1
  16. package/lib/sync/datastoreConnectivity.js +9 -0
  17. package/lib/sync/datastoreConnectivity.js.map +1 -1
  18. package/lib/sync/index.js +522 -397
  19. package/lib/sync/index.js.map +1 -1
  20. package/lib/sync/merger.js +13 -6
  21. package/lib/sync/merger.js.map +1 -1
  22. package/lib/sync/outbox.js +77 -71
  23. package/lib/sync/outbox.js.map +1 -1
  24. package/lib/sync/processors/mutation.js +269 -209
  25. package/lib/sync/processors/mutation.js.map +1 -1
  26. package/lib/sync/processors/subscription.js +213 -178
  27. package/lib/sync/processors/subscription.js.map +1 -1
  28. package/lib/sync/processors/sync.js +126 -121
  29. package/lib/sync/processors/sync.js.map +1 -1
  30. package/lib/sync/utils.js +43 -8
  31. package/lib/sync/utils.js.map +1 -1
  32. package/lib/types.js +10 -1
  33. package/lib/types.js.map +1 -1
  34. package/lib/util.js +419 -166
  35. package/lib/util.js.map +1 -1
  36. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +11 -0
  37. package/lib-esm/authModeStrategies/multiAuthStrategy.js +12 -1
  38. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  39. package/lib-esm/datastore/datastore.d.ts +107 -17
  40. package/lib-esm/datastore/datastore.js +648 -344
  41. package/lib-esm/datastore/datastore.js.map +1 -1
  42. package/lib-esm/index.d.ts +3 -19
  43. package/lib-esm/predicates/index.d.ts +3 -2
  44. package/lib-esm/predicates/index.js +13 -3
  45. package/lib-esm/predicates/index.js.map +1 -1
  46. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  47. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +355 -204
  48. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  49. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  50. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +66 -29
  51. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  52. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +26 -4
  53. package/lib-esm/storage/adapter/IndexedDBAdapter.js +445 -272
  54. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  55. package/lib-esm/storage/adapter/index.d.ts +1 -1
  56. package/lib-esm/storage/storage.d.ts +1 -1
  57. package/lib-esm/storage/storage.js +93 -28
  58. package/lib-esm/storage/storage.js.map +1 -1
  59. package/lib-esm/sync/datastoreConnectivity.d.ts +1 -0
  60. package/lib-esm/sync/datastoreConnectivity.js +10 -1
  61. package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
  62. package/lib-esm/sync/index.d.ts +31 -5
  63. package/lib-esm/sync/index.js +524 -399
  64. package/lib-esm/sync/index.js.map +1 -1
  65. package/lib-esm/sync/merger.d.ts +9 -3
  66. package/lib-esm/sync/merger.js +13 -6
  67. package/lib-esm/sync/merger.js.map +1 -1
  68. package/lib-esm/sync/outbox.d.ts +2 -2
  69. package/lib-esm/sync/outbox.js +78 -72
  70. package/lib-esm/sync/outbox.js.map +1 -1
  71. package/lib-esm/sync/processors/mutation.d.ts +2 -0
  72. package/lib-esm/sync/processors/mutation.js +270 -210
  73. package/lib-esm/sync/processors/mutation.js.map +1 -1
  74. package/lib-esm/sync/processors/subscription.d.ts +2 -0
  75. package/lib-esm/sync/processors/subscription.js +213 -178
  76. package/lib-esm/sync/processors/subscription.js.map +1 -1
  77. package/lib-esm/sync/processors/sync.d.ts +2 -1
  78. package/lib-esm/sync/processors/sync.js +126 -121
  79. package/lib-esm/sync/processors/sync.js.map +1 -1
  80. package/lib-esm/sync/utils.d.ts +3 -2
  81. package/lib-esm/sync/utils.js +45 -11
  82. package/lib-esm/sync/utils.js.map +1 -1
  83. package/lib-esm/types.d.ts +65 -26
  84. package/lib-esm/types.js +9 -2
  85. package/lib-esm/types.js.map +1 -1
  86. package/lib-esm/util.d.ts +67 -24
  87. package/lib-esm/util.js +419 -166
  88. package/lib-esm/util.js.map +1 -1
  89. package/package.json +13 -7
  90. package/src/authModeStrategies/multiAuthStrategy.ts +12 -1
  91. package/src/datastore/datastore.ts +798 -397
  92. package/src/predicates/index.ts +32 -10
  93. package/src/storage/adapter/AsyncStorageAdapter.ts +309 -93
  94. package/src/storage/adapter/AsyncStorageDatabase.ts +74 -26
  95. package/src/storage/adapter/IndexedDBAdapter.ts +358 -134
  96. package/src/storage/adapter/index.ts +1 -1
  97. package/src/storage/storage.ts +69 -22
  98. package/src/sync/datastoreConnectivity.ts +6 -0
  99. package/src/sync/index.ts +521 -412
  100. package/src/sync/merger.ts +20 -4
  101. package/src/sync/outbox.ts +22 -9
  102. package/src/sync/processors/mutation.ts +188 -150
  103. package/src/sync/processors/subscription.ts +289 -253
  104. package/src/sync/processors/sync.ts +151 -138
  105. package/src/sync/utils.ts +67 -12
  106. package/src/types.ts +182 -30
  107. package/src/util.ts +505 -176
  108. package/build.js +0 -5
  109. package/dist/aws-amplify-datastore.js +0 -83311
  110. package/dist/aws-amplify-datastore.js.map +0 -1
  111. package/dist/aws-amplify-datastore.min.js +0 -168
  112. package/dist/aws-amplify-datastore.min.js.map +0 -1
  113. package/index.js +0 -7
  114. package/lib/authModeStrategies/defaultAuthStrategy.d.ts +0 -2
  115. package/lib/authModeStrategies/index.d.ts +0 -2
  116. package/lib/authModeStrategies/multiAuthStrategy.d.ts +0 -2
  117. package/lib/datastore/datastore.d.ts +0 -66
  118. package/lib/index.d.ts +0 -31
  119. package/lib/predicates/index.d.ts +0 -15
  120. package/lib/predicates/sort.d.ts +0 -8
  121. package/lib/ssr/index.d.ts +0 -3
  122. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +0 -40
  123. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +0 -29
  124. package/lib/storage/adapter/InMemoryStore.d.ts +0 -11
  125. package/lib/storage/adapter/InMemoryStore.native.d.ts +0 -1
  126. package/lib/storage/adapter/IndexedDBAdapter.d.ts +0 -37
  127. package/lib/storage/adapter/getDefaultAdapter/index.d.ts +0 -3
  128. package/lib/storage/adapter/getDefaultAdapter/index.native.d.ts +0 -3
  129. package/lib/storage/adapter/index.d.ts +0 -9
  130. package/lib/storage/storage.d.ts +0 -49
  131. package/lib/sync/datastoreConnectivity.d.ts +0 -15
  132. package/lib/sync/datastoreReachability/index.d.ts +0 -3
  133. package/lib/sync/datastoreReachability/index.native.d.ts +0 -3
  134. package/lib/sync/index.d.ts +0 -63
  135. package/lib/sync/merger.d.ts +0 -11
  136. package/lib/sync/outbox.d.ts +0 -27
  137. package/lib/sync/processors/errorMaps.d.ts +0 -17
  138. package/lib/sync/processors/mutation.d.ts +0 -56
  139. package/lib/sync/processors/subscription.d.ts +0 -31
  140. package/lib/sync/processors/sync.d.ts +0 -27
  141. package/lib/sync/utils.d.ts +0 -41
  142. package/lib/types.d.ts +0 -462
  143. package/lib/util.d.ts +0 -113
  144. package/webpack.config.dev.js +0 -6
@@ -29,13 +29,17 @@ import {
29
29
  traverseModel,
30
30
  validatePredicate,
31
31
  sortCompareFunction,
32
+ keysEqual,
33
+ getStorename,
34
+ getIndexKeys,
35
+ extractPrimaryKeyValues,
36
+ isSafariCompatabilityMode,
32
37
  } from '../../util';
33
38
  import { Adapter } from './index';
34
39
 
35
40
  const logger = new Logger('DataStore');
36
41
 
37
42
  const DB_NAME = 'amplify-datastore';
38
-
39
43
  class IndexedDBAdapter implements Adapter {
40
44
  private schema: InternalSchema;
41
45
  private namespaceResolver: NamespaceResolver;
@@ -49,6 +53,32 @@ class IndexedDBAdapter implements Adapter {
49
53
  private resolve: (value?: any) => void;
50
54
  private reject: (value?: any) => void;
51
55
  private dbName: string = DB_NAME;
56
+ private safariCompatabilityMode: boolean = false;
57
+
58
+ private getStorenameForModel(
59
+ modelConstructor: PersistentModelConstructor<any>
60
+ ) {
61
+ const namespace = this.namespaceResolver(modelConstructor);
62
+ const { name: modelName } = modelConstructor;
63
+
64
+ return getStorename(namespace, modelName);
65
+ }
66
+
67
+ // Retrieves primary key values from a model
68
+ private getIndexKeyValuesFromModel<T extends PersistentModel>(
69
+ model: T
70
+ ): string[] {
71
+ const modelConstructor = Object.getPrototypeOf(model)
72
+ .constructor as PersistentModelConstructor<T>;
73
+ const namespaceName = this.namespaceResolver(modelConstructor);
74
+
75
+ const keys = getIndexKeys(
76
+ this.schema.namespaces[namespaceName],
77
+ modelConstructor.name
78
+ );
79
+
80
+ return extractPrimaryKeyValues(model, keys);
81
+ }
52
82
 
53
83
  private async checkPrivate() {
54
84
  const isPrivate = await isPrivateMode().then(isPrivate => {
@@ -64,19 +94,30 @@ class IndexedDBAdapter implements Adapter {
64
94
  }
65
95
  }
66
96
 
67
- private getStorenameForModel(
68
- modelConstructor: PersistentModelConstructor<any>
69
- ) {
70
- const namespace = this.namespaceResolver(modelConstructor);
71
- const { name: modelName } = modelConstructor;
72
-
73
- return this.getStorename(namespace, modelName);
97
+ /**
98
+ * Whether the browser's implementation of IndexedDB is coercing single-field
99
+ * indexes to a scalar key.
100
+ *
101
+ * If this returns `true`, we need to treat indexes containing a single field
102
+ * as scalars.
103
+ *
104
+ * See PR description for reference:
105
+ * https://github.com/aws-amplify/amplify-js/pull/10527
106
+ */
107
+ private async setSafariCompatabilityMode() {
108
+ this.safariCompatabilityMode = await isSafariCompatabilityMode();
109
+
110
+ if (this.safariCompatabilityMode === true) {
111
+ logger.debug('IndexedDB Adapter is running in Safari Compatability Mode');
112
+ }
74
113
  }
75
114
 
76
- private getStorename(namespace: string, modelName: string) {
77
- const storeName = `${namespace}_${modelName}`;
78
-
79
- return storeName;
115
+ private getNamespaceAndModelFromStorename(storeName: string) {
116
+ const [namespaceName, ...modelNameArr] = storeName.split('_');
117
+ return {
118
+ namespaceName,
119
+ modelName: modelNameArr.join('_'),
120
+ };
80
121
  }
81
122
 
82
123
  async setUp(
@@ -90,6 +131,8 @@ class IndexedDBAdapter implements Adapter {
90
131
  sessionId?: string
91
132
  ) {
92
133
  await this.checkPrivate();
134
+ await this.setSafariCompatabilityMode();
135
+
93
136
  if (!this.initPromise) {
94
137
  this.initPromise = new Promise((res, rej) => {
95
138
  this.resolve = res;
@@ -108,7 +151,7 @@ class IndexedDBAdapter implements Adapter {
108
151
 
109
152
  try {
110
153
  if (!this.db) {
111
- const VERSION = 2;
154
+ const VERSION = 3;
112
155
  this.db = await idb.openDB(this.dbName, VERSION, {
113
156
  upgrade: async (db, oldVersion, newVersion, txn) => {
114
157
  if (oldVersion === 0) {
@@ -116,7 +159,7 @@ class IndexedDBAdapter implements Adapter {
116
159
  const namespace = theSchema.namespaces[namespaceName];
117
160
 
118
161
  Object.keys(namespace.models).forEach(modelName => {
119
- const storeName = this.getStorename(namespaceName, modelName);
162
+ const storeName = getStorename(namespaceName, modelName);
120
163
  this.createObjectStoreForModel(
121
164
  db,
122
165
  namespaceName,
@@ -129,7 +172,7 @@ class IndexedDBAdapter implements Adapter {
129
172
  return;
130
173
  }
131
174
 
132
- if (oldVersion === 1 && newVersion === 2) {
175
+ if ((oldVersion === 1 || oldVersion === 2) && newVersion === 3) {
133
176
  try {
134
177
  for (const storeName of txn.objectStoreNames) {
135
178
  const origStore = txn.objectStore(storeName);
@@ -138,13 +181,15 @@ class IndexedDBAdapter implements Adapter {
138
181
  const tmpName = `tmp_${storeName}`;
139
182
  origStore.name = tmpName;
140
183
 
141
- // create new store with original name
142
- const newStore = db.createObjectStore(storeName, {
143
- keyPath: undefined,
144
- autoIncrement: true,
145
- });
184
+ const { namespaceName, modelName } =
185
+ this.getNamespaceAndModelFromStorename(storeName);
146
186
 
147
- newStore.createIndex('byId', 'id', { unique: true });
187
+ const newStore = this.createObjectStoreForModel(
188
+ db,
189
+ namespaceName,
190
+ storeName,
191
+ modelName
192
+ );
148
193
 
149
194
  let cursor = await origStore.openCursor();
150
195
  let count = 0;
@@ -175,7 +220,7 @@ class IndexedDBAdapter implements Adapter {
175
220
  .map(modelName => {
176
221
  return [
177
222
  modelName,
178
- this.getStorename(namespaceName, modelName),
223
+ getStorename(namespaceName, modelName),
179
224
  ];
180
225
  })
181
226
  .filter(([, storeName]) => !objectStoreNames.has(storeName))
@@ -208,19 +253,19 @@ class IndexedDBAdapter implements Adapter {
208
253
 
209
254
  private async _get<T>(
210
255
  storeOrStoreName: idb.IDBPObjectStore | string,
211
- id: string
256
+ keyArr: string[]
212
257
  ): Promise<T> {
213
258
  let index: idb.IDBPIndex;
214
259
 
215
260
  if (typeof storeOrStoreName === 'string') {
216
261
  const storeName = storeOrStoreName;
217
- index = this.db.transaction(storeName, 'readonly').store.index('byId');
262
+ index = this.db.transaction(storeName, 'readonly').store.index('byPk');
218
263
  } else {
219
264
  const store = storeOrStoreName;
220
- index = store.index('byId');
265
+ index = store.index('byPk');
221
266
  }
222
267
 
223
- const result = await index.get(id);
268
+ const result = await index.get(this.canonicalKeyPath(keyArr));
224
269
 
225
270
  return result;
226
271
  }
@@ -233,21 +278,26 @@ class IndexedDBAdapter implements Adapter {
233
278
  const modelConstructor = Object.getPrototypeOf(model)
234
279
  .constructor as PersistentModelConstructor<T>;
235
280
  const storeName = this.getStorenameForModel(modelConstructor);
281
+ const namespaceName = this.namespaceResolver(modelConstructor);
282
+
236
283
  const connectedModels = traverseModel(
237
284
  modelConstructor.name,
238
285
  model,
239
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
286
+ this.schema.namespaces[namespaceName],
240
287
  this.modelInstanceCreator,
241
288
  this.getModelConstructorByModelName
242
289
  );
243
- const namespaceName = this.namespaceResolver(modelConstructor);
244
290
 
245
291
  const set = new Set<string>();
246
292
  const connectionStoreNames = Object.values(connectedModels).map(
247
293
  ({ modelName, item, instance }) => {
248
- const storeName = this.getStorename(namespaceName, modelName);
294
+ const storeName = getStorename(namespaceName, modelName);
249
295
  set.add(storeName);
250
- return { storeName, item, instance };
296
+ const keys = getIndexKeys(
297
+ this.schema.namespaces[namespaceName],
298
+ modelName
299
+ );
300
+ return { storeName, item, instance, keys };
251
301
  }
252
302
  );
253
303
 
@@ -257,7 +307,9 @@ class IndexedDBAdapter implements Adapter {
257
307
  );
258
308
  const store = tx.objectStore(storeName);
259
309
 
260
- const fromDB = await this._get(store, model.id);
310
+ const keyValues = this.getIndexKeyValuesFromModel(model);
311
+
312
+ const fromDB = await this._get(store, keyValues);
261
313
 
262
314
  if (condition && fromDB) {
263
315
  const predicates = ModelPredicateCreator.getPredicates(condition);
@@ -276,17 +328,28 @@ class IndexedDBAdapter implements Adapter {
276
328
  const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
277
329
 
278
330
  for await (const resItem of connectionStoreNames) {
279
- const { storeName, item, instance } = resItem;
331
+ const { storeName, item, instance, keys } = resItem;
280
332
  const store = tx.objectStore(storeName);
281
- const { id } = item;
282
333
 
283
- const fromDB = <T>await this._get(store, id);
334
+ const itemKeyValues = keys.map(key => {
335
+ const value = item[key];
336
+ return value;
337
+ });
338
+
339
+ const fromDB = <T>await this._get(store, itemKeyValues);
284
340
  const opType: OpType =
285
341
  fromDB === undefined ? OpType.INSERT : OpType.UPDATE;
286
342
 
343
+ const modelKeyValues = this.getIndexKeyValuesFromModel(model);
344
+
287
345
  // Even if the parent is an INSERT, the child might not be, so we need to get its key
288
- if (id === model.id || opType === OpType.INSERT) {
289
- const key = await store.index('byId').getKey(item.id);
346
+ if (
347
+ keysEqual(itemKeyValues, modelKeyValues) ||
348
+ opType === OpType.INSERT
349
+ ) {
350
+ const key = await store
351
+ .index('byPk')
352
+ .getKey(this.canonicalKeyPath(itemKeyValues));
290
353
  await store.put(item, key);
291
354
 
292
355
  result.push([instance, opType]);
@@ -306,7 +369,7 @@ class IndexedDBAdapter implements Adapter {
306
369
  const namespace = this.schema.namespaces[namespaceName];
307
370
  const relations = namespace.relationships[srcModelName].relationTypes;
308
371
  const connectionStoreNames = relations.map(({ modelName }) => {
309
- return this.getStorename(namespaceName, modelName);
372
+ return getStorename(namespaceName, modelName);
310
373
  });
311
374
  const modelConstructor = this.getModelConstructorByModelName(
312
375
  namespaceName,
@@ -322,8 +385,9 @@ class IndexedDBAdapter implements Adapter {
322
385
  const tx = this.db.transaction([...connectionStoreNames], 'readonly');
323
386
 
324
387
  for await (const relation of relations) {
325
- const { fieldName, modelName, targetName } = relation;
326
- const storeName = this.getStorename(namespaceName, modelName);
388
+ // target name, metadata, set by init
389
+ const { fieldName, modelName, targetName, targetNames } = relation;
390
+ const storeName = getStorename(namespaceName, modelName);
327
391
  const store = tx.objectStore(storeName);
328
392
  const modelConstructor = this.getModelConstructorByModelName(
329
393
  namespaceName,
@@ -333,35 +397,91 @@ class IndexedDBAdapter implements Adapter {
333
397
  switch (relation.relationType) {
334
398
  case 'HAS_ONE':
335
399
  for await (const recordItem of records) {
336
- const getByfield = recordItem[targetName] ? targetName : fieldName;
337
- if (!recordItem[getByfield]) break;
400
+ // POST CPK codegen changes:
401
+ if (targetNames?.length) {
402
+ let getByFields = [];
403
+ let allPresent;
404
+ // iterate through all targetnames to make sure they are all present in the recordItem
405
+ allPresent = targetNames.every(targetName => {
406
+ return recordItem[targetName] != null;
407
+ });
338
408
 
339
- const connectionRecord = await this._get(
340
- store,
341
- recordItem[getByfield]
342
- );
409
+ if (!allPresent) {
410
+ break;
411
+ }
343
412
 
344
- recordItem[fieldName] =
345
- connectionRecord &&
346
- this.modelInstanceCreator(modelConstructor, connectionRecord);
347
- }
413
+ getByFields = targetNames as any;
348
414
 
415
+ // keys are the key values
416
+ const keys = getByFields.map(
417
+ getByField => recordItem[getByField]
418
+ );
419
+
420
+ const connectionRecord = await this._get(store, keys);
421
+
422
+ recordItem[fieldName] =
423
+ connectionRecord &&
424
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
425
+ } else {
426
+ // If single target name, using old codegen
427
+ const getByfield = recordItem[targetName]
428
+ ? targetName
429
+ : fieldName;
430
+
431
+ // We break here, because the recordItem does not have 'team', the `getByField`
432
+ // extract the keys on the related model.
433
+ if (!recordItem[getByfield]) break;
434
+
435
+ const key = [recordItem[getByfield]];
436
+
437
+ const connectionRecord = await this._get(store, key);
438
+
439
+ recordItem[fieldName] =
440
+ connectionRecord &&
441
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
442
+ }
443
+ }
349
444
  break;
350
445
  case 'BELONGS_TO':
351
446
  for await (const recordItem of records) {
352
- if (recordItem[targetName]) {
353
- const connectionRecord = await this._get(
354
- store,
355
- recordItem[targetName]
447
+ // POST CPK codegen changes:
448
+ if (targetNames?.length) {
449
+ let allPresent;
450
+ // iterate through all targetnames to make sure they are all present in the recordItem
451
+ allPresent = targetNames.every(targetName => {
452
+ return recordItem[targetName] != null;
453
+ });
454
+
455
+ // If not present, there is not yet a connected record
456
+ if (!allPresent) {
457
+ break;
458
+ }
459
+
460
+ const keys = targetNames.map(
461
+ targetName => recordItem[targetName]
356
462
  );
357
463
 
464
+ // Retrieve the connected record
465
+ const connectionRecord = await this._get(store, keys);
466
+
467
+ recordItem[fieldName] =
468
+ connectionRecord &&
469
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
470
+
471
+ targetNames?.map(targetName => {
472
+ delete recordItem[targetName];
473
+ });
474
+ } else if (recordItem[targetName]) {
475
+ const key = [recordItem[targetName]];
476
+
477
+ const connectionRecord = await this._get(store, key);
478
+
358
479
  recordItem[fieldName] =
359
480
  connectionRecord &&
360
481
  this.modelInstanceCreator(modelConstructor, connectionRecord);
361
482
  delete recordItem[targetName];
362
483
  }
363
484
  }
364
-
365
485
  break;
366
486
  case 'HAS_MANY':
367
487
  // TODO: Lazy loading
@@ -388,13 +508,19 @@ class IndexedDBAdapter implements Adapter {
388
508
 
389
509
  const predicates =
390
510
  predicate && ModelPredicateCreator.getPredicates(predicate);
391
- const queryById = predicates && this.idFromPredicate(predicates);
511
+ const keyPath = getIndexKeys(
512
+ this.schema.namespaces[namespaceName],
513
+ modelConstructor.name
514
+ );
515
+ const queryByKey =
516
+ predicates && this.keyValueFromPredicate(predicates, keyPath);
517
+
392
518
  const hasSort = pagination && pagination.sort;
393
519
  const hasPagination = pagination && pagination.limit;
394
520
 
395
521
  const records: T[] = await (async () => {
396
- if (queryById) {
397
- const record = await this.getById(storeName, queryById);
522
+ if (queryByKey) {
523
+ const record = await this.getByKey(storeName, queryByKey);
398
524
  return record ? [record] : [];
399
525
  }
400
526
 
@@ -418,11 +544,11 @@ class IndexedDBAdapter implements Adapter {
418
544
  return await this.load(namespaceName, modelConstructor.name, records);
419
545
  }
420
546
 
421
- private async getById<T extends PersistentModel>(
547
+ private async getByKey<T extends PersistentModel>(
422
548
  storeName: string,
423
- id: string
549
+ keyValue: string[]
424
550
  ): Promise<T> {
425
- const record = <T>await this._get(storeName, id);
551
+ const record = <T>await this._get(storeName, keyValue);
426
552
  return record;
427
553
  }
428
554
 
@@ -432,17 +558,27 @@ class IndexedDBAdapter implements Adapter {
432
558
  return await this.db.getAll(storeName);
433
559
  }
434
560
 
435
- private idFromPredicate<T extends PersistentModel>(
436
- predicates: PredicatesGroup<T>
437
- ) {
561
+ private keyValueFromPredicate<T extends PersistentModel>(
562
+ predicates: PredicatesGroup<T>,
563
+ keyPath: string[]
564
+ ): string[] | undefined {
438
565
  const { predicates: predicateObjs } = predicates;
439
- const idPredicate =
440
- predicateObjs.length === 1 &&
441
- (predicateObjs.find(
442
- p => isPredicateObj(p) && p.field === 'id' && p.operator === 'eq'
443
- ) as PredicateObject<T>);
444
566
 
445
- return idPredicate && idPredicate.operand;
567
+ if (predicateObjs.length !== keyPath.length) {
568
+ return;
569
+ }
570
+
571
+ const keyValues = [];
572
+
573
+ for (const key of keyPath) {
574
+ const predicateObj = predicateObjs.find(
575
+ p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
576
+ ) as PredicateObject<T>;
577
+
578
+ predicateObj && keyValues.push(predicateObj.operand);
579
+ }
580
+
581
+ return keyValues.length === keyPath.length ? keyValues : undefined;
446
582
  }
447
583
 
448
584
  private async filterOnPredicate<T extends PersistentModel>(
@@ -605,15 +741,16 @@ class IndexedDBAdapter implements Adapter {
605
741
 
606
742
  const modelConstructor = Object.getPrototypeOf(model)
607
743
  .constructor as PersistentModelConstructor<T>;
608
- const nameSpace = this.namespaceResolver(modelConstructor);
744
+ const namespaceName = this.namespaceResolver(modelConstructor);
609
745
 
610
746
  const storeName = this.getStorenameForModel(modelConstructor);
611
747
 
612
748
  if (condition) {
613
749
  const tx = this.db.transaction([storeName], 'readwrite');
614
750
  const store = tx.objectStore(storeName);
751
+ const keyValues = this.getIndexKeyValuesFromModel(model);
615
752
 
616
- const fromDB = await this._get(store, model.id);
753
+ const fromDB = await this._get(store, keyValues);
617
754
 
618
755
  if (fromDB === undefined) {
619
756
  const msg = 'Model instance not found in storage';
@@ -636,26 +773,28 @@ class IndexedDBAdapter implements Adapter {
636
773
  await tx.done;
637
774
 
638
775
  const relations =
639
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
640
- .relationTypes;
776
+ this.schema.namespaces[namespaceName].relationships[
777
+ modelConstructor.name
778
+ ].relationTypes;
641
779
 
642
780
  await this.deleteTraverse(
643
781
  relations,
644
782
  [model],
645
783
  modelConstructor.name,
646
- nameSpace,
784
+ namespaceName,
647
785
  deleteQueue
648
786
  );
649
787
  } else {
650
788
  const relations =
651
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
652
- .relationTypes;
789
+ this.schema.namespaces[namespaceName].relationships[
790
+ modelConstructor.name
791
+ ].relationTypes;
653
792
 
654
793
  await this.deleteTraverse(
655
794
  relations,
656
795
  [model],
657
796
  modelConstructor.name,
658
- nameSpace,
797
+ namespaceName,
659
798
  deleteQueue
660
799
  );
661
800
  }
@@ -672,7 +811,10 @@ class IndexedDBAdapter implements Adapter {
672
811
  }
673
812
 
674
813
  private async deleteItem<T extends PersistentModel>(
675
- deleteQueue?: { storeName: string; items: T[] | IDBValidKey[] }[]
814
+ deleteQueue?: {
815
+ storeName: string;
816
+ items: T[] | IDBValidKey[];
817
+ }[]
676
818
  ) {
677
819
  const connectionStoreNames = deleteQueue.map(({ storeName }) => {
678
820
  return storeName;
@@ -688,9 +830,13 @@ class IndexedDBAdapter implements Adapter {
688
830
  let key: IDBValidKey;
689
831
 
690
832
  if (typeof item === 'object') {
691
- key = await store.index('byId').getKey(item['id']);
833
+ const keyValues = this.getIndexKeyValuesFromModel(item as T);
834
+ key = await store
835
+ .index('byPk')
836
+ .getKey(this.canonicalKeyPath(keyValues));
692
837
  } else {
693
- key = await store.index('byId').getKey(item.toString());
838
+ const itemKey = item.toString();
839
+ key = await store.index('byPk').getKey(itemKey);
694
840
  }
695
841
 
696
842
  if (key !== undefined) {
@@ -709,57 +855,109 @@ class IndexedDBAdapter implements Adapter {
709
855
  deleteQueue: { storeName: string; items: T[] }[]
710
856
  ): Promise<void> {
711
857
  for await (const rel of relations) {
712
- const { relationType, fieldName, modelName, targetName } = rel;
713
- const storeName = this.getStorename(nameSpace, modelName);
714
-
715
- const index: string =
716
- getIndex(
717
- this.schema.namespaces[nameSpace].relationships[modelName]
718
- .relationTypes,
719
- srcModel
720
- ) ||
721
- // if we were unable to find an index via relationTypes
722
- // i.e. for keyName connections, attempt to find one by the
723
- // associatedWith property
724
- getIndexFromAssociation(
725
- this.schema.namespaces[nameSpace].relationships[modelName].indexes,
726
- rel.associatedWith
727
- );
858
+ const {
859
+ relationType,
860
+ modelName,
861
+ targetName,
862
+ targetNames,
863
+ associatedWith,
864
+ } = rel;
865
+
866
+ const storeName = getStorename(nameSpace, modelName);
728
867
 
729
868
  switch (relationType) {
730
869
  case 'HAS_ONE':
731
870
  for await (const model of models) {
732
- const hasOneIndex = index || 'byId';
733
-
734
- const hasOneCustomField = targetName in model;
735
- const value = hasOneCustomField ? model[targetName] : model.id;
736
- if (!value) break;
737
-
738
- const recordToDelete = <T>(
739
- await this.db
740
- .transaction(storeName, 'readwrite')
741
- .objectStore(storeName)
742
- .index(hasOneIndex)
743
- .get(value)
744
- );
871
+ const hasOneIndex = 'byPk';
745
872
 
746
- await this.deleteTraverse(
747
- this.schema.namespaces[nameSpace].relationships[modelName]
748
- .relationTypes,
749
- recordToDelete ? [recordToDelete] : [],
750
- modelName,
751
- nameSpace,
752
- deleteQueue
753
- );
873
+ if (targetNames?.length) {
874
+ // CPK codegen
875
+ const values = targetNames.map(targetName => model[targetName]);
876
+
877
+ if (values.length === 0) break;
878
+
879
+ const recordToDelete = <T>(
880
+ await this.db
881
+ .transaction(storeName, 'readwrite')
882
+ .objectStore(storeName)
883
+ .index(hasOneIndex)
884
+ .get(this.canonicalKeyPath(values))
885
+ );
886
+
887
+ await this.deleteTraverse(
888
+ this.schema.namespaces[nameSpace].relationships[modelName]
889
+ .relationTypes,
890
+ recordToDelete ? [recordToDelete] : [],
891
+ modelName,
892
+ nameSpace,
893
+ deleteQueue
894
+ );
895
+ break;
896
+ } else {
897
+ // PRE-CPK codegen
898
+ let index;
899
+ let values: string[];
900
+
901
+ if (targetName && targetName in model) {
902
+ index = hasOneIndex;
903
+ const value = model[targetName];
904
+ values = [value];
905
+ } else {
906
+ // backwards compatability for older versions of codegen that did not emit targetName for HAS_ONE relations
907
+ // TODO: can we deprecate this? it's been ~2 years since codegen started including targetName for HAS_ONE
908
+ // If we deprecate, we'll need to re-gen the MIPR in __tests__/schema.ts > newSchema
909
+ // otherwise some unit tests will fail
910
+ index = getIndex(
911
+ this.schema.namespaces[nameSpace].relationships[modelName]
912
+ .relationTypes,
913
+ srcModel
914
+ );
915
+ values = this.getIndexKeyValuesFromModel(model);
916
+ }
917
+
918
+ if (!values || !index) break;
919
+
920
+ const recordToDelete = <T>(
921
+ await this.db
922
+ .transaction(storeName, 'readwrite')
923
+ .objectStore(storeName)
924
+ .index(index)
925
+ .get(this.canonicalKeyPath(values))
926
+ );
927
+
928
+ await this.deleteTraverse(
929
+ this.schema.namespaces[nameSpace].relationships[modelName]
930
+ .relationTypes,
931
+ recordToDelete ? [recordToDelete] : [],
932
+ modelName,
933
+ nameSpace,
934
+ deleteQueue
935
+ );
936
+ }
754
937
  }
755
938
  break;
756
939
  case 'HAS_MANY':
757
940
  for await (const model of models) {
941
+ const index =
942
+ // explicit bi-directional @hasMany and @manyToMany
943
+ getIndex(
944
+ this.schema.namespaces[nameSpace].relationships[modelName]
945
+ .relationTypes,
946
+ srcModel
947
+ ) ||
948
+ // uni and/or implicit @hasMany
949
+ getIndexFromAssociation(
950
+ this.schema.namespaces[nameSpace].relationships[modelName]
951
+ .indexes,
952
+ associatedWith
953
+ );
954
+ const keyValues = this.getIndexKeyValuesFromModel(model);
955
+
758
956
  const childrenArray = await this.db
759
957
  .transaction(storeName, 'readwrite')
760
958
  .objectStore(storeName)
761
- .index(index)
762
- .getAll(model['id']);
959
+ .index(index as string)
960
+ .getAll(this.canonicalKeyPath(keyValues));
763
961
 
764
962
  await this.deleteTraverse(
765
963
  this.schema.namespaces[nameSpace].relationships[modelName]
@@ -781,7 +979,7 @@ class IndexedDBAdapter implements Adapter {
781
979
  }
782
980
 
783
981
  deleteQueue.push({
784
- storeName: this.getStorename(nameSpace, srcModel),
982
+ storeName: getStorename(nameSpace, srcModel),
785
983
  items: models.map(record =>
786
984
  this.modelInstanceCreator(
787
985
  this.getModelConstructorByModelName(nameSpace, srcModel),
@@ -820,22 +1018,30 @@ class IndexedDBAdapter implements Adapter {
820
1018
  const store = txn.store;
821
1019
 
822
1020
  for (const item of items) {
1021
+ const namespaceName = this.namespaceResolver(modelConstructor);
1022
+ const modelName = modelConstructor.name;
1023
+ const model = this.modelInstanceCreator(modelConstructor, item);
1024
+
823
1025
  const connectedModels = traverseModel(
824
- modelConstructor.name,
825
- this.modelInstanceCreator(modelConstructor, item),
826
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
1026
+ modelName,
1027
+ model,
1028
+ this.schema.namespaces[namespaceName],
827
1029
  this.modelInstanceCreator,
828
1030
  this.getModelConstructorByModelName
829
1031
  );
830
1032
 
831
- const { id, _deleted } = item;
832
- const index = store.index('byId');
833
- const key = await index.getKey(id);
1033
+ const keyValues = this.getIndexKeyValuesFromModel(model);
1034
+ const { _deleted } = item;
1035
+
1036
+ const index = store.index('byPk');
1037
+
1038
+ const key = await index.getKey(this.canonicalKeyPath(keyValues));
834
1039
 
835
1040
  if (!_deleted) {
836
- const { instance } = connectedModels.find(
837
- ({ instance }) => instance.id === id
838
- );
1041
+ const { instance } = connectedModels.find(({ instance }) => {
1042
+ const instanceKeyValues = this.getIndexKeyValuesFromModel(instance);
1043
+ return keysEqual(instanceKeyValues, keyValues);
1044
+ });
839
1045
 
840
1046
  result.push([
841
1047
  <T>(<unknown>instance),
@@ -856,7 +1062,7 @@ class IndexedDBAdapter implements Adapter {
856
1062
  return result;
857
1063
  }
858
1064
 
859
- private async createObjectStoreForModel(
1065
+ private createObjectStoreForModel(
860
1066
  db: idb.IDBPDatabase,
861
1067
  namespaceName: string,
862
1068
  storeName: string,
@@ -866,12 +1072,30 @@ class IndexedDBAdapter implements Adapter {
866
1072
  autoIncrement: true,
867
1073
  });
868
1074
 
869
- const indexes =
870
- this.schema.namespaces[namespaceName].relationships[modelName].indexes;
871
- indexes.forEach(index => store.createIndex(index, index));
1075
+ const { indexes } =
1076
+ this.schema.namespaces[namespaceName].relationships[modelName];
872
1077
 
873
- store.createIndex('byId', 'id', { unique: true });
1078
+ indexes.forEach(([idxName, keyPath, options]) => {
1079
+ store.createIndex(idxName, keyPath, options);
1080
+ });
1081
+
1082
+ return store;
874
1083
  }
1084
+
1085
+ /**
1086
+ * Checks the given path against the browser's IndexedDB implementation for
1087
+ * necessary compatibility transformations, applying those transforms if needed.
1088
+ *
1089
+ * @param `keyArr` strings to compatibilize for browser-indexeddb index operations
1090
+ * @returns An array or string, depending on and given key,
1091
+ * that is ensured to be compatible with the IndexedDB implementation's nuances.
1092
+ */
1093
+ private canonicalKeyPath = (keyArr: string[]) => {
1094
+ if (this.safariCompatabilityMode) {
1095
+ return keyArr.length > 1 ? keyArr : keyArr[0];
1096
+ }
1097
+ return keyArr;
1098
+ };
875
1099
  }
876
1100
 
877
1101
  export default new IndexedDBAdapter();