@aws-amplify/datastore 4.1.1 → 4.1.2

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 (26) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +52 -28
  3. package/lib/storage/adapter/AsyncStorageAdapter.js +212 -476
  4. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  5. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  6. package/lib/storage/adapter/IndexedDBAdapter.d.ts +53 -28
  7. package/lib/storage/adapter/IndexedDBAdapter.js +589 -892
  8. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  9. package/lib/storage/adapter/StorageAdapterBase.d.ts +146 -0
  10. package/lib/storage/adapter/StorageAdapterBase.js +479 -0
  11. package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
  12. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +52 -28
  13. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +215 -479
  14. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  15. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  16. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +53 -28
  17. package/lib-esm/storage/adapter/IndexedDBAdapter.js +588 -891
  18. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  19. package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +146 -0
  20. package/lib-esm/storage/adapter/StorageAdapterBase.js +477 -0
  21. package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
  22. package/package.json +6 -6
  23. package/src/storage/adapter/AsyncStorageAdapter.ts +239 -543
  24. package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
  25. package/src/storage/adapter/IndexedDBAdapter.ts +423 -786
  26. package/src/storage/adapter/StorageAdapterBase.ts +639 -0
@@ -1,19 +1,11 @@
1
- import { ConsoleLogger as Logger } from '@aws-amplify/core';
2
1
  import AsyncStorageDatabase from './AsyncStorageDatabase';
3
- import { Adapter } from './index';
4
- import { ModelInstanceCreator } from '../../datastore/datastore';
5
- import { ModelPredicateCreator } from '../../predicates';
6
2
  import {
7
- InternalSchema,
8
- isPredicateObj,
9
3
  ModelInstanceMetadata,
10
4
  ModelPredicate,
11
- NamespaceResolver,
12
5
  OpType,
13
6
  PaginationInput,
14
7
  PersistentModel,
15
8
  PersistentModelConstructor,
16
- PredicateObject,
17
9
  PredicatesGroup,
18
10
  QueryOne,
19
11
  RelationType,
@@ -22,7 +14,6 @@ import {
22
14
  DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR,
23
15
  getIndex,
24
16
  getIndexFromAssociation,
25
- isModelConstructor,
26
17
  traverseModel,
27
18
  validatePredicate,
28
19
  inMemoryPagination,
@@ -30,162 +21,114 @@ import {
30
21
  keysEqual,
31
22
  getStorename,
32
23
  getIndexKeys,
33
- extractPrimaryKeyValues,
34
24
  IDENTIFIER_KEY_SEPARATOR,
35
25
  } from '../../util';
26
+ import { StorageAdapterBase } from './StorageAdapterBase';
36
27
 
37
- const logger = new Logger('DataStore');
38
-
39
- export class AsyncStorageAdapter implements Adapter {
40
- // Non-null assertions (bang operators) added to most properties to make TS happy.
41
- // For now, we can be reasonably sure they're available when they're needed, because
42
- // the adapter is not used directly outside the library boundary.
43
- // TODO: rejigger for DI?
44
- private schema!: InternalSchema;
45
- private namespaceResolver!: NamespaceResolver;
46
- private modelInstanceCreator!: ModelInstanceCreator;
47
- private getModelConstructorByModelName!: (
48
- namsespaceName: NAMESPACES,
49
- modelName: string
50
- ) => PersistentModelConstructor<any>;
51
- private db!: AsyncStorageDatabase;
52
- private initPromise!: Promise<void>;
53
- private resolve!: (value?: any) => void;
54
- private reject!: (value?: any) => void;
55
-
56
- private getStorenameForModel(
57
- modelConstructor: PersistentModelConstructor<any>
58
- ) {
59
- const namespace = this.namespaceResolver(modelConstructor);
60
- const { name: modelName } = modelConstructor;
28
+ export class AsyncStorageAdapter extends StorageAdapterBase {
29
+ protected db!: AsyncStorageDatabase;
30
+
31
+ // no-ops for this adapter
32
+ protected async preSetUpChecks() {}
33
+ protected async preOpCheck() {}
61
34
 
62
- return getStorename(namespace, modelName);
35
+ /**
36
+ * Open AsyncStorage database
37
+ * Create new DB if one doesn't exist
38
+ *
39
+ * Called by `StorageAdapterBase.setUp()`
40
+ *
41
+ * @returns AsyncStorageDatabase instance
42
+ */
43
+ protected async initDb(): Promise<AsyncStorageDatabase> {
44
+ const db = new AsyncStorageDatabase();
45
+ await db.init();
46
+ return db;
63
47
  }
64
48
 
65
- // Retrieves primary key values from a model
66
- private getIndexKeyValuesFromModel<T extends PersistentModel>(
67
- model: T
68
- ): string[] {
69
- const modelConstructor = Object.getPrototypeOf(model)
70
- .constructor as PersistentModelConstructor<T>;
49
+ async clear(): Promise<void> {
50
+ await this.db.clear();
51
+
52
+ this.db = undefined!;
53
+ this.initPromise = undefined!;
54
+ }
55
+
56
+ async batchSave<T extends PersistentModel>(
57
+ modelConstructor: PersistentModelConstructor<any>,
58
+ items: ModelInstanceMetadata[]
59
+ ): Promise<[T, OpType][]> {
60
+ if (items.length === 0) {
61
+ return [];
62
+ }
63
+
64
+ const modelName = modelConstructor.name;
71
65
  const namespaceName = this.namespaceResolver(modelConstructor);
72
- const keys = getIndexKeys(
73
- this.schema.namespaces[namespaceName],
74
- modelConstructor.name
75
- );
66
+ const storeName = getStorename(namespaceName, modelName);
67
+ const keys = getIndexKeys(this.schema.namespaces[namespaceName], modelName);
68
+ const batch: ModelInstanceMetadata[] = [];
69
+
70
+ for (const item of items) {
71
+ const model = this.modelInstanceCreator(modelConstructor, item);
76
72
 
77
- return extractPrimaryKeyValues(model, keys);
73
+ const connectedModels = traverseModel(
74
+ modelName,
75
+ model,
76
+ this.schema.namespaces[namespaceName],
77
+ this.modelInstanceCreator,
78
+ this.getModelConstructorByModelName
79
+ );
80
+
81
+ const keyValuesPath = this.getIndexKeyValuesPath(model);
82
+
83
+ const { instance } = connectedModels.find(({ instance }) => {
84
+ const instanceKeyValuesPath = this.getIndexKeyValuesPath(instance);
85
+ return keysEqual([instanceKeyValuesPath], [keyValuesPath]);
86
+ })!;
87
+
88
+ batch.push(instance);
89
+ }
90
+
91
+ return await this.db.batchSave(storeName, batch, keys);
78
92
  }
79
93
 
80
- // Retrieves concatenated primary key values from a model
81
- private getIndexKeyValuesPath<T extends PersistentModel>(model: T): string {
82
- return this.getIndexKeyValuesFromModel(model).join(
94
+ protected async _get<T>(storeName: string, keyArr: string[]): Promise<T> {
95
+ const itemKeyValuesPath: string = keyArr.join(
83
96
  DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
84
97
  );
85
- }
86
98
 
87
- async setUp(
88
- theSchema: InternalSchema,
89
- namespaceResolver: NamespaceResolver,
90
- modelInstanceCreator: ModelInstanceCreator,
91
- getModelConstructorByModelName: (
92
- namsespaceName: NAMESPACES,
93
- modelName: string
94
- ) => PersistentModelConstructor<any>
95
- ) {
96
- if (!this.initPromise) {
97
- this.initPromise = new Promise((res, rej) => {
98
- this.resolve = res;
99
- this.reject = rej;
100
- });
101
- } else {
102
- await this.initPromise;
103
- return;
104
- }
105
- this.schema = theSchema;
106
- this.namespaceResolver = namespaceResolver;
107
- this.modelInstanceCreator = modelInstanceCreator;
108
- this.getModelConstructorByModelName = getModelConstructorByModelName;
109
- try {
110
- if (!this.db) {
111
- this.db = new AsyncStorageDatabase();
112
- await this.db.init();
113
- this.resolve();
114
- }
115
- } catch (error) {
116
- this.reject(error);
117
- }
99
+ return <T>await this.db.get(itemKeyValuesPath, storeName);
118
100
  }
119
101
 
120
102
  async save<T extends PersistentModel>(
121
103
  model: T,
122
104
  condition?: ModelPredicate<T>
123
105
  ): Promise<[T, OpType.INSERT | OpType.UPDATE][]> {
124
- const modelConstructor = Object.getPrototypeOf(model)
125
- .constructor as PersistentModelConstructor<T>;
126
- const storeName = this.getStorenameForModel(modelConstructor);
127
-
128
- const namespaceName = this.namespaceResolver(modelConstructor);
106
+ const { storeName, connectionStoreNames, modelKeyValues } =
107
+ this.saveMetadata(model);
129
108
 
130
- const connectedModels = traverseModel(
131
- modelConstructor.name,
132
- model,
133
- this.schema.namespaces[namespaceName],
134
- this.modelInstanceCreator,
135
- this.getModelConstructorByModelName
136
- );
109
+ const fromDB = await this._get(storeName, modelKeyValues);
137
110
 
138
- const set = new Set<string>();
139
- const connectionStoreNames = Object.values(connectedModels).map(
140
- ({ modelName, item, instance }) => {
141
- const storeName = getStorename(namespaceName, modelName);
142
- set.add(storeName);
143
- const keys = getIndexKeys(
144
- this.schema.namespaces[namespaceName],
145
- modelName
146
- );
147
- return { storeName, item, instance, keys };
148
- }
149
- );
150
- const keyValuesPath = this.getIndexKeyValuesPath(model);
151
-
152
- const fromDB = await this.db.get(keyValuesPath, storeName);
153
-
154
- if (condition && fromDB) {
155
- const predicates = ModelPredicateCreator.getPredicates(condition);
156
- const { predicates: predicateObjs, type } = predicates!;
157
-
158
- const isValid = validatePredicate(fromDB, type, predicateObjs);
159
-
160
- if (!isValid) {
161
- const msg = 'Conditional update failed';
162
- logger.error(msg, { model: fromDB, condition: predicateObjs });
163
-
164
- throw new Error(msg);
165
- }
166
- }
111
+ this.validateSaveCondition(condition, fromDB);
167
112
 
168
113
  const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
169
-
170
114
  for await (const resItem of connectionStoreNames) {
171
115
  const { storeName, item, instance, keys } = resItem;
172
116
 
173
- /* Find the key values in the item, and concatenate them */
174
117
  const itemKeyValues: string[] = keys.map(key => item[key]);
175
- const itemKeyValuesPath: string = itemKeyValues.join(
176
- DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
177
- );
178
118
 
179
- const fromDB = <T>await this.db.get(itemKeyValuesPath, storeName);
119
+ const fromDB = <T>await this._get(storeName, itemKeyValues);
180
120
  const opType: OpType = fromDB ? OpType.UPDATE : OpType.INSERT;
181
- const modelKeyValues = this.getIndexKeyValuesFromModel(model);
182
121
 
183
- // If item key values and model key values are equal, save to db
184
122
  if (
185
123
  keysEqual(itemKeyValues, modelKeyValues) ||
186
124
  opType === OpType.INSERT
187
125
  ) {
188
- await this.db.save(item, storeName, keys, itemKeyValuesPath);
126
+ await this.db.save(
127
+ item,
128
+ storeName,
129
+ keys,
130
+ itemKeyValues.join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR)
131
+ );
189
132
 
190
133
  result.push([instance, opType]);
191
134
  }
@@ -193,57 +136,24 @@ export class AsyncStorageAdapter implements Adapter {
193
136
  return result;
194
137
  }
195
138
 
196
- private async load<T>(
197
- namespaceName: NAMESPACES,
198
- srcModelName: string,
199
- records: T[]
200
- ): Promise<T[]> {
201
- const namespace = this.schema.namespaces[namespaceName];
202
- const relations = namespace.relationships![srcModelName].relationTypes;
203
- const connectionStoreNames = relations.map(({ modelName }) => {
204
- return getStorename(namespaceName, modelName);
205
- });
206
- const modelConstructor = this.getModelConstructorByModelName(
207
- namespaceName,
208
- srcModelName
209
- );
210
-
211
- if (connectionStoreNames.length === 0) {
212
- return records.map(record =>
213
- this.modelInstanceCreator(modelConstructor, record)
214
- );
215
- }
216
-
217
- return records.map(record =>
218
- this.modelInstanceCreator(modelConstructor, record)
219
- );
220
- }
221
-
222
139
  async query<T extends PersistentModel>(
223
140
  modelConstructor: PersistentModelConstructor<T>,
224
141
  predicate?: ModelPredicate<T>,
225
142
  pagination?: PaginationInput<T>
226
143
  ): Promise<T[]> {
227
- const storeName = this.getStorenameForModel(modelConstructor);
228
- const namespaceName = this.namespaceResolver(
229
- modelConstructor
230
- ) as NAMESPACES;
231
-
232
- const predicates =
233
- predicate && ModelPredicateCreator.getPredicates(predicate);
234
- const keys = getIndexKeys(
235
- this.schema.namespaces[namespaceName],
236
- modelConstructor.name
237
- );
238
- const queryByKey =
239
- predicates && this.keyValueFromPredicate(predicates, keys);
240
-
241
- const hasSort = pagination && pagination.sort;
242
- const hasPagination = pagination && pagination.limit;
144
+ const {
145
+ storeName,
146
+ namespaceName,
147
+ queryByKey,
148
+ predicates,
149
+ hasSort,
150
+ hasPagination,
151
+ } = this.queryMetadata(modelConstructor, predicate, pagination);
243
152
 
244
153
  const records: T[] = (await (async () => {
245
154
  if (queryByKey) {
246
- const record = await this.getByKey(storeName, queryByKey);
155
+ const keyValues = queryByKey.join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
156
+ const record = await this.getByKey(storeName, keyValues);
247
157
  return record ? [record] : [];
248
158
  }
249
159
 
@@ -267,8 +177,7 @@ export class AsyncStorageAdapter implements Adapter {
267
177
  storeName: string,
268
178
  keyValuePath: string
269
179
  ): Promise<T> {
270
- const record = <T>await this.db.get(keyValuePath, storeName);
271
- return record;
180
+ return <T>await this.db.get(keyValuePath, storeName);
272
181
  }
273
182
 
274
183
  private async getAll<T extends PersistentModel>(
@@ -277,31 +186,6 @@ export class AsyncStorageAdapter implements Adapter {
277
186
  return await this.db.getAll(storeName);
278
187
  }
279
188
 
280
- private keyValueFromPredicate<T extends PersistentModel>(
281
- predicates: PredicatesGroup<T>,
282
- keys: string[]
283
- ): string | undefined {
284
- const { predicates: predicateObjs } = predicates;
285
-
286
- if (predicateObjs.length !== keys.length) {
287
- return;
288
- }
289
-
290
- const keyValues = [] as any[];
291
-
292
- for (const key of keys) {
293
- const predicateObj = predicateObjs.find(
294
- p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
295
- ) as PredicateObject<T>;
296
-
297
- predicateObj && keyValues.push(predicateObj.operand);
298
- }
299
-
300
- return keyValues.length === keys.length
301
- ? keyValues.join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR)
302
- : undefined;
303
- }
304
-
305
189
  private async filterOnPredicate<T extends PersistentModel>(
306
190
  storeName: string,
307
191
  predicates: PredicatesGroup<T>
@@ -334,129 +218,7 @@ export class AsyncStorageAdapter implements Adapter {
334
218
  return result && this.modelInstanceCreator(modelConstructor, result);
335
219
  }
336
220
 
337
- async delete<T extends PersistentModel>(
338
- modelOrModelConstructor: T | PersistentModelConstructor<T>,
339
- condition?: ModelPredicate<T>
340
- ): Promise<[T[], T[]]> {
341
- const deleteQueue: { storeName: string; items: T[] }[] = [];
342
-
343
- if (isModelConstructor(modelOrModelConstructor)) {
344
- const modelConstructor =
345
- modelOrModelConstructor as PersistentModelConstructor<T>;
346
- const nameSpace = this.namespaceResolver(modelConstructor) as NAMESPACES;
347
-
348
- // models to be deleted.
349
- const models = await this.query(modelConstructor, condition!);
350
- // TODO: refactor this to use a function like getRelations()
351
- const relations =
352
- this.schema.namespaces[nameSpace].relationships![modelConstructor.name]
353
- .relationTypes;
354
-
355
- if (condition !== undefined) {
356
- await this.deleteTraverse(
357
- relations,
358
- models,
359
- modelConstructor.name,
360
- nameSpace,
361
- deleteQueue
362
- );
363
-
364
- await this.deleteItem(deleteQueue);
365
-
366
- const deletedModels = deleteQueue.reduce(
367
- (acc, { items }) => acc.concat(items),
368
- <T[]>[]
369
- );
370
-
371
- return [models, deletedModels];
372
- } else {
373
- await this.deleteTraverse(
374
- relations,
375
- models,
376
- modelConstructor.name,
377
- nameSpace,
378
- deleteQueue
379
- );
380
-
381
- await this.deleteItem(deleteQueue);
382
-
383
- const deletedModels = deleteQueue.reduce(
384
- (acc, { items }) => acc.concat(items),
385
- <T[]>[]
386
- );
387
-
388
- return [models, deletedModels];
389
- }
390
- } else {
391
- const model = modelOrModelConstructor as T;
392
-
393
- const modelConstructor = Object.getPrototypeOf(model)
394
- .constructor as PersistentModelConstructor<T>;
395
- const nameSpace = this.namespaceResolver(modelConstructor) as NAMESPACES;
396
-
397
- const storeName = this.getStorenameForModel(modelConstructor);
398
-
399
- if (condition) {
400
- const keyValuePath = this.getIndexKeyValuesPath(model);
401
-
402
- const fromDB = await this.db.get(keyValuePath, storeName);
403
-
404
- if (fromDB === undefined) {
405
- const msg = 'Model instance not found in storage';
406
- logger.warn(msg, { model });
407
-
408
- return [[model], []];
409
- }
410
-
411
- const predicates = ModelPredicateCreator.getPredicates(condition);
412
- const { predicates: predicateObjs, type } = predicates!;
413
-
414
- const isValid = validatePredicate(fromDB, type, predicateObjs);
415
- if (!isValid) {
416
- const msg = 'Conditional update failed';
417
- logger.error(msg, { model: fromDB, condition: predicateObjs });
418
-
419
- throw new Error(msg);
420
- }
421
-
422
- const relations =
423
- this.schema.namespaces[nameSpace].relationships![
424
- modelConstructor.name
425
- ].relationTypes;
426
- await this.deleteTraverse(
427
- relations,
428
- [model],
429
- modelConstructor.name,
430
- nameSpace,
431
- deleteQueue
432
- );
433
- } else {
434
- const relations =
435
- this.schema.namespaces[nameSpace].relationships![
436
- modelConstructor.name
437
- ].relationTypes;
438
-
439
- await this.deleteTraverse(
440
- relations,
441
- [model],
442
- modelConstructor.name,
443
- nameSpace,
444
- deleteQueue
445
- );
446
- }
447
-
448
- await this.deleteItem(deleteQueue);
449
-
450
- const deletedModels = deleteQueue.reduce(
451
- (acc, { items }) => acc.concat(items),
452
- <T[]>[]
453
- );
454
-
455
- return [[model], deletedModels];
456
- }
457
- }
458
-
459
- private async deleteItem<T extends PersistentModel>(
221
+ protected async deleteItem<T extends PersistentModel>(
460
222
  deleteQueue?: { storeName: string; items: T[] | IDBValidKey[] }[]
461
223
  ) {
462
224
  for await (const deleteItem of deleteQueue!) {
@@ -474,243 +236,177 @@ export class AsyncStorageAdapter implements Adapter {
474
236
  }
475
237
 
476
238
  /**
477
- * Populates the delete Queue with all the items to delete
478
- * @param relations
479
- * @param models
239
+ * Gets related Has One record for `model`
240
+ *
241
+ * @param model
480
242
  * @param srcModel
481
- * @param nameSpace
482
- * @param deleteQueue
243
+ * @param namespace
244
+ * @param rel
245
+ * @returns
483
246
  */
484
- private async deleteTraverse<T extends PersistentModel>(
485
- relations: RelationType[],
486
- models: T[],
247
+ protected async getHasOneChild<T extends PersistentModel>(
248
+ model: T,
487
249
  srcModel: string,
488
- nameSpace: NAMESPACES,
489
- deleteQueue: { storeName: string; items: T[] }[]
490
- ): Promise<void> {
491
- for await (const rel of relations) {
492
- const {
493
- relationType,
494
- modelName,
495
- targetName,
496
- targetNames,
497
- associatedWith,
498
- } = rel;
499
- const storeName = getStorename(nameSpace, modelName);
500
-
501
- const index: string | undefined =
502
- getIndex(
503
- this.schema.namespaces[nameSpace].relationships![modelName]
504
- .relationTypes,
505
- srcModel
506
- ) ||
507
- // if we were unable to find an index via relationTypes
508
- // i.e. for keyName connections, attempt to find one by the
509
- // associatedWith property
510
- getIndexFromAssociation(
511
- this.schema.namespaces[nameSpace].relationships![modelName].indexes,
512
- rel.associatedWith!
513
- );
250
+ namespace: NAMESPACES,
251
+ rel: RelationType
252
+ ) {
253
+ let hasOneIndex;
254
+ const { modelName, targetNames, associatedWith } = rel;
255
+ const storeName = getStorename(namespace, modelName);
256
+ const index: string | undefined =
257
+ getIndex(
258
+ this.schema.namespaces[namespace].relationships![modelName]
259
+ .relationTypes,
260
+ srcModel
261
+ ) ||
262
+ // if we were unable to find an index via relationTypes
263
+ // i.e. for keyName connections, attempt to find one by the
264
+ // associatedWith property
265
+ getIndexFromAssociation(
266
+ this.schema.namespaces[namespace].relationships![modelName].indexes,
267
+ rel.associatedWith!
268
+ );
514
269
 
515
- switch (relationType) {
516
- case 'HAS_ONE':
517
- for await (const model of models) {
518
- if (targetNames && targetNames?.length) {
519
- let hasOneIndex;
520
-
521
- if (index) {
522
- hasOneIndex = index.split(IDENTIFIER_KEY_SEPARATOR);
523
- } else if (associatedWith) {
524
- if (Array.isArray(associatedWith)) {
525
- hasOneIndex = associatedWith;
526
- } else {
527
- hasOneIndex = [associatedWith];
528
- }
529
- }
530
-
531
- // iterate over targetNames array and see if each key is present in model object
532
- // targetNames here being the keys for the CHILD model
533
- const hasConnectedModelFields = targetNames.every(targetName =>
534
- model.hasOwnProperty(targetName)
535
- );
536
-
537
- // PK / Composite key for the parent model
538
- const keyValuesPath: string = this.getIndexKeyValuesPath(model);
539
-
540
- let values;
541
-
542
- const isUnidirectionalConnection = hasOneIndex === associatedWith;
543
-
544
- if (hasConnectedModelFields && isUnidirectionalConnection) {
545
- // Values will be that of the child model
546
- values = targetNames
547
- .filter(targetName => model[targetName] ?? false)
548
- .map(targetName => model[targetName]) as any;
549
- } else {
550
- // values will be that of the parent model
551
- values = keyValuesPath.split(
552
- DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
553
- );
554
- }
555
-
556
- if (values.length === 0) break;
557
-
558
- const allRecords = await this.db.getAll(storeName);
559
-
560
- let recordToDelete;
561
-
562
- // values === targetNames
563
- if (hasConnectedModelFields) {
564
- /**
565
- * Retrieve record by finding the record where all
566
- * targetNames are present on the connected model.
567
- *
568
- */
569
- // recordToDelete = allRecords.filter(childItem =>
570
- // values.every(value => childItem[value] != null)
571
- // ) as T[];
572
-
573
- recordToDelete = allRecords.filter(childItem =>
574
- hasOneIndex.every(index => values.includes(childItem[index]))
575
- );
576
- } else {
577
- // values === keyValuePath
578
- recordToDelete = allRecords.filter(
579
- childItem => childItem[hasOneIndex] === values
580
- ) as T[];
581
- }
582
-
583
- await this.deleteTraverse<T>(
584
- this.schema.namespaces[nameSpace].relationships![modelName]
585
- .relationTypes,
586
- recordToDelete,
587
- modelName,
588
- nameSpace,
589
- deleteQueue
590
- );
591
- } else {
592
- const hasOneIndex = index || associatedWith;
593
- const hasOneCustomField = targetName! in model;
594
- const keyValuesPath: string = this.getIndexKeyValuesPath(model);
595
- const value = hasOneCustomField
596
- ? model[targetName!]
597
- : keyValuesPath;
598
-
599
- if (!value) break;
600
-
601
- const allRecords = await this.db.getAll(storeName);
602
-
603
- const recordsToDelete = allRecords.filter(
604
- childItem => childItem[hasOneIndex as string] === value
605
- ) as T[];
606
-
607
- // instantiate models before passing to deleteTraverse
608
- // necessary for extracting PK values via getIndexKeyValuesFromModel
609
- const modelsToDelete = recordsToDelete.length
610
- ? await this.load(nameSpace, modelName, recordsToDelete)
611
- : [];
612
-
613
- await this.deleteTraverse<T>(
614
- this.schema.namespaces[nameSpace].relationships![modelName]
615
- .relationTypes,
616
- modelsToDelete,
617
- modelName,
618
- nameSpace,
619
- deleteQueue
620
- );
621
- }
622
- }
623
- break;
624
- case 'HAS_MANY':
625
- for await (const model of models) {
626
- // Key values for the parent model:
627
- const keyValues: string[] = this.getIndexKeyValuesFromModel(model);
628
-
629
- const allRecords = await this.db.getAll(storeName);
630
-
631
- const indices = index!.split(IDENTIFIER_KEY_SEPARATOR);
632
-
633
- const childRecords = allRecords.filter(childItem =>
634
- indices.every(index => keyValues.includes(childItem[index]))
635
- ) as T[];
636
-
637
- // instantiate models before passing to deleteTraverse
638
- // necessary for extracting PK values via getIndexKeyValuesFromModel
639
- const childModels = await this.load(
640
- nameSpace,
641
- modelName,
642
- childRecords
643
- );
644
-
645
- await this.deleteTraverse<T>(
646
- this.schema.namespaces[nameSpace].relationships![modelName]
647
- .relationTypes,
648
- childModels,
649
- modelName,
650
- nameSpace,
651
- deleteQueue
652
- );
653
- }
654
- break;
655
- case 'BELONGS_TO':
656
- // Intentionally blank
657
- break;
658
- default:
659
- throw new Error(`Invalid relationType ${relationType}`);
270
+ if (index) {
271
+ hasOneIndex = index.split(IDENTIFIER_KEY_SEPARATOR);
272
+ } else if (associatedWith) {
273
+ if (Array.isArray(associatedWith)) {
274
+ hasOneIndex = associatedWith;
275
+ } else {
276
+ hasOneIndex = [associatedWith];
660
277
  }
661
278
  }
662
279
 
663
- deleteQueue.push({
664
- storeName: getStorename(nameSpace, srcModel),
665
- items: models.map(record =>
666
- this.modelInstanceCreator(
667
- this.getModelConstructorByModelName(nameSpace, srcModel),
668
- record
669
- )
670
- ),
671
- });
672
- }
280
+ // iterate over targetNames array and see if each key is present in model object
281
+ // targetNames here being the keys for the CHILD model
282
+ const hasConnectedModelFields = targetNames!.every(targetName =>
283
+ model.hasOwnProperty(targetName)
284
+ );
673
285
 
674
- async clear(): Promise<void> {
675
- await this.db.clear();
286
+ // PK / Composite key for the parent model
287
+ const keyValuesPath: string = this.getIndexKeyValuesPath(model);
676
288
 
677
- this.db = undefined!;
678
- this.initPromise = undefined!;
679
- }
289
+ let values;
680
290
 
681
- async batchSave<T extends PersistentModel>(
682
- modelConstructor: PersistentModelConstructor<any>,
683
- items: ModelInstanceMetadata[]
684
- ): Promise<[T, OpType][]> {
685
- const { name: modelName } = modelConstructor;
686
- const namespaceName = this.namespaceResolver(modelConstructor);
687
- const storeName = getStorename(namespaceName, modelName);
688
- const keys = getIndexKeys(this.schema.namespaces[namespaceName], modelName);
689
- const batch: ModelInstanceMetadata[] = [];
291
+ const isUnidirectionalConnection = hasOneIndex === associatedWith;
690
292
 
691
- for (const item of items) {
692
- const model = this.modelInstanceCreator(modelConstructor, item);
293
+ if (hasConnectedModelFields && isUnidirectionalConnection) {
294
+ // Values will be that of the child model
295
+ values = targetNames!
296
+ .filter(targetName => model[targetName] ?? false)
297
+ .map(targetName => model[targetName]);
298
+ } else {
299
+ // values will be that of the parent model
300
+ values = keyValuesPath.split(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
301
+ }
693
302
 
694
- const connectedModels = traverseModel(
695
- modelName,
696
- model,
697
- this.schema.namespaces[namespaceName],
698
- this.modelInstanceCreator,
699
- this.getModelConstructorByModelName
700
- );
303
+ if (values.length === 0) return;
701
304
 
702
- const keyValuesPath = this.getIndexKeyValuesPath(model);
305
+ const allRecords = await this.db.getAll(storeName);
703
306
 
704
- const { instance } = connectedModels.find(({ instance }) => {
705
- const instanceKeyValuesPath = this.getIndexKeyValuesPath(instance);
706
- return keysEqual([instanceKeyValuesPath], [keyValuesPath]);
707
- })!;
307
+ let recordToDelete;
708
308
 
709
- batch.push(instance);
309
+ // values === targetNames
310
+ if (hasConnectedModelFields) {
311
+ /**
312
+ * Retrieve record by finding the record where all
313
+ * targetNames are present on the connected model.
314
+ *
315
+ */
316
+
317
+ recordToDelete = allRecords.find(childItem =>
318
+ hasOneIndex.every(index => values.includes(childItem[index]))
319
+ );
320
+ } else {
321
+ // values === keyValuePath
322
+ recordToDelete = allRecords.find(
323
+ childItem => childItem[hasOneIndex] === values
324
+ ) as T[];
710
325
  }
711
326
 
712
- return await this.db.batchSave(storeName, batch, keys);
327
+ return recordToDelete;
328
+ }
329
+
330
+ /**
331
+ * Backwards compatability for pre-CPK codegen
332
+ * TODO - deprecate this in v6; will need to re-gen MIPR for older unit
333
+ * tests that hit this path
334
+ */
335
+ protected async getHasOneChildLegacy<T extends PersistentModel>(
336
+ model: T,
337
+ srcModel: string,
338
+ namespace: NAMESPACES,
339
+ rel: RelationType
340
+ ) {
341
+ const { modelName, targetName, associatedWith } = rel;
342
+ const storeName = getStorename(namespace, modelName);
343
+ const index: string | undefined =
344
+ getIndex(
345
+ this.schema.namespaces[namespace].relationships![modelName]
346
+ .relationTypes,
347
+ srcModel
348
+ ) ||
349
+ // if we were unable to find an index via relationTypes
350
+ // i.e. for keyName connections, attempt to find one by the
351
+ // associatedWith property
352
+ getIndexFromAssociation(
353
+ this.schema.namespaces[namespace].relationships![modelName].indexes,
354
+ rel.associatedWith!
355
+ );
356
+ const hasOneIndex = index || associatedWith;
357
+ const hasOneCustomField = targetName! in model;
358
+ const keyValuesPath: string = this.getIndexKeyValuesPath(model);
359
+ const value = hasOneCustomField ? model[targetName!] : keyValuesPath;
360
+
361
+ if (!value) return;
362
+
363
+ const allRecords = await this.db.getAll(storeName);
364
+
365
+ const recordToDelete = allRecords.find(
366
+ childItem => childItem[hasOneIndex as string] === value
367
+ ) as T;
368
+
369
+ return recordToDelete;
713
370
  }
371
+
372
+ /**
373
+ * Gets related Has Many records by given `storeName`, `index`, and `keyValues`
374
+ *
375
+ * @param storeName
376
+ * @param index
377
+ * @param keyValues
378
+ * @returns
379
+ */
380
+ protected async getHasManyChildren<T extends PersistentModel>(
381
+ storeName: string,
382
+ index: string,
383
+ keyValues: string[]
384
+ ): Promise<T[]> {
385
+ const allRecords = await this.db.getAll(storeName);
386
+ const indices = index!.split(IDENTIFIER_KEY_SEPARATOR);
387
+
388
+ const childRecords = allRecords.filter(childItem =>
389
+ indices.every(index => keyValues.includes(childItem[index]))
390
+ ) as T[];
391
+
392
+ return childRecords;
393
+ }
394
+
395
+ //#region platform-specific helper methods
396
+
397
+ /**
398
+ * Retrieves concatenated primary key values from a model
399
+ *
400
+ * @param model
401
+ * @returns
402
+ */
403
+ private getIndexKeyValuesPath<T extends PersistentModel>(model: T): string {
404
+ return this.getIndexKeyValuesFromModel(model).join(
405
+ DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
406
+ );
407
+ }
408
+
409
+ //#endregion
714
410
  }
715
411
 
716
412
  export default new AsyncStorageAdapter();