@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
@@ -8,7 +8,11 @@ import {
8
8
  ProducerModelPredicate,
9
9
  SchemaModel,
10
10
  } from '../types';
11
- import { exhaustiveCheck } from '../util';
11
+ import {
12
+ exhaustiveCheck,
13
+ extractPrimaryKeyFieldNames,
14
+ extractPrimaryKeyValues,
15
+ } from '../util';
12
16
 
13
17
  export { ModelSortPredicateCreator } from './sort';
14
18
 
@@ -85,7 +89,7 @@ export class ModelPredicateCreator {
85
89
 
86
90
  // Push the group to the top-level recorder
87
91
  ModelPredicateCreator.predicateGroupsMap
88
- .get(receiver)
92
+ .get(receiver)!
89
93
  .predicates.push(group);
90
94
 
91
95
  return receiver;
@@ -109,7 +113,7 @@ export class ModelPredicateCreator {
109
113
  operand: any
110
114
  ) => {
111
115
  ModelPredicateCreator.predicateGroupsMap
112
- .get(receiver)
116
+ .get(receiver)!
113
117
  .predicates.push({ field, operator, operand });
114
118
  return receiver;
115
119
  };
@@ -147,7 +151,7 @@ export class ModelPredicateCreator {
147
151
  // transforms cb-style predicate into Proxy
148
152
  static createFromExisting<T extends PersistentModel>(
149
153
  modelDefinition: SchemaModel,
150
- existing: ProducerModelPredicate<T>
154
+ existing?: ProducerModelPredicate<T>
151
155
  ) {
152
156
  if (!existing || !modelDefinition) {
153
157
  return undefined;
@@ -158,13 +162,31 @@ export class ModelPredicateCreator {
158
162
  );
159
163
  }
160
164
 
161
- static createForId<T extends PersistentModel>(
165
+ static createForSingleField<T extends PersistentModel>(
162
166
  modelDefinition: SchemaModel,
163
- id: string
167
+ fieldName: string,
168
+ value: string
164
169
  ) {
165
- return ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition).id(
166
- 'eq',
167
- <any>id
168
- );
170
+ return ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition)[
171
+ fieldName
172
+ ](<any>'eq', <any>value);
173
+ }
174
+
175
+ static createForPk<T extends PersistentModel>(
176
+ modelDefinition: SchemaModel,
177
+ model: T
178
+ ) {
179
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
180
+ const keyValues = extractPrimaryKeyValues(model, keyFields);
181
+
182
+ let modelPredicate =
183
+ ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition);
184
+
185
+ keyFields.forEach((field, idx) => {
186
+ const operand = keyValues[idx];
187
+ modelPredicate = modelPredicate[field](<any>'eq', <any>operand);
188
+ });
189
+
190
+ return modelPredicate;
169
191
  }
170
192
  }
@@ -33,6 +33,8 @@ import {
33
33
 
34
34
  const logger = new Logger('DataStore');
35
35
 
36
+ const DEFAULT_PRIMARY_KEY_SEPARATOR = '#';
37
+
36
38
  export class AsyncStorageAdapter implements Adapter {
37
39
  private schema: InternalSchema;
38
40
  private namespaceResolver: NamespaceResolver;
@@ -61,6 +63,56 @@ export class AsyncStorageAdapter implements Adapter {
61
63
  return storeName;
62
64
  }
63
65
 
66
+ // Returns primary keys for a model
67
+ private getIndexKeys(namespaceName: string, modelName: string): string[] {
68
+ const namespace = this.schema.namespaces[namespaceName];
69
+
70
+ const keyPath = namespace?.keys[modelName]?.primaryKey;
71
+
72
+ if (keyPath) {
73
+ return keyPath;
74
+ }
75
+
76
+ return ['id'];
77
+ }
78
+
79
+ // Retrieves concatenated primary key values from a model
80
+ private getIndexKeyValuesPath<T extends PersistentModel>(model: T): string {
81
+ const modelConstructor = Object.getPrototypeOf(model)
82
+ .constructor as PersistentModelConstructor<T>;
83
+ const namespaceName = this.namespaceResolver(modelConstructor);
84
+ const keys = this.getIndexKeys(namespaceName, modelConstructor.name);
85
+
86
+ // Retrieve key values from model
87
+ const keyValues = keys.map(field => model[field]);
88
+
89
+ // Return concatenated key values
90
+ return keyValues.join(DEFAULT_PRIMARY_KEY_SEPARATOR);
91
+ }
92
+
93
+ // Retrieves concatenated primary key values from a model
94
+ private getIndexKeyValues<T extends PersistentModel>(model: T): string[] {
95
+ const modelConstructor = Object.getPrototypeOf(model)
96
+ .constructor as PersistentModelConstructor<T>;
97
+ const namespaceName = this.namespaceResolver(modelConstructor);
98
+ const keys = this.getIndexKeys(namespaceName, modelConstructor.name);
99
+
100
+ // Retrieve key values from model
101
+ return keys.map(field => model[field]);
102
+ }
103
+
104
+ private keysEqual(keysA, keysB): boolean {
105
+ if (keysA.length !== keysB.length) {
106
+ return false;
107
+ }
108
+
109
+ if (keysA.length === 1) {
110
+ return keysA[0] === keysB[0];
111
+ }
112
+
113
+ return keysA.every((key, idx) => key === keysB[idx]);
114
+ }
115
+
64
116
  async setUp(
65
117
  theSchema: InternalSchema,
66
118
  namespaceResolver: NamespaceResolver,
@@ -101,23 +153,29 @@ export class AsyncStorageAdapter implements Adapter {
101
153
  const modelConstructor = Object.getPrototypeOf(model)
102
154
  .constructor as PersistentModelConstructor<T>;
103
155
  const storeName = this.getStorenameForModel(modelConstructor);
156
+
157
+ const namespaceName = this.namespaceResolver(modelConstructor);
158
+
104
159
  const connectedModels = traverseModel(
105
160
  modelConstructor.name,
106
161
  model,
107
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
162
+ this.schema.namespaces[namespaceName],
108
163
  this.modelInstanceCreator,
109
164
  this.getModelConstructorByModelName
110
165
  );
111
- const namespaceName = this.namespaceResolver(modelConstructor);
166
+
112
167
  const set = new Set<string>();
113
168
  const connectionStoreNames = Object.values(connectedModels).map(
114
169
  ({ modelName, item, instance }) => {
115
170
  const storeName = this.getStorename(namespaceName, modelName);
116
171
  set.add(storeName);
117
- return { storeName, item, instance };
172
+ const keys = this.getIndexKeys(namespaceName, modelName);
173
+ return { storeName, item, instance, keys };
118
174
  }
119
175
  );
120
- const fromDB = await this.db.get(model.id, storeName);
176
+ const keyValuesPath = this.getIndexKeyValuesPath(model);
177
+
178
+ const fromDB = await this.db.get(keyValuesPath, storeName);
121
179
 
122
180
  if (condition && fromDB) {
123
181
  const predicates = ModelPredicateCreator.getPredicates(condition);
@@ -136,14 +194,22 @@ export class AsyncStorageAdapter implements Adapter {
136
194
  const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
137
195
 
138
196
  for await (const resItem of connectionStoreNames) {
139
- const { storeName, item, instance } = resItem;
140
- const { id } = item;
197
+ const { storeName, item, instance, keys } = resItem;
141
198
 
142
- const fromDB = <T>await this.db.get(id, storeName);
199
+ /* Find the key values in the item, and concatenate them */
200
+ const itemKeyValues: string[] = keys.map(key => item[key]);
201
+ const itemKeyValuesPath: string = itemKeyValues.join(
202
+ DEFAULT_PRIMARY_KEY_SEPARATOR
203
+ );
204
+
205
+ const fromDB = <T>await this.db.get(itemKeyValuesPath, storeName);
143
206
  const opType: OpType = fromDB ? OpType.UPDATE : OpType.INSERT;
207
+ const modelKeyValues = this.getIndexKeyValues(model);
208
+ const keysEqual = this.keysEqual(itemKeyValues, modelKeyValues);
144
209
 
145
- if (id === model.id || opType === OpType.INSERT) {
146
- await this.db.save(item, storeName);
210
+ // If item key values and model key values are equal, save to db
211
+ if (keysEqual || opType === OpType.INSERT) {
212
+ await this.db.save(item, storeName, keys, itemKeyValuesPath);
147
213
 
148
214
  result.push([instance, opType]);
149
215
  }
@@ -174,7 +240,8 @@ export class AsyncStorageAdapter implements Adapter {
174
240
  }
175
241
 
176
242
  for await (const relation of relations) {
177
- const { fieldName, modelName, targetName, relationType } = relation;
243
+ const { fieldName, modelName, targetName, targetNames, relationType } =
244
+ relation;
178
245
  const storeName = this.getStorename(namespaceName, modelName);
179
246
  const modelConstructor = this.getModelConstructorByModelName(
180
247
  namespaceName,
@@ -184,28 +251,88 @@ export class AsyncStorageAdapter implements Adapter {
184
251
  switch (relationType) {
185
252
  case 'HAS_ONE':
186
253
  for await (const recordItem of records) {
187
- const getByfield = recordItem[targetName] ? targetName : fieldName;
188
- if (!recordItem[getByfield]) break;
254
+ // ASYNC CPK TODO: make this cleaner
255
+ if (targetNames?.length) {
256
+ let getByFields = [];
257
+ let allPresent;
258
+ // iterate through all targetnames to make sure they are all present in the recordItem
259
+ allPresent = targetNames.every(targetName => {
260
+ return recordItem[targetName] != null;
261
+ });
262
+
263
+ if (!allPresent) {
264
+ break;
265
+ }
266
+
267
+ getByFields = targetNames as any;
268
+
269
+ // keys are the key values
270
+ const keys = getByFields
271
+ .map(getByField => recordItem[getByField])
272
+ .join(DEFAULT_PRIMARY_KEY_SEPARATOR);
189
273
 
190
- const connectionRecord = await this.db.get(
191
- recordItem[getByfield],
192
- storeName
193
- );
274
+ const connectionRecord = await this.db.get(
275
+ keys as any,
276
+ storeName
277
+ );
278
+
279
+ recordItem[fieldName] =
280
+ connectionRecord &&
281
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
282
+ } else {
283
+ const getByfield = recordItem[targetName]
284
+ ? targetName
285
+ : fieldName;
286
+ if (!recordItem[getByfield]) break;
287
+
288
+ const key = recordItem[getByfield];
289
+
290
+ const connectionRecord = await this.db.get(key, storeName);
194
291
 
195
- recordItem[fieldName] =
196
- connectionRecord &&
197
- this.modelInstanceCreator(modelConstructor, connectionRecord);
292
+ recordItem[fieldName] =
293
+ connectionRecord &&
294
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
295
+ }
198
296
  }
199
297
 
200
298
  break;
201
299
  case 'BELONGS_TO':
202
300
  for await (const recordItem of records) {
203
- if (recordItem[targetName]) {
301
+ // ASYNC CPK TODO: make this cleaner
302
+ if (targetNames?.length) {
303
+ let allPresent;
304
+ // iterate through all targetnames to make sure they are all present in the recordItem
305
+ allPresent = targetNames.every(targetName => {
306
+ return recordItem[targetName] != null;
307
+ });
308
+
309
+ // If not present, there is not yet a connected record
310
+ if (!allPresent) {
311
+ break;
312
+ }
313
+
314
+ const keys = targetNames
315
+ .map(targetName => recordItem[targetName])
316
+ .join(DEFAULT_PRIMARY_KEY_SEPARATOR);
317
+
318
+ // Retrieve the connected record
204
319
  const connectionRecord = await this.db.get(
205
- recordItem[targetName],
320
+ keys as any,
206
321
  storeName
207
322
  );
208
323
 
324
+ recordItem[fieldName] =
325
+ connectionRecord &&
326
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
327
+
328
+ targetNames?.map(targetName => {
329
+ delete recordItem[targetName];
330
+ });
331
+ } else if (recordItem[targetName as any]) {
332
+ const key = recordItem[targetName];
333
+
334
+ const connectionRecord = await this.db.get(key, storeName);
335
+
209
336
  recordItem[fieldName] =
210
337
  connectionRecord &&
211
338
  this.modelInstanceCreator(modelConstructor, connectionRecord);
@@ -238,13 +365,16 @@ export class AsyncStorageAdapter implements Adapter {
238
365
 
239
366
  const predicates =
240
367
  predicate && ModelPredicateCreator.getPredicates(predicate);
241
- const queryById = predicates && this.idFromPredicate(predicates);
368
+ const keys = this.getIndexKeys(namespaceName, modelConstructor.name);
369
+ const queryByKey =
370
+ predicates && this.keyValueFromPredicate(predicates, keys);
371
+
242
372
  const hasSort = pagination && pagination.sort;
243
373
  const hasPagination = pagination && pagination.limit;
244
374
 
245
375
  const records: T[] = await (async () => {
246
- if (queryById) {
247
- const record = await this.getById(storeName, queryById);
376
+ if (queryByKey) {
377
+ const record = await this.getByKey(storeName, queryByKey);
248
378
  return record ? [record] : [];
249
379
  }
250
380
 
@@ -264,11 +394,11 @@ export class AsyncStorageAdapter implements Adapter {
264
394
  return await this.load(namespaceName, modelConstructor.name, records);
265
395
  }
266
396
 
267
- private async getById<T extends PersistentModel>(
397
+ private async getByKey<T extends PersistentModel>(
268
398
  storeName: string,
269
- id: string
399
+ keyValuePath: string
270
400
  ): Promise<T> {
271
- const record = <T>await this.db.get(id, storeName);
401
+ const record = <T>await this.db.get(keyValuePath, storeName);
272
402
  return record;
273
403
  }
274
404
 
@@ -278,17 +408,29 @@ export class AsyncStorageAdapter implements Adapter {
278
408
  return await this.db.getAll(storeName);
279
409
  }
280
410
 
281
- private idFromPredicate<T extends PersistentModel>(
282
- predicates: PredicatesGroup<T>
283
- ) {
411
+ private keyValueFromPredicate<T extends PersistentModel>(
412
+ predicates: PredicatesGroup<T>,
413
+ keys: string[]
414
+ ): string | undefined {
284
415
  const { predicates: predicateObjs } = predicates;
285
- const idPredicate =
286
- predicateObjs.length === 1 &&
287
- (predicateObjs.find(
288
- p => isPredicateObj(p) && p.field === 'id' && p.operator === 'eq'
289
- ) as PredicateObject<T>);
290
416
 
291
- return idPredicate && idPredicate.operand;
417
+ if (predicateObjs.length !== keys.length) {
418
+ return;
419
+ }
420
+
421
+ const keyValues = [];
422
+
423
+ for (const key of keys) {
424
+ const predicateObj = predicateObjs.find(
425
+ p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
426
+ ) as PredicateObject<T>;
427
+
428
+ predicateObj && keyValues.push(predicateObj.operand);
429
+ }
430
+
431
+ return keyValues.length === keys.length
432
+ ? keyValues.join(DEFAULT_PRIMARY_KEY_SEPARATOR)
433
+ : undefined;
292
434
  }
293
435
 
294
436
  private async filterOnPredicate<T extends PersistentModel>(
@@ -328,6 +470,7 @@ export class AsyncStorageAdapter implements Adapter {
328
470
 
329
471
  return records.slice(start, end);
330
472
  }
473
+
331
474
  return records;
332
475
  }
333
476
 
@@ -337,6 +480,7 @@ export class AsyncStorageAdapter implements Adapter {
337
480
  ): Promise<T | undefined> {
338
481
  const storeName = this.getStorenameForModel(modelConstructor);
339
482
  const result = <T>await this.db.getOne(firstOrLast, storeName);
483
+
340
484
  return result && this.modelInstanceCreator(modelConstructor, result);
341
485
  }
342
486
 
@@ -372,6 +516,7 @@ export class AsyncStorageAdapter implements Adapter {
372
516
  (acc, { items }) => acc.concat(items),
373
517
  <T[]>[]
374
518
  );
519
+
375
520
  return [models, deletedModels];
376
521
  } else {
377
522
  await this.deleteTraverse(
@@ -396,11 +541,14 @@ export class AsyncStorageAdapter implements Adapter {
396
541
 
397
542
  const modelConstructor = Object.getPrototypeOf(model)
398
543
  .constructor as PersistentModelConstructor<T>;
399
- const nameSpace = this.namespaceResolver(modelConstructor);
544
+ const namespaceName = this.namespaceResolver(modelConstructor);
400
545
 
401
546
  const storeName = this.getStorenameForModel(modelConstructor);
547
+
402
548
  if (condition) {
403
- const fromDB = await this.db.get(model.id, storeName);
549
+ const keyValuePath = this.getIndexKeyValuesPath(model);
550
+
551
+ const fromDB = await this.db.get(keyValuePath, storeName);
404
552
 
405
553
  if (fromDB === undefined) {
406
554
  const msg = 'Model instance not found in storage';
@@ -421,25 +569,28 @@ export class AsyncStorageAdapter implements Adapter {
421
569
  }
422
570
 
423
571
  const relations =
424
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
425
- .relationTypes;
572
+ this.schema.namespaces[namespaceName].relationships[
573
+ modelConstructor.name
574
+ ].relationTypes;
575
+
426
576
  await this.deleteTraverse(
427
577
  relations,
428
578
  [model],
429
579
  modelConstructor.name,
430
- nameSpace,
580
+ namespaceName,
431
581
  deleteQueue
432
582
  );
433
583
  } else {
434
584
  const relations =
435
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
436
- .relationTypes;
585
+ this.schema.namespaces[namespaceName].relationships[
586
+ modelConstructor.name
587
+ ].relationTypes;
437
588
 
438
589
  await this.deleteTraverse(
439
590
  relations,
440
591
  [model],
441
592
  modelConstructor.name,
442
- nameSpace,
593
+ namespaceName,
443
594
  deleteQueue
444
595
  );
445
596
  }
@@ -464,8 +615,8 @@ export class AsyncStorageAdapter implements Adapter {
464
615
  for await (const item of items) {
465
616
  if (item) {
466
617
  if (typeof item === 'object') {
467
- const id = item['id'];
468
- await this.db.delete(id, storeName);
618
+ const keyValuesPath: string = this.getIndexKeyValuesPath(item as T);
619
+ await this.db.delete(keyValuesPath, storeName);
469
620
  }
470
621
  }
471
622
  }
@@ -487,10 +638,16 @@ export class AsyncStorageAdapter implements Adapter {
487
638
  deleteQueue: { storeName: string; items: T[] }[]
488
639
  ): Promise<void> {
489
640
  for await (const rel of relations) {
490
- const { relationType, modelName, targetName } = rel;
641
+ const {
642
+ relationType,
643
+ modelName,
644
+ targetName,
645
+ targetNames,
646
+ associatedWith,
647
+ } = rel;
491
648
  const storeName = this.getStorename(nameSpace, modelName);
492
649
 
493
- const index: string =
650
+ const index: any =
494
651
  getIndex(
495
652
  this.schema.namespaces[nameSpace].relationships[modelName]
496
653
  .relationTypes,
@@ -507,35 +664,102 @@ export class AsyncStorageAdapter implements Adapter {
507
664
  switch (relationType) {
508
665
  case 'HAS_ONE':
509
666
  for await (const model of models) {
510
- const hasOneIndex = index || 'byId';
511
-
512
- const hasOneCustomField = targetName in model;
513
- const value = hasOneCustomField ? model[targetName] : model.id;
514
- if (!value) break;
667
+ if (targetNames && targetNames?.length) {
668
+ const hasOneIndex = index || associatedWith;
515
669
 
516
- const allRecords = await this.db.getAll(storeName);
517
- const recordToDelete = allRecords.filter(
518
- childItem => childItem[hasOneIndex] === value
519
- );
670
+ // iterate over targetNames array and see if each item is present in model object
671
+ // targetNames here being the keys for the CHILD model
672
+ const hasConnectedModelFields = targetNames.every(targetName =>
673
+ model.hasOwnProperty(targetName)
674
+ );
520
675
 
521
- await this.deleteTraverse(
522
- this.schema.namespaces[nameSpace].relationships[modelName]
523
- .relationTypes,
524
- recordToDelete,
525
- modelName,
526
- nameSpace,
527
- deleteQueue
528
- );
676
+ // PK / Composite key for the parent model
677
+ const keyValuesPath: string = this.getIndexKeyValuesPath(model);
678
+
679
+ let values;
680
+
681
+ if (hasConnectedModelFields) {
682
+ // Values will be that of the child model
683
+ values = targetNames.map(
684
+ targetName => model[targetName]
685
+ ) as any;
686
+ } else {
687
+ // values will be that of the parent model
688
+ values = keyValuesPath;
689
+ }
690
+
691
+ if (values.length === 0) break;
692
+
693
+ const allRecords = await this.db.getAll(storeName);
694
+
695
+ let recordToDelete;
696
+
697
+ // values === targetNames
698
+ if (hasConnectedModelFields) {
699
+ /**
700
+ * Retrieve record by finding the record where all
701
+ * targetNames are present on the connected model
702
+ */
703
+ recordToDelete = allRecords.filter(childItem =>
704
+ values.every(value => childItem[value] != null)
705
+ ) as T[];
706
+ } else {
707
+ // values === keyValuePath
708
+ recordToDelete = allRecords.filter(
709
+ childItem => childItem[hasOneIndex] === values
710
+ ) as T[];
711
+ }
712
+
713
+ await this.deleteTraverse<T>(
714
+ this.schema.namespaces[nameSpace].relationships[modelName]
715
+ .relationTypes,
716
+ recordToDelete,
717
+ modelName,
718
+ nameSpace,
719
+ deleteQueue
720
+ );
721
+ } else {
722
+ const hasOneIndex = index || associatedWith;
723
+ const hasOneCustomField = targetName in model;
724
+ const keyValuesPath: string = this.getIndexKeyValuesPath(model);
725
+ const value = hasOneCustomField
726
+ ? model[targetName]
727
+ : keyValuesPath;
728
+
729
+ if (!value) break;
730
+
731
+ const allRecords = await this.db.getAll(storeName);
732
+
733
+ const recordToDelete = allRecords.filter(
734
+ childItem => childItem[hasOneIndex] === value
735
+ ) as T[];
736
+
737
+ await this.deleteTraverse<T>(
738
+ this.schema.namespaces[nameSpace].relationships[modelName]
739
+ .relationTypes,
740
+ recordToDelete,
741
+ modelName,
742
+ nameSpace,
743
+ deleteQueue
744
+ );
745
+ }
529
746
  }
530
747
  break;
531
748
  case 'HAS_MANY':
532
749
  for await (const model of models) {
750
+ // Key values for the parent model:
751
+ const keyValues: string[] = this.getIndexKeyValues(model);
752
+
533
753
  const allRecords = await this.db.getAll(storeName);
534
- const childrenArray = allRecords.filter(
535
- childItem => childItem[index] === model.id
536
- );
537
754
 
538
- await this.deleteTraverse(
755
+ // Use constant if we go with this approach
756
+ const indices = index.split('-');
757
+
758
+ const childrenArray = allRecords.filter(childItem =>
759
+ indices.every(index => keyValues.includes(childItem[index]))
760
+ ) as T[];
761
+
762
+ await this.deleteTraverse<T>(
539
763
  this.schema.namespaces[nameSpace].relationships[modelName]
540
764
  .relationTypes,
541
765
  childrenArray,
@@ -579,28 +803,31 @@ export class AsyncStorageAdapter implements Adapter {
579
803
  const { name: modelName } = modelConstructor;
580
804
  const namespaceName = this.namespaceResolver(modelConstructor);
581
805
  const storeName = this.getStorename(namespaceName, modelName);
582
-
806
+ const keys = this.getIndexKeys(namespaceName, modelName);
583
807
  const batch: ModelInstanceMetadata[] = [];
584
808
 
585
809
  for (const item of items) {
586
- const { id } = item;
810
+ const model = this.modelInstanceCreator(modelConstructor, item);
587
811
 
588
812
  const connectedModels = traverseModel(
589
- modelConstructor.name,
590
- this.modelInstanceCreator(modelConstructor, item),
591
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
813
+ modelName,
814
+ model,
815
+ this.schema.namespaces[namespaceName],
592
816
  this.modelInstanceCreator,
593
817
  this.getModelConstructorByModelName
594
818
  );
595
819
 
596
- const { instance } = connectedModels.find(
597
- ({ instance }) => instance.id === id
598
- );
820
+ const keyValuesPath = this.getIndexKeyValuesPath(model);
821
+
822
+ const { instance } = connectedModels.find(({ instance }) => {
823
+ const instanceKeyValuesPath = this.getIndexKeyValuesPath(instance);
824
+ return this.keysEqual([instanceKeyValuesPath], [keyValuesPath]);
825
+ });
599
826
 
600
827
  batch.push(instance);
601
828
  }
602
829
 
603
- return await this.db.batchSave(storeName, batch);
830
+ return await this.db.batchSave(storeName, batch, keys);
604
831
  }
605
832
  }
606
833