@aws-amplify/datastore 3.12.12 → 3.12.13-ds-allow-applicable-data.6

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 (131) hide show
  1. package/CHANGELOG.md +19 -114
  2. package/dist/aws-amplify-datastore.js +4815 -2572
  3. package/dist/aws-amplify-datastore.js.map +1 -1
  4. package/dist/aws-amplify-datastore.min.js +9 -9
  5. package/dist/aws-amplify-datastore.min.js.map +1 -1
  6. package/lib/authModeStrategies/multiAuthStrategy.d.ts +11 -0
  7. package/lib/authModeStrategies/multiAuthStrategy.js +11 -0
  8. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  9. package/lib/datastore/datastore.d.ts +91 -17
  10. package/lib/datastore/datastore.js +535 -332
  11. package/lib/datastore/datastore.js.map +1 -1
  12. package/lib/index.d.ts +3 -19
  13. package/lib/predicates/index.d.ts +3 -2
  14. package/lib/predicates/index.js +12 -2
  15. package/lib/predicates/index.js.map +1 -1
  16. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  17. package/lib/storage/adapter/AsyncStorageAdapter.js +354 -203
  18. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  19. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  20. package/lib/storage/adapter/AsyncStorageDatabase.js +65 -28
  21. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  22. package/lib/storage/adapter/IndexedDBAdapter.d.ts +5 -4
  23. package/lib/storage/adapter/IndexedDBAdapter.js +389 -267
  24. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  25. package/lib/storage/adapter/index.d.ts +1 -1
  26. package/lib/storage/storage.d.ts +1 -1
  27. package/lib/storage/storage.js +93 -28
  28. package/lib/storage/storage.js.map +1 -1
  29. package/lib/sync/datastoreConnectivity.d.ts +1 -0
  30. package/lib/sync/datastoreConnectivity.js +45 -0
  31. package/lib/sync/datastoreConnectivity.js.map +1 -1
  32. package/lib/sync/index.d.ts +27 -4
  33. package/lib/sync/index.js +354 -210
  34. package/lib/sync/index.js.map +1 -1
  35. package/lib/sync/merger.d.ts +9 -3
  36. package/lib/sync/merger.js +13 -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 +77 -71
  40. package/lib/sync/outbox.js.map +1 -1
  41. package/lib/sync/processors/mutation.d.ts +2 -0
  42. package/lib/sync/processors/mutation.js +256 -200
  43. package/lib/sync/processors/mutation.js.map +1 -1
  44. package/lib/sync/processors/subscription.d.ts +2 -0
  45. package/lib/sync/processors/subscription.js +212 -171
  46. package/lib/sync/processors/subscription.js.map +1 -1
  47. package/lib/sync/processors/sync.d.ts +2 -1
  48. package/lib/sync/processors/sync.js +88 -67
  49. package/lib/sync/processors/sync.js.map +1 -1
  50. package/lib/sync/utils.d.ts +3 -2
  51. package/lib/sync/utils.js +59 -8
  52. package/lib/sync/utils.js.map +1 -1
  53. package/lib/types.d.ts +64 -25
  54. package/lib/types.js +10 -1
  55. package/lib/types.js.map +1 -1
  56. package/lib/util.d.ts +56 -24
  57. package/lib/util.js +334 -170
  58. package/lib/util.js.map +1 -1
  59. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +11 -0
  60. package/lib-esm/authModeStrategies/multiAuthStrategy.js +11 -0
  61. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  62. package/lib-esm/datastore/datastore.d.ts +91 -17
  63. package/lib-esm/datastore/datastore.js +537 -334
  64. package/lib-esm/datastore/datastore.js.map +1 -1
  65. package/lib-esm/index.d.ts +3 -19
  66. package/lib-esm/predicates/index.d.ts +3 -2
  67. package/lib-esm/predicates/index.js +13 -3
  68. package/lib-esm/predicates/index.js.map +1 -1
  69. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  70. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +355 -204
  71. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  72. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  73. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +66 -29
  74. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  75. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +5 -4
  76. package/lib-esm/storage/adapter/IndexedDBAdapter.js +390 -268
  77. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  78. package/lib-esm/storage/adapter/index.d.ts +1 -1
  79. package/lib-esm/storage/storage.d.ts +1 -1
  80. package/lib-esm/storage/storage.js +93 -28
  81. package/lib-esm/storage/storage.js.map +1 -1
  82. package/lib-esm/sync/datastoreConnectivity.d.ts +1 -0
  83. package/lib-esm/sync/datastoreConnectivity.js +45 -0
  84. package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
  85. package/lib-esm/sync/index.d.ts +27 -4
  86. package/lib-esm/sync/index.js +357 -213
  87. package/lib-esm/sync/index.js.map +1 -1
  88. package/lib-esm/sync/merger.d.ts +9 -3
  89. package/lib-esm/sync/merger.js +13 -6
  90. package/lib-esm/sync/merger.js.map +1 -1
  91. package/lib-esm/sync/outbox.d.ts +2 -2
  92. package/lib-esm/sync/outbox.js +78 -72
  93. package/lib-esm/sync/outbox.js.map +1 -1
  94. package/lib-esm/sync/processors/mutation.d.ts +2 -0
  95. package/lib-esm/sync/processors/mutation.js +258 -202
  96. package/lib-esm/sync/processors/mutation.js.map +1 -1
  97. package/lib-esm/sync/processors/subscription.d.ts +2 -0
  98. package/lib-esm/sync/processors/subscription.js +213 -172
  99. package/lib-esm/sync/processors/subscription.js.map +1 -1
  100. package/lib-esm/sync/processors/sync.d.ts +2 -1
  101. package/lib-esm/sync/processors/sync.js +89 -68
  102. package/lib-esm/sync/processors/sync.js.map +1 -1
  103. package/lib-esm/sync/utils.d.ts +3 -2
  104. package/lib-esm/sync/utils.js +60 -10
  105. package/lib-esm/sync/utils.js.map +1 -1
  106. package/lib-esm/types.d.ts +64 -25
  107. package/lib-esm/types.js +9 -2
  108. package/lib-esm/types.js.map +1 -1
  109. package/lib-esm/util.d.ts +56 -24
  110. package/lib-esm/util.js +334 -170
  111. package/lib-esm/util.js.map +1 -1
  112. package/not-tsconfig.json +32 -0
  113. package/package.json +7 -7
  114. package/src/authModeStrategies/multiAuthStrategy.ts +11 -0
  115. package/src/datastore/datastore.ts +672 -391
  116. package/src/predicates/index.ts +32 -10
  117. package/src/storage/adapter/AsyncStorageAdapter.ts +309 -93
  118. package/src/storage/adapter/AsyncStorageDatabase.ts +74 -26
  119. package/src/storage/adapter/IndexedDBAdapter.ts +319 -136
  120. package/src/storage/adapter/index.ts +1 -1
  121. package/src/storage/storage.ts +69 -22
  122. package/src/sync/datastoreConnectivity.ts +6 -0
  123. package/src/sync/index.ts +473 -353
  124. package/src/sync/merger.ts +20 -4
  125. package/src/sync/outbox.ts +22 -9
  126. package/src/sync/processors/mutation.ts +194 -149
  127. package/src/sync/processors/subscription.ts +279 -246
  128. package/src/sync/processors/sync.ts +162 -138
  129. package/src/sync/utils.ts +67 -12
  130. package/src/types.ts +181 -29
  131. package/src/util.ts +415 -176
@@ -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
  }
@@ -22,6 +22,7 @@ import {
22
22
  RelationType,
23
23
  } from '../../types';
24
24
  import {
25
+ DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR,
25
26
  exhaustiveCheck,
26
27
  getIndex,
27
28
  getIndexFromAssociation,
@@ -29,6 +30,11 @@ import {
29
30
  traverseModel,
30
31
  validatePredicate,
31
32
  sortCompareFunction,
33
+ keysEqual,
34
+ getStorename,
35
+ getIndexKeys,
36
+ extractPrimaryKeyValues,
37
+ IDENTIFIER_KEY_SEPARATOR,
32
38
  } from '../../util';
33
39
 
34
40
  const logger = new Logger('DataStore');
@@ -52,13 +58,29 @@ export class AsyncStorageAdapter implements Adapter {
52
58
  const namespace = this.namespaceResolver(modelConstructor);
53
59
  const { name: modelName } = modelConstructor;
54
60
 
55
- return this.getStorename(namespace, modelName);
61
+ return getStorename(namespace, modelName);
56
62
  }
57
63
 
58
- private getStorename(namespace: string, modelName: string) {
59
- const storeName = `${namespace}_${modelName}`;
64
+ // Retrieves primary key values from a model
65
+ private getIndexKeyValuesFromModel<T extends PersistentModel>(
66
+ model: T
67
+ ): string[] {
68
+ const modelConstructor = Object.getPrototypeOf(model)
69
+ .constructor as PersistentModelConstructor<T>;
70
+ const namespaceName = this.namespaceResolver(modelConstructor);
71
+ const keys = getIndexKeys(
72
+ this.schema.namespaces[namespaceName],
73
+ modelConstructor.name
74
+ );
75
+
76
+ return extractPrimaryKeyValues(model, keys);
77
+ }
60
78
 
61
- return storeName;
79
+ // Retrieves concatenated primary key values from a model
80
+ private getIndexKeyValuesPath<T extends PersistentModel>(model: T): string {
81
+ return this.getIndexKeyValuesFromModel(model).join(
82
+ DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
83
+ );
62
84
  }
63
85
 
64
86
  async setUp(
@@ -101,23 +123,32 @@ export class AsyncStorageAdapter implements Adapter {
101
123
  const modelConstructor = Object.getPrototypeOf(model)
102
124
  .constructor as PersistentModelConstructor<T>;
103
125
  const storeName = this.getStorenameForModel(modelConstructor);
126
+
127
+ const namespaceName = this.namespaceResolver(modelConstructor);
128
+
104
129
  const connectedModels = traverseModel(
105
130
  modelConstructor.name,
106
131
  model,
107
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
132
+ this.schema.namespaces[namespaceName],
108
133
  this.modelInstanceCreator,
109
134
  this.getModelConstructorByModelName
110
135
  );
111
- const namespaceName = this.namespaceResolver(modelConstructor);
136
+
112
137
  const set = new Set<string>();
113
138
  const connectionStoreNames = Object.values(connectedModels).map(
114
139
  ({ modelName, item, instance }) => {
115
- const storeName = this.getStorename(namespaceName, modelName);
140
+ const storeName = getStorename(namespaceName, modelName);
116
141
  set.add(storeName);
117
- return { storeName, item, instance };
142
+ const keys = getIndexKeys(
143
+ this.schema.namespaces[namespaceName],
144
+ modelName
145
+ );
146
+ return { storeName, item, instance, keys };
118
147
  }
119
148
  );
120
- const fromDB = await this.db.get(model.id, storeName);
149
+ const keyValuesPath = this.getIndexKeyValuesPath(model);
150
+
151
+ const fromDB = await this.db.get(keyValuesPath, storeName);
121
152
 
122
153
  if (condition && fromDB) {
123
154
  const predicates = ModelPredicateCreator.getPredicates(condition);
@@ -136,14 +167,24 @@ export class AsyncStorageAdapter implements Adapter {
136
167
  const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
137
168
 
138
169
  for await (const resItem of connectionStoreNames) {
139
- const { storeName, item, instance } = resItem;
140
- const { id } = item;
170
+ const { storeName, item, instance, keys } = resItem;
171
+
172
+ /* Find the key values in the item, and concatenate them */
173
+ const itemKeyValues: string[] = keys.map(key => item[key]);
174
+ const itemKeyValuesPath: string = itemKeyValues.join(
175
+ DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
176
+ );
141
177
 
142
- const fromDB = <T>await this.db.get(id, storeName);
178
+ const fromDB = <T>await this.db.get(itemKeyValuesPath, storeName);
143
179
  const opType: OpType = fromDB ? OpType.UPDATE : OpType.INSERT;
180
+ const modelKeyValues = this.getIndexKeyValuesFromModel(model);
144
181
 
145
- if (id === model.id || opType === OpType.INSERT) {
146
- await this.db.save(item, storeName);
182
+ // If item key values and model key values are equal, save to db
183
+ if (
184
+ keysEqual(itemKeyValues, modelKeyValues) ||
185
+ opType === OpType.INSERT
186
+ ) {
187
+ await this.db.save(item, storeName, keys, itemKeyValuesPath);
147
188
 
148
189
  result.push([instance, opType]);
149
190
  }
@@ -160,7 +201,7 @@ export class AsyncStorageAdapter implements Adapter {
160
201
  const namespace = this.schema.namespaces[namespaceName];
161
202
  const relations = namespace.relationships[srcModelName].relationTypes;
162
203
  const connectionStoreNames = relations.map(({ modelName }) => {
163
- return this.getStorename(namespaceName, modelName);
204
+ return getStorename(namespaceName, modelName);
164
205
  });
165
206
  const modelConstructor = this.getModelConstructorByModelName(
166
207
  namespaceName,
@@ -174,8 +215,9 @@ export class AsyncStorageAdapter implements Adapter {
174
215
  }
175
216
 
176
217
  for await (const relation of relations) {
177
- const { fieldName, modelName, targetName, relationType } = relation;
178
- const storeName = this.getStorename(namespaceName, modelName);
218
+ const { fieldName, modelName, targetName, targetNames, relationType } =
219
+ relation;
220
+ const storeName = getStorename(namespaceName, modelName);
179
221
  const modelConstructor = this.getModelConstructorByModelName(
180
222
  namespaceName,
181
223
  modelName
@@ -184,27 +226,81 @@ export class AsyncStorageAdapter implements Adapter {
184
226
  switch (relationType) {
185
227
  case 'HAS_ONE':
186
228
  for await (const recordItem of records) {
187
- const getByfield = recordItem[targetName] ? targetName : fieldName;
188
- if (!recordItem[getByfield]) break;
229
+ // ASYNC CPK TODO: make this cleaner
230
+ if (targetNames?.length) {
231
+ let getByFields = [];
232
+ let allPresent;
233
+ // iterate through all targetnames to make sure they are all present in the recordItem
234
+ allPresent = targetNames.every(targetName => {
235
+ return recordItem[targetName] != null;
236
+ });
189
237
 
190
- const connectionRecord = await this.db.get(
191
- recordItem[getByfield],
192
- storeName
193
- );
238
+ if (!allPresent) {
239
+ break;
240
+ }
241
+
242
+ getByFields = targetNames as any;
243
+
244
+ // keys are the key values
245
+ const keys = getByFields
246
+ .map(getByField => recordItem[getByField])
247
+ .join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
248
+
249
+ const connectionRecord = await this.db.get(keys, storeName);
194
250
 
195
- recordItem[fieldName] =
196
- connectionRecord &&
197
- this.modelInstanceCreator(modelConstructor, connectionRecord);
251
+ recordItem[fieldName] =
252
+ connectionRecord &&
253
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
254
+ } else {
255
+ const getByfield = recordItem[targetName]
256
+ ? targetName
257
+ : fieldName;
258
+ if (!recordItem[getByfield]) break;
259
+
260
+ const key = recordItem[getByfield];
261
+
262
+ const connectionRecord = await this.db.get(key, storeName);
263
+
264
+ recordItem[fieldName] =
265
+ connectionRecord &&
266
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
267
+ }
198
268
  }
199
269
 
200
270
  break;
201
271
  case 'BELONGS_TO':
202
272
  for await (const recordItem of records) {
203
- if (recordItem[targetName]) {
204
- const connectionRecord = await this.db.get(
205
- recordItem[targetName],
206
- storeName
207
- );
273
+ // ASYNC CPK TODO: make this cleaner
274
+ if (targetNames?.length) {
275
+ let allPresent;
276
+ // iterate through all targetnames to make sure they are all present in the recordItem
277
+ allPresent = targetNames.every(targetName => {
278
+ return recordItem[targetName] != null;
279
+ });
280
+
281
+ // If not present, there is not yet a connected record
282
+ if (!allPresent) {
283
+ break;
284
+ }
285
+
286
+ const keys = targetNames
287
+ .map(targetName => recordItem[targetName])
288
+ .join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
289
+
290
+ // Retrieve the connected record
291
+ const connectionRecord = await this.db.get(keys, storeName);
292
+
293
+ recordItem[fieldName] =
294
+ connectionRecord &&
295
+ this.modelInstanceCreator(modelConstructor, connectionRecord);
296
+
297
+ targetNames?.map(targetName => {
298
+ delete recordItem[targetName];
299
+ });
300
+ } else if (recordItem[targetName as any]) {
301
+ const key = recordItem[targetName];
302
+
303
+ const connectionRecord = await this.db.get(key, storeName);
208
304
 
209
305
  recordItem[fieldName] =
210
306
  connectionRecord &&
@@ -238,13 +334,19 @@ export class AsyncStorageAdapter implements Adapter {
238
334
 
239
335
  const predicates =
240
336
  predicate && ModelPredicateCreator.getPredicates(predicate);
241
- const queryById = predicates && this.idFromPredicate(predicates);
337
+ const keys = getIndexKeys(
338
+ this.schema.namespaces[namespaceName],
339
+ modelConstructor.name
340
+ );
341
+ const queryByKey =
342
+ predicates && this.keyValueFromPredicate(predicates, keys);
343
+
242
344
  const hasSort = pagination && pagination.sort;
243
345
  const hasPagination = pagination && pagination.limit;
244
346
 
245
347
  const records: T[] = await (async () => {
246
- if (queryById) {
247
- const record = await this.getById(storeName, queryById);
348
+ if (queryByKey) {
349
+ const record = await this.getByKey(storeName, queryByKey);
248
350
  return record ? [record] : [];
249
351
  }
250
352
 
@@ -264,11 +366,11 @@ export class AsyncStorageAdapter implements Adapter {
264
366
  return await this.load(namespaceName, modelConstructor.name, records);
265
367
  }
266
368
 
267
- private async getById<T extends PersistentModel>(
369
+ private async getByKey<T extends PersistentModel>(
268
370
  storeName: string,
269
- id: string
371
+ keyValuePath: string
270
372
  ): Promise<T> {
271
- const record = <T>await this.db.get(id, storeName);
373
+ const record = <T>await this.db.get(keyValuePath, storeName);
272
374
  return record;
273
375
  }
274
376
 
@@ -278,17 +380,29 @@ export class AsyncStorageAdapter implements Adapter {
278
380
  return await this.db.getAll(storeName);
279
381
  }
280
382
 
281
- private idFromPredicate<T extends PersistentModel>(
282
- predicates: PredicatesGroup<T>
283
- ) {
383
+ private keyValueFromPredicate<T extends PersistentModel>(
384
+ predicates: PredicatesGroup<T>,
385
+ keys: string[]
386
+ ): string | undefined {
284
387
  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
388
 
291
- return idPredicate && idPredicate.operand;
389
+ if (predicateObjs.length !== keys.length) {
390
+ return;
391
+ }
392
+
393
+ const keyValues = [];
394
+
395
+ for (const key of keys) {
396
+ const predicateObj = predicateObjs.find(
397
+ p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
398
+ ) as PredicateObject<T>;
399
+
400
+ predicateObj && keyValues.push(predicateObj.operand);
401
+ }
402
+
403
+ return keyValues.length === keys.length
404
+ ? keyValues.join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR)
405
+ : undefined;
292
406
  }
293
407
 
294
408
  private async filterOnPredicate<T extends PersistentModel>(
@@ -328,6 +442,7 @@ export class AsyncStorageAdapter implements Adapter {
328
442
 
329
443
  return records.slice(start, end);
330
444
  }
445
+
331
446
  return records;
332
447
  }
333
448
 
@@ -337,6 +452,7 @@ export class AsyncStorageAdapter implements Adapter {
337
452
  ): Promise<T | undefined> {
338
453
  const storeName = this.getStorenameForModel(modelConstructor);
339
454
  const result = <T>await this.db.getOne(firstOrLast, storeName);
455
+
340
456
  return result && this.modelInstanceCreator(modelConstructor, result);
341
457
  }
342
458
 
@@ -372,6 +488,7 @@ export class AsyncStorageAdapter implements Adapter {
372
488
  (acc, { items }) => acc.concat(items),
373
489
  <T[]>[]
374
490
  );
491
+
375
492
  return [models, deletedModels];
376
493
  } else {
377
494
  await this.deleteTraverse(
@@ -396,12 +513,14 @@ export class AsyncStorageAdapter implements Adapter {
396
513
 
397
514
  const modelConstructor = Object.getPrototypeOf(model)
398
515
  .constructor as PersistentModelConstructor<T>;
399
- const nameSpace = this.namespaceResolver(modelConstructor);
516
+ const namespaceName = this.namespaceResolver(modelConstructor);
400
517
 
401
518
  const storeName = this.getStorenameForModel(modelConstructor);
402
519
 
403
520
  if (condition) {
404
- const fromDB = await this.db.get(model.id, storeName);
521
+ const keyValuePath = this.getIndexKeyValuesPath(model);
522
+
523
+ const fromDB = await this.db.get(keyValuePath, storeName);
405
524
 
406
525
  if (fromDB === undefined) {
407
526
  const msg = 'Model instance not found in storage';
@@ -422,25 +541,28 @@ export class AsyncStorageAdapter implements Adapter {
422
541
  }
423
542
 
424
543
  const relations =
425
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
426
- .relationTypes;
544
+ this.schema.namespaces[namespaceName].relationships[
545
+ modelConstructor.name
546
+ ].relationTypes;
547
+
427
548
  await this.deleteTraverse(
428
549
  relations,
429
550
  [model],
430
551
  modelConstructor.name,
431
- nameSpace,
552
+ namespaceName,
432
553
  deleteQueue
433
554
  );
434
555
  } else {
435
556
  const relations =
436
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
437
- .relationTypes;
557
+ this.schema.namespaces[namespaceName].relationships[
558
+ modelConstructor.name
559
+ ].relationTypes;
438
560
 
439
561
  await this.deleteTraverse(
440
562
  relations,
441
563
  [model],
442
564
  modelConstructor.name,
443
- nameSpace,
565
+ namespaceName,
444
566
  deleteQueue
445
567
  );
446
568
  }
@@ -465,8 +587,8 @@ export class AsyncStorageAdapter implements Adapter {
465
587
  for await (const item of items) {
466
588
  if (item) {
467
589
  if (typeof item === 'object') {
468
- const id = item['id'];
469
- await this.db.delete(id, storeName);
590
+ const keyValuesPath: string = this.getIndexKeyValuesPath(item as T);
591
+ await this.db.delete(keyValuesPath, storeName);
470
592
  }
471
593
  }
472
594
  }
@@ -488,10 +610,16 @@ export class AsyncStorageAdapter implements Adapter {
488
610
  deleteQueue: { storeName: string; items: T[] }[]
489
611
  ): Promise<void> {
490
612
  for await (const rel of relations) {
491
- const { relationType, modelName, targetName } = rel;
492
- const storeName = this.getStorename(nameSpace, modelName);
493
-
494
- const index: string =
613
+ const {
614
+ relationType,
615
+ modelName,
616
+ targetName,
617
+ targetNames,
618
+ associatedWith,
619
+ } = rel;
620
+ const storeName = getStorename(nameSpace, modelName);
621
+
622
+ const index: string | undefined =
495
623
  getIndex(
496
624
  this.schema.namespaces[nameSpace].relationships[modelName]
497
625
  .relationTypes,
@@ -508,35 +636,120 @@ export class AsyncStorageAdapter implements Adapter {
508
636
  switch (relationType) {
509
637
  case 'HAS_ONE':
510
638
  for await (const model of models) {
511
- const hasOneIndex = index || 'byId';
512
-
513
- const hasOneCustomField = targetName in model;
514
- const value = hasOneCustomField ? model[targetName] : model.id;
515
- if (!value) break;
516
-
517
- const allRecords = await this.db.getAll(storeName);
518
- const recordToDelete = allRecords.filter(
519
- childItem => childItem[hasOneIndex] === value
520
- );
639
+ if (targetNames && targetNames?.length) {
640
+ let hasOneIndex;
641
+
642
+ if (index) {
643
+ hasOneIndex = index.split(IDENTIFIER_KEY_SEPARATOR);
644
+ } else if (associatedWith) {
645
+ if (Array.isArray(associatedWith)) {
646
+ hasOneIndex = associatedWith;
647
+ } else {
648
+ hasOneIndex = [associatedWith];
649
+ }
650
+ }
651
+
652
+ // iterate over targetNames array and see if each key is present in model object
653
+ // targetNames here being the keys for the CHILD model
654
+ const hasConnectedModelFields = targetNames.every(targetName =>
655
+ model.hasOwnProperty(targetName)
656
+ );
521
657
 
522
- await this.deleteTraverse(
523
- this.schema.namespaces[nameSpace].relationships[modelName]
524
- .relationTypes,
525
- recordToDelete,
526
- modelName,
527
- nameSpace,
528
- deleteQueue
529
- );
658
+ // PK / Composite key for the parent model
659
+ const keyValuesPath: string = this.getIndexKeyValuesPath(model);
660
+
661
+ let values;
662
+
663
+ const isUnidirectionalConnection = hasOneIndex === associatedWith;
664
+
665
+ if (hasConnectedModelFields && isUnidirectionalConnection) {
666
+ // Values will be that of the child model
667
+ values = targetNames.map(
668
+ targetName => model[targetName]
669
+ ) as any;
670
+ } else {
671
+ // values will be that of the parent model
672
+ values = keyValuesPath.split(
673
+ DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
674
+ );
675
+ }
676
+
677
+ if (values.length === 0) break;
678
+
679
+ const allRecords = await this.db.getAll(storeName);
680
+
681
+ let recordToDelete;
682
+
683
+ // values === targetNames
684
+ if (hasConnectedModelFields) {
685
+ /**
686
+ * Retrieve record by finding the record where all
687
+ * targetNames are present on the connected model.
688
+ *
689
+ */
690
+ // recordToDelete = allRecords.filter(childItem =>
691
+ // values.every(value => childItem[value] != null)
692
+ // ) as T[];
693
+
694
+ recordToDelete = allRecords.filter(childItem =>
695
+ hasOneIndex.every(index => values.includes(childItem[index]))
696
+ );
697
+ } else {
698
+ // values === keyValuePath
699
+ recordToDelete = allRecords.filter(
700
+ childItem => childItem[hasOneIndex] === values
701
+ ) as T[];
702
+ }
703
+
704
+ await this.deleteTraverse<T>(
705
+ this.schema.namespaces[nameSpace].relationships[modelName]
706
+ .relationTypes,
707
+ recordToDelete,
708
+ modelName,
709
+ nameSpace,
710
+ deleteQueue
711
+ );
712
+ } else {
713
+ const hasOneIndex = index || associatedWith;
714
+ const hasOneCustomField = targetName in model;
715
+ const keyValuesPath: string = this.getIndexKeyValuesPath(model);
716
+ const value = hasOneCustomField
717
+ ? model[targetName]
718
+ : keyValuesPath;
719
+
720
+ if (!value) break;
721
+
722
+ const allRecords = await this.db.getAll(storeName);
723
+
724
+ const recordToDelete = allRecords.filter(
725
+ childItem => childItem[hasOneIndex as string] === value
726
+ ) as T[];
727
+
728
+ await this.deleteTraverse<T>(
729
+ this.schema.namespaces[nameSpace].relationships[modelName]
730
+ .relationTypes,
731
+ recordToDelete,
732
+ modelName,
733
+ nameSpace,
734
+ deleteQueue
735
+ );
736
+ }
530
737
  }
531
738
  break;
532
739
  case 'HAS_MANY':
533
740
  for await (const model of models) {
741
+ // Key values for the parent model:
742
+ const keyValues: string[] = this.getIndexKeyValuesFromModel(model);
743
+
534
744
  const allRecords = await this.db.getAll(storeName);
535
- const childrenArray = allRecords.filter(
536
- childItem => childItem[index] === model.id
537
- );
538
745
 
539
- await this.deleteTraverse(
746
+ const indices = index.split(IDENTIFIER_KEY_SEPARATOR);
747
+
748
+ const childrenArray = allRecords.filter(childItem =>
749
+ indices.every(index => keyValues.includes(childItem[index]))
750
+ ) as T[];
751
+
752
+ await this.deleteTraverse<T>(
540
753
  this.schema.namespaces[nameSpace].relationships[modelName]
541
754
  .relationTypes,
542
755
  childrenArray,
@@ -556,7 +769,7 @@ export class AsyncStorageAdapter implements Adapter {
556
769
  }
557
770
 
558
771
  deleteQueue.push({
559
- storeName: this.getStorename(nameSpace, srcModel),
772
+ storeName: getStorename(nameSpace, srcModel),
560
773
  items: models.map(record =>
561
774
  this.modelInstanceCreator(
562
775
  this.getModelConstructorByModelName(nameSpace, srcModel),
@@ -579,29 +792,32 @@ export class AsyncStorageAdapter implements Adapter {
579
792
  ): Promise<[T, OpType][]> {
580
793
  const { name: modelName } = modelConstructor;
581
794
  const namespaceName = this.namespaceResolver(modelConstructor);
582
- const storeName = this.getStorename(namespaceName, modelName);
583
-
795
+ const storeName = getStorename(namespaceName, modelName);
796
+ const keys = getIndexKeys(this.schema.namespaces[namespaceName], modelName);
584
797
  const batch: ModelInstanceMetadata[] = [];
585
798
 
586
799
  for (const item of items) {
587
- const { id } = item;
800
+ const model = this.modelInstanceCreator(modelConstructor, item);
588
801
 
589
802
  const connectedModels = traverseModel(
590
- modelConstructor.name,
591
- this.modelInstanceCreator(modelConstructor, item),
592
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
803
+ modelName,
804
+ model,
805
+ this.schema.namespaces[namespaceName],
593
806
  this.modelInstanceCreator,
594
807
  this.getModelConstructorByModelName
595
808
  );
596
809
 
597
- const { instance } = connectedModels.find(
598
- ({ instance }) => instance.id === id
599
- );
810
+ const keyValuesPath = this.getIndexKeyValuesPath(model);
811
+
812
+ const { instance } = connectedModels.find(({ instance }) => {
813
+ const instanceKeyValuesPath = this.getIndexKeyValuesPath(instance);
814
+ return keysEqual([instanceKeyValuesPath], [keyValuesPath]);
815
+ });
600
816
 
601
817
  batch.push(instance);
602
818
  }
603
819
 
604
- return await this.db.batchSave(storeName, batch);
820
+ return await this.db.batchSave(storeName, batch, keys);
605
821
  }
606
822
  }
607
823