@aws-amplify/datastore 3.12.12 → 3.12.13-unstable.1

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 (105) hide show
  1. package/dist/aws-amplify-datastore.js +1854 -965
  2. package/dist/aws-amplify-datastore.js.map +1 -1
  3. package/dist/aws-amplify-datastore.min.js +7 -7
  4. package/dist/aws-amplify-datastore.min.js.map +1 -1
  5. package/lib/datastore/datastore.d.ts +13 -16
  6. package/lib/datastore/datastore.js +130 -63
  7. package/lib/datastore/datastore.js.map +1 -1
  8. package/lib/index.d.ts +3 -19
  9. package/lib/predicates/index.d.ts +3 -2
  10. package/lib/predicates/index.js +12 -2
  11. package/lib/predicates/index.js.map +1 -1
  12. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  13. package/lib/storage/adapter/AsyncStorageAdapter.js +354 -203
  14. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  15. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  16. package/lib/storage/adapter/AsyncStorageDatabase.js +65 -28
  17. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  18. package/lib/storage/adapter/IndexedDBAdapter.d.ts +5 -4
  19. package/lib/storage/adapter/IndexedDBAdapter.js +389 -267
  20. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  21. package/lib/storage/adapter/index.d.ts +1 -1
  22. package/lib/storage/storage.d.ts +1 -1
  23. package/lib/storage/storage.js +92 -27
  24. package/lib/storage/storage.js.map +1 -1
  25. package/lib/sync/index.d.ts +21 -4
  26. package/lib/sync/index.js +13 -11
  27. package/lib/sync/index.js.map +1 -1
  28. package/lib/sync/merger.d.ts +3 -3
  29. package/lib/sync/merger.js +7 -6
  30. package/lib/sync/merger.js.map +1 -1
  31. package/lib/sync/outbox.d.ts +2 -2
  32. package/lib/sync/outbox.js +11 -9
  33. package/lib/sync/outbox.js.map +1 -1
  34. package/lib/sync/processors/mutation.js +60 -42
  35. package/lib/sync/processors/mutation.js.map +1 -1
  36. package/lib/sync/processors/subscription.js.map +1 -1
  37. package/lib/sync/processors/sync.js.map +1 -1
  38. package/lib/sync/utils.d.ts +3 -2
  39. package/lib/sync/utils.js +61 -8
  40. package/lib/sync/utils.js.map +1 -1
  41. package/lib/types.d.ts +64 -25
  42. package/lib/types.js +10 -1
  43. package/lib/types.js.map +1 -1
  44. package/lib/util.d.ts +56 -24
  45. package/lib/util.js +334 -170
  46. package/lib/util.js.map +1 -1
  47. package/lib-esm/datastore/datastore.d.ts +13 -16
  48. package/lib-esm/datastore/datastore.js +132 -65
  49. package/lib-esm/datastore/datastore.js.map +1 -1
  50. package/lib-esm/index.d.ts +3 -19
  51. package/lib-esm/predicates/index.d.ts +3 -2
  52. package/lib-esm/predicates/index.js +13 -3
  53. package/lib-esm/predicates/index.js.map +1 -1
  54. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  55. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +355 -204
  56. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  57. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  58. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +66 -29
  59. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  60. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +5 -4
  61. package/lib-esm/storage/adapter/IndexedDBAdapter.js +390 -268
  62. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  63. package/lib-esm/storage/adapter/index.d.ts +1 -1
  64. package/lib-esm/storage/storage.d.ts +1 -1
  65. package/lib-esm/storage/storage.js +92 -27
  66. package/lib-esm/storage/storage.js.map +1 -1
  67. package/lib-esm/sync/index.d.ts +21 -4
  68. package/lib-esm/sync/index.js +15 -13
  69. package/lib-esm/sync/index.js.map +1 -1
  70. package/lib-esm/sync/merger.d.ts +3 -3
  71. package/lib-esm/sync/merger.js +7 -6
  72. package/lib-esm/sync/merger.js.map +1 -1
  73. package/lib-esm/sync/outbox.d.ts +2 -2
  74. package/lib-esm/sync/outbox.js +12 -10
  75. package/lib-esm/sync/outbox.js.map +1 -1
  76. package/lib-esm/sync/processors/mutation.js +61 -43
  77. package/lib-esm/sync/processors/mutation.js.map +1 -1
  78. package/lib-esm/sync/processors/subscription.js.map +1 -1
  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 +62 -10
  82. package/lib-esm/sync/utils.js.map +1 -1
  83. package/lib-esm/types.d.ts +64 -25
  84. package/lib-esm/types.js +9 -2
  85. package/lib-esm/types.js.map +1 -1
  86. package/lib-esm/util.d.ts +56 -24
  87. package/lib-esm/util.js +334 -170
  88. package/lib-esm/util.js.map +1 -1
  89. package/package.json +7 -7
  90. package/src/datastore/datastore.ts +253 -113
  91. package/src/predicates/index.ts +32 -10
  92. package/src/storage/adapter/AsyncStorageAdapter.ts +309 -93
  93. package/src/storage/adapter/AsyncStorageDatabase.ts +74 -26
  94. package/src/storage/adapter/IndexedDBAdapter.ts +319 -136
  95. package/src/storage/adapter/index.ts +1 -1
  96. package/src/storage/storage.ts +68 -21
  97. package/src/sync/index.ts +41 -26
  98. package/src/sync/merger.ts +14 -4
  99. package/src/sync/outbox.ts +21 -8
  100. package/src/sync/processors/mutation.ts +49 -45
  101. package/src/sync/processors/subscription.ts +0 -1
  102. package/src/sync/processors/sync.ts +1 -3
  103. package/src/sync/utils.ts +69 -12
  104. package/src/types.ts +181 -29
  105. package/src/util.ts +415 -176
@@ -29,13 +29,16 @@ import {
29
29
  traverseModel,
30
30
  validatePredicate,
31
31
  sortCompareFunction,
32
+ keysEqual,
33
+ getStorename,
34
+ getIndexKeys,
35
+ extractPrimaryKeyValues,
32
36
  } from '../../util';
33
37
  import { Adapter } from './index';
34
38
 
35
39
  const logger = new Logger('DataStore');
36
40
 
37
41
  const DB_NAME = 'amplify-datastore';
38
-
39
42
  class IndexedDBAdapter implements Adapter {
40
43
  private schema: InternalSchema;
41
44
  private namespaceResolver: NamespaceResolver;
@@ -50,6 +53,31 @@ class IndexedDBAdapter implements Adapter {
50
53
  private reject: (value?: any) => void;
51
54
  private dbName: string = DB_NAME;
52
55
 
56
+ private getStorenameForModel(
57
+ modelConstructor: PersistentModelConstructor<any>
58
+ ) {
59
+ const namespace = this.namespaceResolver(modelConstructor);
60
+ const { name: modelName } = modelConstructor;
61
+
62
+ return getStorename(namespace, modelName);
63
+ }
64
+
65
+ // Retrieves primary key values from a model
66
+ private getIndexKeyValuesFromModel<T extends PersistentModel>(
67
+ model: T
68
+ ): string[] {
69
+ const modelConstructor = Object.getPrototypeOf(model)
70
+ .constructor as PersistentModelConstructor<T>;
71
+ const namespaceName = this.namespaceResolver(modelConstructor);
72
+
73
+ const keys = getIndexKeys(
74
+ this.schema.namespaces[namespaceName],
75
+ modelConstructor.name
76
+ );
77
+
78
+ return extractPrimaryKeyValues(model, keys);
79
+ }
80
+
53
81
  private async checkPrivate() {
54
82
  const isPrivate = await isPrivateMode().then(isPrivate => {
55
83
  return isPrivate;
@@ -64,19 +92,12 @@ class IndexedDBAdapter implements Adapter {
64
92
  }
65
93
  }
66
94
 
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);
74
- }
75
-
76
- private getStorename(namespace: string, modelName: string) {
77
- const storeName = `${namespace}_${modelName}`;
78
-
79
- return storeName;
95
+ private getNamespaceAndModelFromStorename(storeName: string) {
96
+ const [namespaceName, ...modelNameArr] = storeName.split('_');
97
+ return {
98
+ namespaceName,
99
+ modelName: modelNameArr.join('_'),
100
+ };
80
101
  }
81
102
 
82
103
  async setUp(
@@ -90,6 +111,7 @@ class IndexedDBAdapter implements Adapter {
90
111
  sessionId?: string
91
112
  ) {
92
113
  await this.checkPrivate();
114
+
93
115
  if (!this.initPromise) {
94
116
  this.initPromise = new Promise((res, rej) => {
95
117
  this.resolve = res;
@@ -108,7 +130,7 @@ class IndexedDBAdapter implements Adapter {
108
130
 
109
131
  try {
110
132
  if (!this.db) {
111
- const VERSION = 2;
133
+ const VERSION = 3;
112
134
  this.db = await idb.openDB(this.dbName, VERSION, {
113
135
  upgrade: async (db, oldVersion, newVersion, txn) => {
114
136
  if (oldVersion === 0) {
@@ -116,7 +138,7 @@ class IndexedDBAdapter implements Adapter {
116
138
  const namespace = theSchema.namespaces[namespaceName];
117
139
 
118
140
  Object.keys(namespace.models).forEach(modelName => {
119
- const storeName = this.getStorename(namespaceName, modelName);
141
+ const storeName = getStorename(namespaceName, modelName);
120
142
  this.createObjectStoreForModel(
121
143
  db,
122
144
  namespaceName,
@@ -129,7 +151,7 @@ class IndexedDBAdapter implements Adapter {
129
151
  return;
130
152
  }
131
153
 
132
- if (oldVersion === 1 && newVersion === 2) {
154
+ if ((oldVersion === 1 || oldVersion === 2) && newVersion === 3) {
133
155
  try {
134
156
  for (const storeName of txn.objectStoreNames) {
135
157
  const origStore = txn.objectStore(storeName);
@@ -138,13 +160,15 @@ class IndexedDBAdapter implements Adapter {
138
160
  const tmpName = `tmp_${storeName}`;
139
161
  origStore.name = tmpName;
140
162
 
141
- // create new store with original name
142
- const newStore = db.createObjectStore(storeName, {
143
- keyPath: undefined,
144
- autoIncrement: true,
145
- });
163
+ const { namespaceName, modelName } =
164
+ this.getNamespaceAndModelFromStorename(storeName);
146
165
 
147
- newStore.createIndex('byId', 'id', { unique: true });
166
+ const newStore = this.createObjectStoreForModel(
167
+ db,
168
+ namespaceName,
169
+ storeName,
170
+ modelName
171
+ );
148
172
 
149
173
  let cursor = await origStore.openCursor();
150
174
  let count = 0;
@@ -175,7 +199,7 @@ class IndexedDBAdapter implements Adapter {
175
199
  .map(modelName => {
176
200
  return [
177
201
  modelName,
178
- this.getStorename(namespaceName, modelName),
202
+ getStorename(namespaceName, modelName),
179
203
  ];
180
204
  })
181
205
  .filter(([, storeName]) => !objectStoreNames.has(storeName))
@@ -208,19 +232,19 @@ class IndexedDBAdapter implements Adapter {
208
232
 
209
233
  private async _get<T>(
210
234
  storeOrStoreName: idb.IDBPObjectStore | string,
211
- id: string
235
+ keyArr: string[]
212
236
  ): Promise<T> {
213
237
  let index: idb.IDBPIndex;
214
238
 
215
239
  if (typeof storeOrStoreName === 'string') {
216
240
  const storeName = storeOrStoreName;
217
- index = this.db.transaction(storeName, 'readonly').store.index('byId');
241
+ index = this.db.transaction(storeName, 'readonly').store.index('byPk');
218
242
  } else {
219
243
  const store = storeOrStoreName;
220
- index = store.index('byId');
244
+ index = store.index('byPk');
221
245
  }
222
246
 
223
- const result = await index.get(id);
247
+ const result = await index.get(keyArr);
224
248
 
225
249
  return result;
226
250
  }
@@ -233,21 +257,26 @@ class IndexedDBAdapter implements Adapter {
233
257
  const modelConstructor = Object.getPrototypeOf(model)
234
258
  .constructor as PersistentModelConstructor<T>;
235
259
  const storeName = this.getStorenameForModel(modelConstructor);
260
+ const namespaceName = this.namespaceResolver(modelConstructor);
261
+
236
262
  const connectedModels = traverseModel(
237
263
  modelConstructor.name,
238
264
  model,
239
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
265
+ this.schema.namespaces[namespaceName],
240
266
  this.modelInstanceCreator,
241
267
  this.getModelConstructorByModelName
242
268
  );
243
- const namespaceName = this.namespaceResolver(modelConstructor);
244
269
 
245
270
  const set = new Set<string>();
246
271
  const connectionStoreNames = Object.values(connectedModels).map(
247
272
  ({ modelName, item, instance }) => {
248
- const storeName = this.getStorename(namespaceName, modelName);
273
+ const storeName = getStorename(namespaceName, modelName);
249
274
  set.add(storeName);
250
- return { storeName, item, instance };
275
+ const keys = getIndexKeys(
276
+ this.schema.namespaces[namespaceName],
277
+ modelName
278
+ );
279
+ return { storeName, item, instance, keys };
251
280
  }
252
281
  );
253
282
 
@@ -257,7 +286,9 @@ class IndexedDBAdapter implements Adapter {
257
286
  );
258
287
  const store = tx.objectStore(storeName);
259
288
 
260
- const fromDB = await this._get(store, model.id);
289
+ const keyValues = this.getIndexKeyValuesFromModel(model);
290
+
291
+ const fromDB = await this._get(store, keyValues);
261
292
 
262
293
  if (condition && fromDB) {
263
294
  const predicates = ModelPredicateCreator.getPredicates(condition);
@@ -276,17 +307,26 @@ class IndexedDBAdapter implements Adapter {
276
307
  const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
277
308
 
278
309
  for await (const resItem of connectionStoreNames) {
279
- const { storeName, item, instance } = resItem;
310
+ const { storeName, item, instance, keys } = resItem;
280
311
  const store = tx.objectStore(storeName);
281
- const { id } = item;
282
312
 
283
- const fromDB = <T>await this._get(store, id);
313
+ const itemKeyValues = keys.map(key => {
314
+ const value = item[key];
315
+ return value;
316
+ });
317
+
318
+ const fromDB = <T>await this._get(store, itemKeyValues);
284
319
  const opType: OpType =
285
320
  fromDB === undefined ? OpType.INSERT : OpType.UPDATE;
286
321
 
322
+ const modelKeyValues = this.getIndexKeyValuesFromModel(model);
323
+
287
324
  // 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);
325
+ if (
326
+ keysEqual(itemKeyValues, modelKeyValues) ||
327
+ opType === OpType.INSERT
328
+ ) {
329
+ const key = await store.index('byPk').getKey(itemKeyValues);
290
330
  await store.put(item, key);
291
331
 
292
332
  result.push([instance, opType]);
@@ -306,7 +346,7 @@ class IndexedDBAdapter implements Adapter {
306
346
  const namespace = this.schema.namespaces[namespaceName];
307
347
  const relations = namespace.relationships[srcModelName].relationTypes;
308
348
  const connectionStoreNames = relations.map(({ modelName }) => {
309
- return this.getStorename(namespaceName, modelName);
349
+ return getStorename(namespaceName, modelName);
310
350
  });
311
351
  const modelConstructor = this.getModelConstructorByModelName(
312
352
  namespaceName,
@@ -322,8 +362,9 @@ class IndexedDBAdapter implements Adapter {
322
362
  const tx = this.db.transaction([...connectionStoreNames], 'readonly');
323
363
 
324
364
  for await (const relation of relations) {
325
- const { fieldName, modelName, targetName } = relation;
326
- const storeName = this.getStorename(namespaceName, modelName);
365
+ // target name, metadata, set by init
366
+ const { fieldName, modelName, targetName, targetNames } = relation;
367
+ const storeName = getStorename(namespaceName, modelName);
327
368
  const store = tx.objectStore(storeName);
328
369
  const modelConstructor = this.getModelConstructorByModelName(
329
370
  namespaceName,
@@ -333,35 +374,91 @@ class IndexedDBAdapter implements Adapter {
333
374
  switch (relation.relationType) {
334
375
  case 'HAS_ONE':
335
376
  for await (const recordItem of records) {
336
- const getByfield = recordItem[targetName] ? targetName : fieldName;
337
- if (!recordItem[getByfield]) break;
377
+ // POST CPK codegen changes:
378
+ if (targetNames?.length) {
379
+ let getByFields = [];
380
+ let allPresent;
381
+ // iterate through all targetnames to make sure they are all present in the recordItem
382
+ allPresent = targetNames.every(targetName => {
383
+ return recordItem[targetName] != null;
384
+ });
338
385
 
339
- const connectionRecord = await this._get(
340
- store,
341
- recordItem[getByfield]
342
- );
386
+ if (!allPresent) {
387
+ break;
388
+ }
343
389
 
344
- recordItem[fieldName] =
345
- connectionRecord &&
346
- this.modelInstanceCreator(modelConstructor, connectionRecord);
347
- }
390
+ getByFields = targetNames as any;
391
+
392
+ // keys are the key values
393
+ const keys = getByFields.map(
394
+ getByField => recordItem[getByField]
395
+ );
396
+
397
+ const connectionRecord = await this._get(store, keys);
398
+
399
+ recordItem[fieldName] =
400
+ connectionRecord &&
401
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
402
+ } else {
403
+ // If single target name, using old codegen
404
+ const getByfield = recordItem[targetName]
405
+ ? targetName
406
+ : fieldName;
407
+
408
+ // We break here, because the recordItem does not have 'team', the `getByField`
409
+ // extract the keys on the related model.
410
+ if (!recordItem[getByfield]) break;
411
+
412
+ const key = [recordItem[getByfield]];
413
+
414
+ const connectionRecord = await this._get(store, key);
348
415
 
416
+ recordItem[fieldName] =
417
+ connectionRecord &&
418
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
419
+ }
420
+ }
349
421
  break;
350
422
  case 'BELONGS_TO':
351
423
  for await (const recordItem of records) {
352
- if (recordItem[targetName]) {
353
- const connectionRecord = await this._get(
354
- store,
355
- recordItem[targetName]
424
+ // POST CPK codegen changes:
425
+ if (targetNames?.length) {
426
+ let allPresent;
427
+ // iterate through all targetnames to make sure they are all present in the recordItem
428
+ allPresent = targetNames.every(targetName => {
429
+ return recordItem[targetName] != null;
430
+ });
431
+
432
+ // If not present, there is not yet a connected record
433
+ if (!allPresent) {
434
+ break;
435
+ }
436
+
437
+ const keys = targetNames.map(
438
+ targetName => recordItem[targetName]
356
439
  );
357
440
 
441
+ // Retrieve the connected record
442
+ const connectionRecord = await this._get(store, keys);
443
+
444
+ recordItem[fieldName] =
445
+ connectionRecord &&
446
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
447
+
448
+ targetNames?.map(targetName => {
449
+ delete recordItem[targetName];
450
+ });
451
+ } else if (recordItem[targetName]) {
452
+ const key = [recordItem[targetName]];
453
+
454
+ const connectionRecord = await this._get(store, key);
455
+
358
456
  recordItem[fieldName] =
359
457
  connectionRecord &&
360
458
  this.modelInstanceCreator(modelConstructor, connectionRecord);
361
459
  delete recordItem[targetName];
362
460
  }
363
461
  }
364
-
365
462
  break;
366
463
  case 'HAS_MANY':
367
464
  // TODO: Lazy loading
@@ -388,13 +485,19 @@ class IndexedDBAdapter implements Adapter {
388
485
 
389
486
  const predicates =
390
487
  predicate && ModelPredicateCreator.getPredicates(predicate);
391
- const queryById = predicates && this.idFromPredicate(predicates);
488
+ const keyPath = getIndexKeys(
489
+ this.schema.namespaces[namespaceName],
490
+ modelConstructor.name
491
+ );
492
+ const queryByKey =
493
+ predicates && this.keyValueFromPredicate(predicates, keyPath);
494
+
392
495
  const hasSort = pagination && pagination.sort;
393
496
  const hasPagination = pagination && pagination.limit;
394
497
 
395
498
  const records: T[] = await (async () => {
396
- if (queryById) {
397
- const record = await this.getById(storeName, queryById);
499
+ if (queryByKey) {
500
+ const record = await this.getByKey(storeName, queryByKey);
398
501
  return record ? [record] : [];
399
502
  }
400
503
 
@@ -418,11 +521,11 @@ class IndexedDBAdapter implements Adapter {
418
521
  return await this.load(namespaceName, modelConstructor.name, records);
419
522
  }
420
523
 
421
- private async getById<T extends PersistentModel>(
524
+ private async getByKey<T extends PersistentModel>(
422
525
  storeName: string,
423
- id: string
526
+ keyValue: string[]
424
527
  ): Promise<T> {
425
- const record = <T>await this._get(storeName, id);
528
+ const record = <T>await this._get(storeName, keyValue);
426
529
  return record;
427
530
  }
428
531
 
@@ -432,17 +535,27 @@ class IndexedDBAdapter implements Adapter {
432
535
  return await this.db.getAll(storeName);
433
536
  }
434
537
 
435
- private idFromPredicate<T extends PersistentModel>(
436
- predicates: PredicatesGroup<T>
437
- ) {
538
+ private keyValueFromPredicate<T extends PersistentModel>(
539
+ predicates: PredicatesGroup<T>,
540
+ keyPath: string[]
541
+ ): string[] | undefined {
438
542
  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
543
 
445
- return idPredicate && idPredicate.operand;
544
+ if (predicateObjs.length !== keyPath.length) {
545
+ return;
546
+ }
547
+
548
+ const keyValues = [];
549
+
550
+ for (const key of keyPath) {
551
+ const predicateObj = predicateObjs.find(
552
+ p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
553
+ ) as PredicateObject<T>;
554
+
555
+ predicateObj && keyValues.push(predicateObj.operand);
556
+ }
557
+
558
+ return keyValues.length === keyPath.length ? keyValues : undefined;
446
559
  }
447
560
 
448
561
  private async filterOnPredicate<T extends PersistentModel>(
@@ -605,15 +718,16 @@ class IndexedDBAdapter implements Adapter {
605
718
 
606
719
  const modelConstructor = Object.getPrototypeOf(model)
607
720
  .constructor as PersistentModelConstructor<T>;
608
- const nameSpace = this.namespaceResolver(modelConstructor);
721
+ const namespaceName = this.namespaceResolver(modelConstructor);
609
722
 
610
723
  const storeName = this.getStorenameForModel(modelConstructor);
611
724
 
612
725
  if (condition) {
613
726
  const tx = this.db.transaction([storeName], 'readwrite');
614
727
  const store = tx.objectStore(storeName);
728
+ const keyValues = this.getIndexKeyValuesFromModel(model);
615
729
 
616
- const fromDB = await this._get(store, model.id);
730
+ const fromDB = await this._get(store, keyValues);
617
731
 
618
732
  if (fromDB === undefined) {
619
733
  const msg = 'Model instance not found in storage';
@@ -636,26 +750,28 @@ class IndexedDBAdapter implements Adapter {
636
750
  await tx.done;
637
751
 
638
752
  const relations =
639
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
640
- .relationTypes;
753
+ this.schema.namespaces[namespaceName].relationships[
754
+ modelConstructor.name
755
+ ].relationTypes;
641
756
 
642
757
  await this.deleteTraverse(
643
758
  relations,
644
759
  [model],
645
760
  modelConstructor.name,
646
- nameSpace,
761
+ namespaceName,
647
762
  deleteQueue
648
763
  );
649
764
  } else {
650
765
  const relations =
651
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
652
- .relationTypes;
766
+ this.schema.namespaces[namespaceName].relationships[
767
+ modelConstructor.name
768
+ ].relationTypes;
653
769
 
654
770
  await this.deleteTraverse(
655
771
  relations,
656
772
  [model],
657
773
  modelConstructor.name,
658
- nameSpace,
774
+ namespaceName,
659
775
  deleteQueue
660
776
  );
661
777
  }
@@ -672,7 +788,10 @@ class IndexedDBAdapter implements Adapter {
672
788
  }
673
789
 
674
790
  private async deleteItem<T extends PersistentModel>(
675
- deleteQueue?: { storeName: string; items: T[] | IDBValidKey[] }[]
791
+ deleteQueue?: {
792
+ storeName: string;
793
+ items: T[] | IDBValidKey[];
794
+ }[]
676
795
  ) {
677
796
  const connectionStoreNames = deleteQueue.map(({ storeName }) => {
678
797
  return storeName;
@@ -688,9 +807,11 @@ class IndexedDBAdapter implements Adapter {
688
807
  let key: IDBValidKey;
689
808
 
690
809
  if (typeof item === 'object') {
691
- key = await store.index('byId').getKey(item['id']);
810
+ const keyValues = this.getIndexKeyValuesFromModel(item as T);
811
+ key = await store.index('byPk').getKey(keyValues);
692
812
  } else {
693
- key = await store.index('byId').getKey(item.toString());
813
+ const itemKey = [item.toString()];
814
+ key = await store.index('byPk').getKey([itemKey]);
694
815
  }
695
816
 
696
817
  if (key !== undefined) {
@@ -709,57 +830,109 @@ class IndexedDBAdapter implements Adapter {
709
830
  deleteQueue: { storeName: string; items: T[] }[]
710
831
  ): Promise<void> {
711
832
  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
- );
833
+ const {
834
+ relationType,
835
+ modelName,
836
+ targetName,
837
+ targetNames,
838
+ associatedWith,
839
+ } = rel;
840
+
841
+ const storeName = getStorename(nameSpace, modelName);
728
842
 
729
843
  switch (relationType) {
730
844
  case 'HAS_ONE':
731
845
  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
- );
846
+ const hasOneIndex = 'byPk';
745
847
 
746
- await this.deleteTraverse(
747
- this.schema.namespaces[nameSpace].relationships[modelName]
748
- .relationTypes,
749
- recordToDelete ? [recordToDelete] : [],
750
- modelName,
751
- nameSpace,
752
- deleteQueue
753
- );
848
+ if (targetNames?.length) {
849
+ // CPK codegen
850
+ const values = targetNames.map(targetName => model[targetName]);
851
+
852
+ if (values.length === 0) break;
853
+
854
+ const recordToDelete = <T>(
855
+ await this.db
856
+ .transaction(storeName, 'readwrite')
857
+ .objectStore(storeName)
858
+ .index(hasOneIndex)
859
+ .get(values)
860
+ );
861
+
862
+ await this.deleteTraverse(
863
+ this.schema.namespaces[nameSpace].relationships[modelName]
864
+ .relationTypes,
865
+ recordToDelete ? [recordToDelete] : [],
866
+ modelName,
867
+ nameSpace,
868
+ deleteQueue
869
+ );
870
+ break;
871
+ } else {
872
+ // PRE-CPK codegen
873
+ let index;
874
+ let values: string[];
875
+
876
+ if (targetName && targetName in model) {
877
+ index = hasOneIndex;
878
+ const value = model[targetName];
879
+ values = [value];
880
+ } else {
881
+ // backwards compatability for older versions of codegen that did not emit targetName for HAS_ONE relations
882
+ // TODO: can we deprecate this? it's been ~2 years since codegen started including targetName for HAS_ONE
883
+ // If we deprecate, we'll need to re-gen the MIPR in __tests__/schema.ts > newSchema
884
+ // otherwise some unit tests will fail
885
+ index = getIndex(
886
+ this.schema.namespaces[nameSpace].relationships[modelName]
887
+ .relationTypes,
888
+ srcModel
889
+ );
890
+ values = this.getIndexKeyValuesFromModel(model);
891
+ }
892
+
893
+ if (!values || !index) break;
894
+
895
+ const recordToDelete = <T>(
896
+ await this.db
897
+ .transaction(storeName, 'readwrite')
898
+ .objectStore(storeName)
899
+ .index(index)
900
+ .get(values)
901
+ );
902
+
903
+ await this.deleteTraverse(
904
+ this.schema.namespaces[nameSpace].relationships[modelName]
905
+ .relationTypes,
906
+ recordToDelete ? [recordToDelete] : [],
907
+ modelName,
908
+ nameSpace,
909
+ deleteQueue
910
+ );
911
+ }
754
912
  }
755
913
  break;
756
914
  case 'HAS_MANY':
757
915
  for await (const model of models) {
916
+ const index =
917
+ // explicit bi-directional @hasMany and @manyToMany
918
+ getIndex(
919
+ this.schema.namespaces[nameSpace].relationships[modelName]
920
+ .relationTypes,
921
+ srcModel
922
+ ) ||
923
+ // uni and/or implicit @hasMany
924
+ getIndexFromAssociation(
925
+ this.schema.namespaces[nameSpace].relationships[modelName]
926
+ .indexes,
927
+ associatedWith
928
+ );
929
+ const keyValues = this.getIndexKeyValuesFromModel(model);
930
+
758
931
  const childrenArray = await this.db
759
932
  .transaction(storeName, 'readwrite')
760
933
  .objectStore(storeName)
761
- .index(index)
762
- .getAll(model['id']);
934
+ .index(index as string)
935
+ .getAll(keyValues);
763
936
 
764
937
  await this.deleteTraverse(
765
938
  this.schema.namespaces[nameSpace].relationships[modelName]
@@ -781,7 +954,7 @@ class IndexedDBAdapter implements Adapter {
781
954
  }
782
955
 
783
956
  deleteQueue.push({
784
- storeName: this.getStorename(nameSpace, srcModel),
957
+ storeName: getStorename(nameSpace, srcModel),
785
958
  items: models.map(record =>
786
959
  this.modelInstanceCreator(
787
960
  this.getModelConstructorByModelName(nameSpace, srcModel),
@@ -820,22 +993,29 @@ class IndexedDBAdapter implements Adapter {
820
993
  const store = txn.store;
821
994
 
822
995
  for (const item of items) {
996
+ const namespaceName = this.namespaceResolver(modelConstructor);
997
+ const modelName = modelConstructor.name;
998
+ const model = this.modelInstanceCreator(modelConstructor, item);
999
+
823
1000
  const connectedModels = traverseModel(
824
- modelConstructor.name,
825
- this.modelInstanceCreator(modelConstructor, item),
826
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
1001
+ modelName,
1002
+ model,
1003
+ this.schema.namespaces[namespaceName],
827
1004
  this.modelInstanceCreator,
828
1005
  this.getModelConstructorByModelName
829
1006
  );
830
1007
 
831
- const { id, _deleted } = item;
832
- const index = store.index('byId');
833
- const key = await index.getKey(id);
1008
+ const keyValues = this.getIndexKeyValuesFromModel(model);
1009
+ const { _deleted } = item;
1010
+
1011
+ const index = store.index('byPk');
1012
+ const key = await index.getKey(keyValues);
834
1013
 
835
1014
  if (!_deleted) {
836
- const { instance } = connectedModels.find(
837
- ({ instance }) => instance.id === id
838
- );
1015
+ const { instance } = connectedModels.find(({ instance }) => {
1016
+ const instanceKeyValues = this.getIndexKeyValuesFromModel(instance);
1017
+ return keysEqual(instanceKeyValues, keyValues);
1018
+ });
839
1019
 
840
1020
  result.push([
841
1021
  <T>(<unknown>instance),
@@ -856,7 +1036,7 @@ class IndexedDBAdapter implements Adapter {
856
1036
  return result;
857
1037
  }
858
1038
 
859
- private async createObjectStoreForModel(
1039
+ private createObjectStoreForModel(
860
1040
  db: idb.IDBPDatabase,
861
1041
  namespaceName: string,
862
1042
  storeName: string,
@@ -866,11 +1046,14 @@ class IndexedDBAdapter implements Adapter {
866
1046
  autoIncrement: true,
867
1047
  });
868
1048
 
869
- const indexes =
870
- this.schema.namespaces[namespaceName].relationships[modelName].indexes;
871
- indexes.forEach(index => store.createIndex(index, index));
1049
+ const { indexes } =
1050
+ this.schema.namespaces[namespaceName].relationships[modelName];
1051
+
1052
+ indexes.forEach(([idxName, keyPath, options]) => {
1053
+ store.createIndex(idxName, keyPath, options);
1054
+ });
872
1055
 
873
- store.createIndex('byId', 'id', { unique: true });
1056
+ return store;
874
1057
  }
875
1058
  }
876
1059