@aws-amplify/datastore 3.7.3-webpack5.5 → 3.7.4-custom-pk.80

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 (137) hide show
  1. package/CHANGELOG.md +153 -15
  2. package/README.md +154 -0
  3. package/dist/aws-amplify-datastore.js +4638 -2685
  4. package/dist/aws-amplify-datastore.js.map +1 -1
  5. package/dist/aws-amplify-datastore.min.js +25 -95
  6. package/dist/aws-amplify-datastore.min.js.map +1 -1
  7. package/lib/authModeStrategies/multiAuthStrategy.d.ts +2 -2
  8. package/lib/authModeStrategies/multiAuthStrategy.js +32 -26
  9. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  10. package/lib/datastore/datastore.d.ts +17 -16
  11. package/lib/datastore/datastore.js +249 -78
  12. package/lib/datastore/datastore.js.map +1 -1
  13. package/lib/index.d.ts +3 -19
  14. package/lib/predicates/index.d.ts +3 -2
  15. package/lib/predicates/index.js +12 -2
  16. package/lib/predicates/index.js.map +1 -1
  17. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
  18. package/lib/storage/adapter/AsyncStorageAdapter.js +140 -66
  19. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  20. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
  21. package/lib/storage/adapter/AsyncStorageDatabase.js +57 -27
  22. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  23. package/lib/storage/adapter/InMemoryStore.js +2 -2
  24. package/lib/storage/adapter/InMemoryStore.js.map +1 -1
  25. package/lib/storage/adapter/IndexedDBAdapter.d.ts +7 -2
  26. package/lib/storage/adapter/IndexedDBAdapter.js +454 -252
  27. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  28. package/lib/storage/adapter/index.d.ts +1 -1
  29. package/lib/storage/storage.d.ts +3 -3
  30. package/lib/storage/storage.js +106 -28
  31. package/lib/storage/storage.js.map +1 -1
  32. package/lib/sync/index.d.ts +23 -7
  33. package/lib/sync/index.js +18 -17
  34. package/lib/sync/index.js.map +1 -1
  35. package/lib/sync/merger.d.ts +3 -3
  36. package/lib/sync/merger.js +8 -6
  37. package/lib/sync/merger.js.map +1 -1
  38. package/lib/sync/outbox.d.ts +2 -2
  39. package/lib/sync/outbox.js +12 -9
  40. package/lib/sync/outbox.js.map +1 -1
  41. package/lib/sync/processors/errorMaps.d.ts +17 -0
  42. package/lib/sync/processors/errorMaps.js +125 -0
  43. package/lib/sync/processors/errorMaps.js.map +1 -0
  44. package/lib/sync/processors/mutation.d.ts +22 -4
  45. package/lib/sync/processors/mutation.js +119 -51
  46. package/lib/sync/processors/mutation.js.map +1 -1
  47. package/lib/sync/processors/subscription.d.ts +4 -2
  48. package/lib/sync/processors/subscription.js +76 -41
  49. package/lib/sync/processors/subscription.js.map +1 -1
  50. package/lib/sync/processors/sync.d.ts +4 -2
  51. package/lib/sync/processors/sync.js +62 -27
  52. package/lib/sync/processors/sync.js.map +1 -1
  53. package/lib/sync/utils.d.ts +3 -2
  54. package/lib/sync/utils.js +58 -7
  55. package/lib/sync/utils.js.map +1 -1
  56. package/lib/types.d.ts +90 -34
  57. package/lib/types.js +31 -2
  58. package/lib/types.js.map +1 -1
  59. package/lib/util.d.ts +44 -33
  60. package/lib/util.js +210 -57
  61. package/lib/util.js.map +1 -1
  62. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +2 -2
  63. package/lib-esm/authModeStrategies/multiAuthStrategy.js +32 -26
  64. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  65. package/lib-esm/datastore/datastore.d.ts +17 -16
  66. package/lib-esm/datastore/datastore.js +251 -80
  67. package/lib-esm/datastore/datastore.js.map +1 -1
  68. package/lib-esm/index.d.ts +3 -19
  69. package/lib-esm/predicates/index.d.ts +3 -2
  70. package/lib-esm/predicates/index.js +13 -3
  71. package/lib-esm/predicates/index.js.map +1 -1
  72. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
  73. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +140 -66
  74. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  75. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
  76. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +57 -27
  77. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  78. package/lib-esm/storage/adapter/InMemoryStore.js +2 -2
  79. package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
  80. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +7 -2
  81. package/lib-esm/storage/adapter/IndexedDBAdapter.js +454 -252
  82. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  83. package/lib-esm/storage/adapter/index.d.ts +1 -1
  84. package/lib-esm/storage/storage.d.ts +3 -3
  85. package/lib-esm/storage/storage.js +106 -28
  86. package/lib-esm/storage/storage.js.map +1 -1
  87. package/lib-esm/sync/index.d.ts +23 -7
  88. package/lib-esm/sync/index.js +20 -19
  89. package/lib-esm/sync/index.js.map +1 -1
  90. package/lib-esm/sync/merger.d.ts +3 -3
  91. package/lib-esm/sync/merger.js +8 -6
  92. package/lib-esm/sync/merger.js.map +1 -1
  93. package/lib-esm/sync/outbox.d.ts +2 -2
  94. package/lib-esm/sync/outbox.js +13 -10
  95. package/lib-esm/sync/outbox.js.map +1 -1
  96. package/lib-esm/sync/processors/errorMaps.d.ts +17 -0
  97. package/lib-esm/sync/processors/errorMaps.js +119 -0
  98. package/lib-esm/sync/processors/errorMaps.js.map +1 -0
  99. package/lib-esm/sync/processors/mutation.d.ts +22 -4
  100. package/lib-esm/sync/processors/mutation.js +122 -54
  101. package/lib-esm/sync/processors/mutation.js.map +1 -1
  102. package/lib-esm/sync/processors/subscription.d.ts +4 -2
  103. package/lib-esm/sync/processors/subscription.js +77 -42
  104. package/lib-esm/sync/processors/subscription.js.map +1 -1
  105. package/lib-esm/sync/processors/sync.d.ts +4 -2
  106. package/lib-esm/sync/processors/sync.js +62 -27
  107. package/lib-esm/sync/processors/sync.js.map +1 -1
  108. package/lib-esm/sync/utils.d.ts +3 -2
  109. package/lib-esm/sync/utils.js +58 -8
  110. package/lib-esm/sync/utils.js.map +1 -1
  111. package/lib-esm/types.d.ts +90 -34
  112. package/lib-esm/types.js +29 -3
  113. package/lib-esm/types.js.map +1 -1
  114. package/lib-esm/util.d.ts +44 -33
  115. package/lib-esm/util.js +199 -54
  116. package/lib-esm/util.js.map +1 -1
  117. package/package.json +10 -9
  118. package/src/authModeStrategies/multiAuthStrategy.ts +24 -21
  119. package/src/datastore/datastore.ts +395 -143
  120. package/src/predicates/index.ts +32 -10
  121. package/src/storage/adapter/AsyncStorageAdapter.ts +161 -61
  122. package/src/storage/adapter/AsyncStorageDatabase.ts +62 -25
  123. package/src/storage/adapter/InMemoryStore.ts +2 -2
  124. package/src/storage/adapter/IndexedDBAdapter.ts +345 -101
  125. package/src/storage/adapter/index.ts +1 -1
  126. package/src/storage/storage.ts +85 -24
  127. package/src/sync/index.ts +74 -58
  128. package/src/sync/merger.ts +16 -4
  129. package/src/sync/outbox.ts +22 -8
  130. package/src/sync/processors/errorMaps.ts +93 -0
  131. package/src/sync/processors/mutation.ts +143 -84
  132. package/src/sync/processors/subscription.ts +67 -36
  133. package/src/sync/processors/sync.ts +37 -12
  134. package/src/sync/utils.ts +67 -16
  135. package/src/types.ts +252 -50
  136. package/src/util.ts +241 -42
  137. package/ssr/package.json +1 -1
@@ -29,13 +29,13 @@ import {
29
29
  traverseModel,
30
30
  validatePredicate,
31
31
  sortCompareFunction,
32
+ extractTargetNamesFromSrc,
32
33
  } from '../../util';
33
34
  import { Adapter } from './index';
34
35
 
35
36
  const logger = new Logger('DataStore');
36
37
 
37
38
  const DB_NAME = 'amplify-datastore';
38
-
39
39
  class IndexedDBAdapter implements Adapter {
40
40
  private schema: InternalSchema;
41
41
  private namespaceResolver: NamespaceResolver;
@@ -79,6 +79,45 @@ class IndexedDBAdapter implements Adapter {
79
79
  return storeName;
80
80
  }
81
81
 
82
+ private getNamespaceAndModelFromStorename(storeName: string) {
83
+ const [namespaceName, ...modelNameArr] = storeName.split('_');
84
+ return {
85
+ namespaceName,
86
+ modelName: modelNameArr.join('_'),
87
+ };
88
+ }
89
+
90
+ private getIndexKeyPath(namespaceName: string, modelName: string): string[] {
91
+ const namespace = this.schema.namespaces[namespaceName];
92
+
93
+ const keyPath = namespace?.keys[modelName]?.primaryKey;
94
+
95
+ if (keyPath) {
96
+ return keyPath;
97
+ }
98
+
99
+ return ['id'];
100
+ }
101
+
102
+ private getIndexKeyValues<T extends PersistentModel>(model: T): string[] {
103
+ const modelConstructor = Object.getPrototypeOf(model)
104
+ .constructor as PersistentModelConstructor<T>;
105
+ const namespaceName = this.namespaceResolver(modelConstructor);
106
+
107
+ const keys = this.getIndexKeyPath(namespaceName, modelConstructor.name);
108
+
109
+ const keyValues = keys.map(field => model[field]);
110
+ return keyValues;
111
+ }
112
+
113
+ private keysEqual(keysA, keysB): boolean {
114
+ if (keysA.length !== keysB.length) {
115
+ return false;
116
+ }
117
+
118
+ return keysA.every((key, idx) => key === keysB[idx]);
119
+ }
120
+
82
121
  async setUp(
83
122
  theSchema: InternalSchema,
84
123
  namespaceResolver: NamespaceResolver,
@@ -90,6 +129,7 @@ class IndexedDBAdapter implements Adapter {
90
129
  sessionId?: string
91
130
  ) {
92
131
  await this.checkPrivate();
132
+
93
133
  if (!this.initPromise) {
94
134
  this.initPromise = new Promise((res, rej) => {
95
135
  this.resolve = res;
@@ -108,7 +148,7 @@ class IndexedDBAdapter implements Adapter {
108
148
 
109
149
  try {
110
150
  if (!this.db) {
111
- const VERSION = 2;
151
+ const VERSION = 3;
112
152
  this.db = await idb.openDB(this.dbName, VERSION, {
113
153
  upgrade: async (db, oldVersion, newVersion, txn) => {
114
154
  if (oldVersion === 0) {
@@ -117,24 +157,19 @@ class IndexedDBAdapter implements Adapter {
117
157
 
118
158
  Object.keys(namespace.models).forEach(modelName => {
119
159
  const storeName = this.getStorename(namespaceName, modelName);
120
- const store = db.createObjectStore(storeName, {
121
- autoIncrement: true,
122
- });
123
-
124
- const indexes =
125
- this.schema.namespaces[namespaceName].relationships[
126
- modelName
127
- ].indexes;
128
- indexes.forEach(index => store.createIndex(index, index));
129
-
130
- store.createIndex('byId', 'id', { unique: true });
160
+ this.createObjectStoreForModel(
161
+ db,
162
+ namespaceName,
163
+ storeName,
164
+ modelName
165
+ );
131
166
  });
132
167
  });
133
168
 
134
169
  return;
135
170
  }
136
171
 
137
- if (oldVersion === 1 && newVersion === 2) {
172
+ if ((oldVersion === 1 || oldVersion === 2) && newVersion === 3) {
138
173
  try {
139
174
  for (const storeName of txn.objectStoreNames) {
140
175
  const origStore = txn.objectStore(storeName);
@@ -149,7 +184,26 @@ class IndexedDBAdapter implements Adapter {
149
184
  autoIncrement: true,
150
185
  });
151
186
 
152
- newStore.createIndex('byId', 'id', { unique: true });
187
+ const origIndexNames = Object.values(origStore.indexNames);
188
+ for (const idxName of origIndexNames) {
189
+ if (idxName === 'byId') {
190
+ // don't migrate this index. It'll be replaced with byPk
191
+ continue;
192
+ }
193
+ const idx = origStore.index(idxName);
194
+ const { keyPath, unique } = idx;
195
+
196
+ newStore.createIndex(idxName, keyPath, { unique });
197
+ }
198
+
199
+ const { namespaceName, modelName } =
200
+ this.getNamespaceAndModelFromStorename(storeName);
201
+
202
+ const keyPath = this.getIndexKeyPath(
203
+ namespaceName,
204
+ modelName
205
+ );
206
+ newStore.createIndex('byPk', keyPath, { unique: true });
153
207
 
154
208
  let cursor = await origStore.openCursor();
155
209
  let count = 0;
@@ -168,6 +222,31 @@ class IndexedDBAdapter implements Adapter {
168
222
 
169
223
  logger.debug(`${count} ${storeName} records migrated`);
170
224
  }
225
+
226
+ // add new models created after IndexedDB, but before migration
227
+ // this case may happen when a user has not opened an app for
228
+ // some time and a new model is added during that time
229
+ Object.keys(theSchema.namespaces).forEach(namespaceName => {
230
+ const namespace = theSchema.namespaces[namespaceName];
231
+ const objectStoreNames = new Set(txn.objectStoreNames);
232
+
233
+ Object.keys(namespace.models)
234
+ .map(modelName => {
235
+ return [
236
+ modelName,
237
+ this.getStorename(namespaceName, modelName),
238
+ ];
239
+ })
240
+ .filter(([, storeName]) => !objectStoreNames.has(storeName))
241
+ .forEach(([modelName, storeName]) => {
242
+ this.createObjectStoreForModel(
243
+ db,
244
+ namespaceName,
245
+ storeName,
246
+ modelName
247
+ );
248
+ });
249
+ });
171
250
  } catch (error) {
172
251
  logger.error('Error migrating IndexedDB data', error);
173
252
  txn.abort();
@@ -188,19 +267,19 @@ class IndexedDBAdapter implements Adapter {
188
267
 
189
268
  private async _get<T>(
190
269
  storeOrStoreName: idb.IDBPObjectStore | string,
191
- id: string
270
+ keyArr: any[]
192
271
  ): Promise<T> {
193
272
  let index: idb.IDBPIndex;
194
273
 
195
274
  if (typeof storeOrStoreName === 'string') {
196
275
  const storeName = storeOrStoreName;
197
- index = this.db.transaction(storeName, 'readonly').store.index('byId');
276
+ index = this.db.transaction(storeName, 'readonly').store.index('byPk');
198
277
  } else {
199
278
  const store = storeOrStoreName;
200
- index = store.index('byId');
279
+ index = store.index('byPk');
201
280
  }
202
281
 
203
- const result = await index.get(id);
282
+ const result = await index.get(keyArr);
204
283
 
205
284
  return result;
206
285
  }
@@ -213,30 +292,35 @@ class IndexedDBAdapter implements Adapter {
213
292
  const modelConstructor = Object.getPrototypeOf(model)
214
293
  .constructor as PersistentModelConstructor<T>;
215
294
  const storeName = this.getStorenameForModel(modelConstructor);
295
+ const namespaceName = this.namespaceResolver(modelConstructor);
296
+
216
297
  const connectedModels = traverseModel(
217
298
  modelConstructor.name,
218
299
  model,
219
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
300
+ this.schema.namespaces[namespaceName],
220
301
  this.modelInstanceCreator,
221
302
  this.getModelConstructorByModelName
222
303
  );
223
- const namespaceName = this.namespaceResolver(modelConstructor);
224
304
 
225
305
  const set = new Set<string>();
226
306
  const connectionStoreNames = Object.values(connectedModels).map(
227
307
  ({ modelName, item, instance }) => {
228
308
  const storeName = this.getStorename(namespaceName, modelName);
229
309
  set.add(storeName);
230
- return { storeName, item, instance };
310
+ const keys = this.getIndexKeyPath(namespaceName, modelName);
311
+ return { storeName, item, instance, keys };
231
312
  }
232
313
  );
314
+
233
315
  const tx = this.db.transaction(
234
316
  [storeName, ...Array.from(set.values())],
235
317
  'readwrite'
236
318
  );
237
319
  const store = tx.objectStore(storeName);
238
320
 
239
- const fromDB = await this._get(store, model.id);
321
+ const keyValues = this.getIndexKeyValues(model);
322
+
323
+ const fromDB = await this._get(store, keyValues);
240
324
 
241
325
  if (condition && fromDB) {
242
326
  const predicates = ModelPredicateCreator.getPredicates(condition);
@@ -255,17 +339,24 @@ class IndexedDBAdapter implements Adapter {
255
339
  const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
256
340
 
257
341
  for await (const resItem of connectionStoreNames) {
258
- const { storeName, item, instance } = resItem;
342
+ const { storeName, item, instance, keys } = resItem;
259
343
  const store = tx.objectStore(storeName);
260
- const { id } = item;
261
344
 
262
- const fromDB = <T>await this._get(store, id);
345
+ const itemKeyValues = keys.map(key => {
346
+ const value = item[key];
347
+ return value;
348
+ });
349
+
350
+ const fromDB = <T>await this._get(store, itemKeyValues);
263
351
  const opType: OpType =
264
352
  fromDB === undefined ? OpType.INSERT : OpType.UPDATE;
265
353
 
354
+ const modelKeyValues = this.getIndexKeyValues(model);
355
+ const keysEqual = this.keysEqual(itemKeyValues, modelKeyValues);
356
+
266
357
  // Even if the parent is an INSERT, the child might not be, so we need to get its key
267
- if (id === model.id || opType === OpType.INSERT) {
268
- const key = await store.index('byId').getKey(item.id);
358
+ if (keysEqual || opType === OpType.INSERT) {
359
+ const key = await store.index('byPk').getKey(itemKeyValues);
269
360
  await store.put(item, key);
270
361
 
271
362
  result.push([instance, opType]);
@@ -301,7 +392,8 @@ class IndexedDBAdapter implements Adapter {
301
392
  const tx = this.db.transaction([...connectionStoreNames], 'readonly');
302
393
 
303
394
  for await (const relation of relations) {
304
- const { fieldName, modelName, targetName } = relation;
395
+ // target name, metadata, set by init
396
+ const { fieldName, modelName, targetName, targetNames } = relation;
305
397
  const storeName = this.getStorename(namespaceName, modelName);
306
398
  const store = tx.objectStore(storeName);
307
399
  const modelConstructor = this.getModelConstructorByModelName(
@@ -312,35 +404,91 @@ class IndexedDBAdapter implements Adapter {
312
404
  switch (relation.relationType) {
313
405
  case 'HAS_ONE':
314
406
  for await (const recordItem of records) {
315
- const getByfield = recordItem[targetName] ? targetName : fieldName;
316
- if (!recordItem[getByfield]) break;
407
+ // POST CPK codegen changes:
408
+ if (targetNames?.length) {
409
+ let getByFields = [];
410
+ let allPresent;
411
+ // iterate through all targetnames to make sure they are all present in the recordItem
412
+ allPresent = targetNames.every(targetName => {
413
+ return recordItem[targetName] != null;
414
+ });
317
415
 
318
- const connectionRecord = await this._get(
319
- store,
320
- recordItem[getByfield]
321
- );
416
+ if (!allPresent) {
417
+ break;
418
+ }
322
419
 
323
- recordItem[fieldName] =
324
- connectionRecord &&
325
- this.modelInstanceCreator(modelConstructor, connectionRecord);
326
- }
420
+ getByFields = targetNames as any;
421
+
422
+ // keys are the key values
423
+ const keys = getByFields.map(
424
+ getByField => recordItem[getByField]
425
+ );
426
+
427
+ const connectionRecord = await this._get(store, keys);
428
+
429
+ recordItem[fieldName] =
430
+ connectionRecord &&
431
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
432
+ } else {
433
+ // If single target name, using old codegen
434
+ const getByfield = recordItem[targetName]
435
+ ? targetName
436
+ : fieldName;
437
+
438
+ // We break here, because the recordItem does not have 'team', the `getByField`
439
+ // extract the keys on the related model.
440
+ if (!recordItem[getByfield]) break;
441
+
442
+ const key = [recordItem[getByfield]];
327
443
 
444
+ const connectionRecord = await this._get(store, key);
445
+
446
+ recordItem[fieldName] =
447
+ connectionRecord &&
448
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
449
+ }
450
+ }
328
451
  break;
329
452
  case 'BELONGS_TO':
330
453
  for await (const recordItem of records) {
331
- if (recordItem[targetName]) {
332
- const connectionRecord = await this._get(
333
- store,
334
- recordItem[targetName]
454
+ // POST CPK codegen changes:
455
+ if (targetNames?.length) {
456
+ let allPresent;
457
+ // iterate through all targetnames to make sure they are all present in the recordItem
458
+ allPresent = targetNames.every(targetName => {
459
+ return recordItem[targetName] != null;
460
+ });
461
+
462
+ // If not present, there is not yet a connected record
463
+ if (!allPresent) {
464
+ break;
465
+ }
466
+
467
+ const keys = targetNames.map(
468
+ targetName => recordItem[targetName]
335
469
  );
336
470
 
471
+ // Retrieve the connected record
472
+ const connectionRecord = await this._get(store, keys);
473
+
474
+ recordItem[fieldName] =
475
+ connectionRecord &&
476
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
477
+
478
+ targetNames?.map(targetName => {
479
+ delete recordItem[targetName];
480
+ });
481
+ } else if (recordItem[targetName]) {
482
+ const key = [recordItem[targetName]];
483
+
484
+ const connectionRecord = await this._get(store, key);
485
+
337
486
  recordItem[fieldName] =
338
487
  connectionRecord &&
339
488
  this.modelInstanceCreator(modelConstructor, connectionRecord);
340
489
  delete recordItem[targetName];
341
490
  }
342
491
  }
343
-
344
492
  break;
345
493
  case 'HAS_MANY':
346
494
  // TODO: Lazy loading
@@ -367,13 +515,16 @@ class IndexedDBAdapter implements Adapter {
367
515
 
368
516
  const predicates =
369
517
  predicate && ModelPredicateCreator.getPredicates(predicate);
370
- const queryById = predicates && this.idFromPredicate(predicates);
518
+ const keyPath = this.getIndexKeyPath(namespaceName, modelConstructor.name);
519
+ const queryByKey =
520
+ predicates && this.keyValueFromPredicate(predicates, keyPath);
521
+
371
522
  const hasSort = pagination && pagination.sort;
372
523
  const hasPagination = pagination && pagination.limit;
373
524
 
374
525
  const records: T[] = await (async () => {
375
- if (queryById) {
376
- const record = await this.getById(storeName, queryById);
526
+ if (queryByKey) {
527
+ const record = await this.getByKey(storeName, queryByKey);
377
528
  return record ? [record] : [];
378
529
  }
379
530
 
@@ -397,31 +548,41 @@ class IndexedDBAdapter implements Adapter {
397
548
  return await this.load(namespaceName, modelConstructor.name, records);
398
549
  }
399
550
 
400
- private async getById<T extends PersistentModel>(
551
+ private async getByKey<T extends PersistentModel>(
401
552
  storeName: string,
402
- id: string
553
+ keyValue: string[]
403
554
  ): Promise<T> {
404
- const record = <T>await this._get(storeName, id);
555
+ const record = <T>await this._get(storeName, keyValue);
405
556
  return record;
406
557
  }
407
558
 
408
559
  private async getAll<T extends PersistentModel>(
409
- storeName: string
560
+ storeName: any
410
561
  ): Promise<T[]> {
411
562
  return await this.db.getAll(storeName);
412
563
  }
413
564
 
414
- private idFromPredicate<T extends PersistentModel>(
415
- predicates: PredicatesGroup<T>
416
- ) {
565
+ private keyValueFromPredicate<T extends PersistentModel>(
566
+ predicates: PredicatesGroup<T>,
567
+ keyPath: string[]
568
+ ): string[] | undefined {
417
569
  const { predicates: predicateObjs } = predicates;
418
- const idPredicate =
419
- predicateObjs.length === 1 &&
420
- (predicateObjs.find(
421
- p => isPredicateObj(p) && p.field === 'id' && p.operator === 'eq'
422
- ) as PredicateObject<T>);
423
570
 
424
- return idPredicate && idPredicate.operand;
571
+ if (predicateObjs.length !== keyPath.length) {
572
+ return;
573
+ }
574
+
575
+ const keyValues = [];
576
+
577
+ for (const key of keyPath) {
578
+ const predicateObj = predicateObjs.find(
579
+ p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
580
+ ) as PredicateObject<T>;
581
+
582
+ predicateObj && keyValues.push(predicateObj.operand);
583
+ }
584
+
585
+ return keyValues.length === keyPath.length ? keyValues : undefined;
425
586
  }
426
587
 
427
588
  private async filterOnPredicate<T extends PersistentModel>(
@@ -584,15 +745,16 @@ class IndexedDBAdapter implements Adapter {
584
745
 
585
746
  const modelConstructor = Object.getPrototypeOf(model)
586
747
  .constructor as PersistentModelConstructor<T>;
587
- const nameSpace = this.namespaceResolver(modelConstructor);
748
+ const namespaceName = this.namespaceResolver(modelConstructor);
588
749
 
589
750
  const storeName = this.getStorenameForModel(modelConstructor);
590
751
 
591
752
  if (condition) {
592
753
  const tx = this.db.transaction([storeName], 'readwrite');
593
754
  const store = tx.objectStore(storeName);
755
+ const keyValues = this.getIndexKeyValues(model);
594
756
 
595
- const fromDB = await this._get(store, model.id);
757
+ const fromDB = await this._get(store, keyValues);
596
758
 
597
759
  if (fromDB === undefined) {
598
760
  const msg = 'Model instance not found in storage';
@@ -615,26 +777,28 @@ class IndexedDBAdapter implements Adapter {
615
777
  await tx.done;
616
778
 
617
779
  const relations =
618
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
619
- .relationTypes;
780
+ this.schema.namespaces[namespaceName].relationships[
781
+ modelConstructor.name
782
+ ].relationTypes;
620
783
 
621
784
  await this.deleteTraverse(
622
785
  relations,
623
786
  [model],
624
787
  modelConstructor.name,
625
- nameSpace,
788
+ namespaceName,
626
789
  deleteQueue
627
790
  );
628
791
  } else {
629
792
  const relations =
630
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
631
- .relationTypes;
793
+ this.schema.namespaces[namespaceName].relationships[
794
+ modelConstructor.name
795
+ ].relationTypes;
632
796
 
633
797
  await this.deleteTraverse(
634
798
  relations,
635
799
  [model],
636
800
  modelConstructor.name,
637
- nameSpace,
801
+ namespaceName,
638
802
  deleteQueue
639
803
  );
640
804
  }
@@ -651,7 +815,10 @@ class IndexedDBAdapter implements Adapter {
651
815
  }
652
816
 
653
817
  private async deleteItem<T extends PersistentModel>(
654
- deleteQueue?: { storeName: string; items: T[] | IDBValidKey[] }[]
818
+ deleteQueue?: {
819
+ storeName: string;
820
+ items: T[] | IDBValidKey[];
821
+ }[]
655
822
  ) {
656
823
  const connectionStoreNames = deleteQueue.map(({ storeName }) => {
657
824
  return storeName;
@@ -667,9 +834,11 @@ class IndexedDBAdapter implements Adapter {
667
834
  let key: IDBValidKey;
668
835
 
669
836
  if (typeof item === 'object') {
670
- key = await store.index('byId').getKey(item['id']);
837
+ const keyValues = this.getIndexKeyValues(item as T);
838
+ key = await store.index('byPk').getKey(keyValues);
671
839
  } else {
672
- key = await store.index('byId').getKey(item.toString());
840
+ const itemKey = [item.toString()];
841
+ key = await store.index('byPk').getKey([itemKey]);
673
842
  }
674
843
 
675
844
  if (key !== undefined) {
@@ -688,10 +857,12 @@ class IndexedDBAdapter implements Adapter {
688
857
  deleteQueue: { storeName: string; items: T[] }[]
689
858
  ): Promise<void> {
690
859
  for await (const rel of relations) {
691
- const { relationType, fieldName, modelName, targetName } = rel;
860
+ const { relationType, fieldName, modelName, targetName, targetNames } =
861
+ rel;
862
+
692
863
  const storeName = this.getStorename(nameSpace, modelName);
693
864
 
694
- const index: string =
865
+ const index: string | string[] | undefined =
695
866
  getIndex(
696
867
  this.schema.namespaces[nameSpace].relationships[modelName]
697
868
  .relationTypes,
@@ -705,40 +876,88 @@ class IndexedDBAdapter implements Adapter {
705
876
  rel.associatedWith
706
877
  );
707
878
 
879
+ // TODO: refactor
708
880
  switch (relationType) {
709
881
  case 'HAS_ONE':
710
882
  for await (const model of models) {
711
- const hasOneIndex = index || 'byId';
883
+ const hasOneIndex = index || 'byPk';
884
+
885
+ // CPK
886
+ if (targetNames && targetNames.length > 0) {
887
+ // iterate over targetNames array and see if each item is present in model object
888
+ // targetNames here being the keys for the CHILD model
889
+ const hasConnectedModelFields = targetNames.every(targetName =>
890
+ model.hasOwnProperty(targetName)
891
+ );
892
+
893
+ // PK / Composite key for the PARENT model
894
+ const keyValues = this.getIndexKeyValues(model);
895
+
896
+ let values = [];
897
+
898
+ if (hasConnectedModelFields) {
899
+ // Values will be that of the child model
900
+ values = targetNames.map(
901
+ targetName => model[targetName]
902
+ ) as any;
903
+ } else {
904
+ // values will be that of the parent model
905
+ values = keyValues as any;
906
+ }
712
907
 
713
- const hasOneCustomField = targetName in model;
714
- const value = hasOneCustomField ? model[targetName] : model.id;
715
- if (!value) break;
908
+ if (values.length === 0) break;
716
909
 
717
- const recordToDelete = <T>(
718
- await this.db
910
+ const recordToDelete = <T>await this.db
719
911
  .transaction(storeName, 'readwrite')
720
912
  .objectStore(storeName)
721
- .index(hasOneIndex)
722
- .get(value)
723
- );
913
+ .index(hasOneIndex as string)
914
+ .get(values);
915
+
916
+ await this.deleteTraverse(
917
+ this.schema.namespaces[nameSpace].relationships[modelName]
918
+ .relationTypes,
919
+ recordToDelete ? [recordToDelete] : [],
920
+ modelName,
921
+ nameSpace,
922
+ deleteQueue
923
+ );
924
+ break;
925
+ } else {
926
+ // Backwards compatibility (TODO: combine with above)
927
+ const hasOneIndex = index || 'byPk';
724
928
 
725
- await this.deleteTraverse(
726
- this.schema.namespaces[nameSpace].relationships[modelName]
727
- .relationTypes,
728
- recordToDelete ? [recordToDelete] : [],
729
- modelName,
730
- nameSpace,
731
- deleteQueue
732
- );
929
+ const hasOneCustomField = targetName in model;
930
+ const keyValues = this.getIndexKeyValues(model);
931
+ const value = hasOneCustomField ? [model[targetName]] : keyValues;
932
+
933
+ if (!value) break;
934
+
935
+ const recordToDelete = <T>await this.db
936
+ .transaction(storeName, 'readwrite')
937
+ .objectStore(storeName)
938
+ .index(hasOneIndex as string)
939
+ .get(value);
940
+
941
+ await this.deleteTraverse(
942
+ this.schema.namespaces[nameSpace].relationships[modelName]
943
+ .relationTypes,
944
+ recordToDelete ? [recordToDelete] : [],
945
+ modelName,
946
+ nameSpace,
947
+ deleteQueue
948
+ );
949
+ }
733
950
  }
734
951
  break;
735
952
  case 'HAS_MANY':
736
953
  for await (const model of models) {
954
+ const keyValues = this.getIndexKeyValues(model);
955
+
737
956
  const childrenArray = await this.db
738
957
  .transaction(storeName, 'readwrite')
739
958
  .objectStore(storeName)
740
- .index(index)
741
- .getAll(model['id']);
959
+ .index(index as string)
960
+ .getAll(keyValues);
742
961
 
743
962
  await this.deleteTraverse(
744
963
  this.schema.namespaces[nameSpace].relationships[modelName]
@@ -799,22 +1018,29 @@ class IndexedDBAdapter implements Adapter {
799
1018
  const store = txn.store;
800
1019
 
801
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
+
802
1025
  const connectedModels = traverseModel(
803
- modelConstructor.name,
804
- this.modelInstanceCreator(modelConstructor, item),
805
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
1026
+ modelName,
1027
+ model,
1028
+ this.schema.namespaces[namespaceName],
806
1029
  this.modelInstanceCreator,
807
1030
  this.getModelConstructorByModelName
808
1031
  );
809
1032
 
810
- const { id, _deleted } = item;
811
- const index = store.index('byId');
812
- const key = await index.getKey(id);
1033
+ const keyValues = this.getIndexKeyValues(model);
1034
+ const { _deleted } = item;
1035
+
1036
+ const index = store.index('byPk');
1037
+ const key = await index.getKey(keyValues);
813
1038
 
814
1039
  if (!_deleted) {
815
- const { instance } = connectedModels.find(
816
- ({ instance }) => instance.id === id
817
- );
1040
+ const { instance } = connectedModels.find(({ instance }) => {
1041
+ const instanceKeyValues = this.getIndexKeyValues(instance);
1042
+ return this.keysEqual(instanceKeyValues, keyValues);
1043
+ });
818
1044
 
819
1045
  result.push([
820
1046
  <T>(<unknown>instance),
@@ -834,6 +1060,24 @@ class IndexedDBAdapter implements Adapter {
834
1060
 
835
1061
  return result;
836
1062
  }
1063
+
1064
+ private async createObjectStoreForModel(
1065
+ db: idb.IDBPDatabase,
1066
+ namespaceName: string,
1067
+ storeName: string,
1068
+ modelName: string
1069
+ ) {
1070
+ const store = db.createObjectStore(storeName, {
1071
+ autoIncrement: true,
1072
+ });
1073
+
1074
+ const { indexes } =
1075
+ this.schema.namespaces[namespaceName].relationships[modelName];
1076
+
1077
+ indexes.forEach(([idxName, keyPath, options]) => {
1078
+ store.createIndex(idxName, keyPath, options);
1079
+ });
1080
+ }
837
1081
  }
838
1082
 
839
1083
  export default new IndexedDBAdapter();