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

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