@aws-amplify/datastore 3.7.3 → 3.7.4-custom-pk.86

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