@aws-amplify/datastore 3.12.12 → 3.12.13-custom-pk.1

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 (105) hide show
  1. package/dist/aws-amplify-datastore.js +1854 -965
  2. package/dist/aws-amplify-datastore.js.map +1 -1
  3. package/dist/aws-amplify-datastore.min.js +7 -7
  4. package/dist/aws-amplify-datastore.min.js.map +1 -1
  5. package/lib/datastore/datastore.d.ts +13 -16
  6. package/lib/datastore/datastore.js +130 -63
  7. package/lib/datastore/datastore.js.map +1 -1
  8. package/lib/index.d.ts +3 -19
  9. package/lib/predicates/index.d.ts +3 -2
  10. package/lib/predicates/index.js +12 -2
  11. package/lib/predicates/index.js.map +1 -1
  12. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  13. package/lib/storage/adapter/AsyncStorageAdapter.js +354 -203
  14. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  15. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  16. package/lib/storage/adapter/AsyncStorageDatabase.js +65 -28
  17. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  18. package/lib/storage/adapter/IndexedDBAdapter.d.ts +5 -4
  19. package/lib/storage/adapter/IndexedDBAdapter.js +389 -267
  20. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  21. package/lib/storage/adapter/index.d.ts +1 -1
  22. package/lib/storage/storage.d.ts +1 -1
  23. package/lib/storage/storage.js +92 -27
  24. package/lib/storage/storage.js.map +1 -1
  25. package/lib/sync/index.d.ts +21 -4
  26. package/lib/sync/index.js +13 -11
  27. package/lib/sync/index.js.map +1 -1
  28. package/lib/sync/merger.d.ts +3 -3
  29. package/lib/sync/merger.js +7 -6
  30. package/lib/sync/merger.js.map +1 -1
  31. package/lib/sync/outbox.d.ts +2 -2
  32. package/lib/sync/outbox.js +11 -9
  33. package/lib/sync/outbox.js.map +1 -1
  34. package/lib/sync/processors/mutation.js +60 -42
  35. package/lib/sync/processors/mutation.js.map +1 -1
  36. package/lib/sync/processors/subscription.js.map +1 -1
  37. package/lib/sync/processors/sync.js.map +1 -1
  38. package/lib/sync/utils.d.ts +3 -2
  39. package/lib/sync/utils.js +61 -8
  40. package/lib/sync/utils.js.map +1 -1
  41. package/lib/types.d.ts +64 -25
  42. package/lib/types.js +10 -1
  43. package/lib/types.js.map +1 -1
  44. package/lib/util.d.ts +56 -24
  45. package/lib/util.js +334 -170
  46. package/lib/util.js.map +1 -1
  47. package/lib-esm/datastore/datastore.d.ts +13 -16
  48. package/lib-esm/datastore/datastore.js +132 -65
  49. package/lib-esm/datastore/datastore.js.map +1 -1
  50. package/lib-esm/index.d.ts +3 -19
  51. package/lib-esm/predicates/index.d.ts +3 -2
  52. package/lib-esm/predicates/index.js +13 -3
  53. package/lib-esm/predicates/index.js.map +1 -1
  54. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  55. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +355 -204
  56. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  57. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  58. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +66 -29
  59. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  60. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +5 -4
  61. package/lib-esm/storage/adapter/IndexedDBAdapter.js +390 -268
  62. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  63. package/lib-esm/storage/adapter/index.d.ts +1 -1
  64. package/lib-esm/storage/storage.d.ts +1 -1
  65. package/lib-esm/storage/storage.js +92 -27
  66. package/lib-esm/storage/storage.js.map +1 -1
  67. package/lib-esm/sync/index.d.ts +21 -4
  68. package/lib-esm/sync/index.js +15 -13
  69. package/lib-esm/sync/index.js.map +1 -1
  70. package/lib-esm/sync/merger.d.ts +3 -3
  71. package/lib-esm/sync/merger.js +7 -6
  72. package/lib-esm/sync/merger.js.map +1 -1
  73. package/lib-esm/sync/outbox.d.ts +2 -2
  74. package/lib-esm/sync/outbox.js +12 -10
  75. package/lib-esm/sync/outbox.js.map +1 -1
  76. package/lib-esm/sync/processors/mutation.js +61 -43
  77. package/lib-esm/sync/processors/mutation.js.map +1 -1
  78. package/lib-esm/sync/processors/subscription.js.map +1 -1
  79. package/lib-esm/sync/processors/sync.js.map +1 -1
  80. package/lib-esm/sync/utils.d.ts +3 -2
  81. package/lib-esm/sync/utils.js +62 -10
  82. package/lib-esm/sync/utils.js.map +1 -1
  83. package/lib-esm/types.d.ts +64 -25
  84. package/lib-esm/types.js +9 -2
  85. package/lib-esm/types.js.map +1 -1
  86. package/lib-esm/util.d.ts +56 -24
  87. package/lib-esm/util.js +334 -170
  88. package/lib-esm/util.js.map +1 -1
  89. package/package.json +7 -7
  90. package/src/datastore/datastore.ts +253 -113
  91. package/src/predicates/index.ts +32 -10
  92. package/src/storage/adapter/AsyncStorageAdapter.ts +309 -93
  93. package/src/storage/adapter/AsyncStorageDatabase.ts +74 -26
  94. package/src/storage/adapter/IndexedDBAdapter.ts +319 -136
  95. package/src/storage/adapter/index.ts +1 -1
  96. package/src/storage/storage.ts +68 -21
  97. package/src/sync/index.ts +41 -26
  98. package/src/sync/merger.ts +14 -4
  99. package/src/sync/outbox.ts +21 -8
  100. package/src/sync/processors/mutation.ts +49 -45
  101. package/src/sync/processors/subscription.ts +0 -1
  102. package/src/sync/processors/sync.ts +1 -3
  103. package/src/sync/utils.ts +69 -12
  104. package/src/types.ts +181 -29
  105. package/src/util.ts +415 -176
@@ -29,7 +29,7 @@ export interface Adapter extends SystemComponent {
29
29
  firstOrLast: QueryOne
30
30
  ): Promise<T | undefined>;
31
31
  batchSave<T extends PersistentModel>(
32
- modelConstructor: PersistentModelConstructor<any>,
32
+ modelConstructor: PersistentModelConstructor<T>,
33
33
  items: ModelInstanceMetadata[]
34
34
  ): Promise<[T, OpType][]>;
35
35
  }
@@ -26,6 +26,7 @@ import {
26
26
  validatePredicate,
27
27
  valuesEqual,
28
28
  } from '../util';
29
+ import { getIdentifierValue } from '../sync/utils';
29
30
  import { Adapter } from './adapter';
30
31
  import getDefaultAdapter from './adapter/getDefaultAdapter';
31
32
 
@@ -188,7 +189,21 @@ class StorageClass implements StorageFacade {
188
189
  condition
189
190
  );
190
191
 
191
- const modelIds = new Set(models.map(({ id }) => id));
192
+ const modelConstructor = isModelConstructor(modelOrModelConstructor)
193
+ ? modelOrModelConstructor
194
+ : (Object.getPrototypeOf(modelOrModelConstructor || {})
195
+ .constructor as PersistentModelConstructor<T>);
196
+ const namespaceName = this.namespaceResolver(modelConstructor);
197
+
198
+ const modelDefinition =
199
+ this.schema.namespaces[namespaceName].models[modelConstructor.name];
200
+
201
+ const modelIds = new Set(
202
+ models.map(model => {
203
+ const modelId = getIdentifierValue(modelDefinition, model);
204
+ return modelId;
205
+ })
206
+ );
192
207
 
193
208
  if (
194
209
  !isModelConstructor(modelOrModelConstructor) &&
@@ -204,7 +219,8 @@ class StorageClass implements StorageFacade {
204
219
  let theCondition: PredicatesGroup<any>;
205
220
 
206
221
  if (!isModelConstructor(modelOrModelConstructor)) {
207
- theCondition = modelIds.has(model.id)
222
+ const modelId = getIdentifierValue(modelDefinition, model);
223
+ theCondition = modelIds.has(modelId)
208
224
  ? ModelPredicateCreator.getPredicates(condition, false)
209
225
  : undefined;
210
226
  }
@@ -337,30 +353,65 @@ class StorageClass implements StorageFacade {
337
353
 
338
354
  // set original values for these fields
339
355
  updatedFields.forEach((field: string) => {
340
- const targetName: any = isTargetNameAssociation(
356
+ const targetNames: any = isTargetNameAssociation(
341
357
  fields[field]?.association
342
358
  );
343
359
 
344
- // if field refers to a belongsTo relation, use the target field instead
345
- const key = targetName || field;
360
+ if (Array.isArray(targetNames)) {
361
+ // if field refers to a belongsTo relation, use the target field instead
362
+
363
+ for (const targetName of targetNames) {
364
+ // check field values by value. Ignore unchanged fields
365
+ if (!valuesEqual(source[targetName], originalElement[targetName])) {
366
+ // if the field was updated to 'undefined', replace with 'null' for compatibility with JSON and GraphQL
367
+
368
+ updatedElement[targetName] =
369
+ originalElement[targetName] === undefined
370
+ ? null
371
+ : originalElement[targetName];
372
+
373
+ for (const fieldSet of compositeKeys) {
374
+ // include all of the fields that comprise the composite key
375
+ if (fieldSet.has(targetName)) {
376
+ for (const compositeField of fieldSet) {
377
+ updatedElement[compositeField] =
378
+ originalElement[compositeField];
379
+ }
380
+ }
381
+ }
382
+ }
383
+ }
384
+ } else {
385
+ // Backwards compatibility pre-CPK
386
+
387
+ // if field refers to a belongsTo relation, use the target field instead
388
+ const key = targetNames || field;
389
+
390
+ // check field values by value. Ignore unchanged fields
391
+ if (!valuesEqual(source[key], originalElement[key])) {
392
+ // if the field was updated to 'undefined', replace with 'null' for compatibility with JSON and GraphQL
346
393
 
347
- // check field values by value. Ignore unchanged fields
348
- if (!valuesEqual(source[key], originalElement[key])) {
349
- // if the field was updated to 'undefined', replace with 'null' for compatibility with JSON and GraphQL
350
- updatedElement[key] =
351
- originalElement[key] === undefined ? null : originalElement[key];
394
+ updatedElement[key] =
395
+ originalElement[key] === undefined ? null : originalElement[key];
352
396
 
353
- for (const fieldSet of compositeKeys) {
354
- // include all of the fields that comprise the composite key
355
- if (fieldSet.has(key)) {
356
- for (const compositeField of fieldSet) {
357
- updatedElement[compositeField] = originalElement[compositeField];
397
+ for (const fieldSet of compositeKeys) {
398
+ // include all of the fields that comprise the composite key
399
+ if (fieldSet.has(key)) {
400
+ for (const compositeField of fieldSet) {
401
+ updatedElement[compositeField] =
402
+ originalElement[compositeField];
403
+ }
358
404
  }
359
405
  }
360
406
  }
361
407
  }
362
408
  });
363
409
 
410
+ // Exit early when there are no changes introduced in the update mutation
411
+ if (Object.keys(updatedElement).length === 0) {
412
+ return null;
413
+ }
414
+
364
415
  // include field(s) from custom PK if one is specified for the model
365
416
  if (primaryKey && primaryKey.length) {
366
417
  for (const pkField of primaryKey) {
@@ -368,10 +419,6 @@ class StorageClass implements StorageFacade {
368
419
  }
369
420
  }
370
421
 
371
- if (Object.keys(updatedElement).length === 0) {
372
- return null;
373
- }
374
-
375
422
  const { id, _version, _lastChangedAt, _deleted } = originalElement;
376
423
 
377
424
  // For update mutations we only want to send fields with changes
@@ -421,7 +468,7 @@ class ExclusiveStorage implements StorageFacade {
421
468
  patchesTuple?: [Patch[], PersistentModel]
422
469
  ): Promise<[T, OpType.INSERT | OpType.UPDATE][]> {
423
470
  return this.runExclusive<[T, OpType.INSERT | OpType.UPDATE][]>(storage =>
424
- storage.save<T>(model, condition, mutator, patchesTuple)
471
+ storage.save(model, condition, mutator, patchesTuple)
425
472
  );
426
473
  }
427
474
 
@@ -489,7 +536,7 @@ class ExclusiveStorage implements StorageFacade {
489
536
  }
490
537
 
491
538
  batchSave<T extends PersistentModel>(
492
- modelConstructor: PersistentModelConstructor<any>,
539
+ modelConstructor: PersistentModelConstructor<T>,
493
540
  items: ModelInstanceMetadata[]
494
541
  ): Promise<[T, OpType][]> {
495
542
  return this.storage.batchSave(modelConstructor, items);
package/src/sync/index.ts CHANGED
@@ -21,6 +21,9 @@ import {
21
21
  TypeConstructorMap,
22
22
  ModelPredicate,
23
23
  AuthModeStrategy,
24
+ ManagedIdentifier,
25
+ OptionallyManagedIdentifier,
26
+ __modelMeta__,
24
27
  AmplifyContext,
25
28
  } from '../types';
26
29
  import { exhaustiveCheck, getNow, SYNC, USER } from '../util';
@@ -32,6 +35,7 @@ import { CONTROL_MSG, SubscriptionProcessor } from './processors/subscription';
32
35
  import { SyncProcessor } from './processors/sync';
33
36
  import {
34
37
  createMutationInstanceFromModelOperation,
38
+ getIdentifierValue,
35
39
  predicateToGraphQLCondition,
36
40
  TransformerMutationType,
37
41
  } from './utils';
@@ -46,25 +50,26 @@ type StartParams = {
46
50
  };
47
51
 
48
52
  export declare class MutationEvent {
49
- constructor(init: ModelInit<MutationEvent>);
50
- static copyOf(
51
- src: MutationEvent,
52
- mutator: (draft: MutableModel<MutationEvent>) => void | MutationEvent
53
- ): MutationEvent;
53
+ readonly [__modelMeta__]: {
54
+ identifier: OptionallyManagedIdentifier<MutationEvent, 'id'>;
55
+ };
54
56
  public readonly id: string;
55
57
  public readonly model: string;
56
58
  public readonly operation: TransformerMutationType;
57
59
  public readonly modelId: string;
58
60
  public readonly condition: string;
59
- public data: string;
61
+ public readonly data: string;
62
+ constructor(init: ModelInit<MutationEvent>);
63
+ static copyOf(
64
+ src: MutationEvent,
65
+ mutator: (draft: MutableModel<MutationEvent>) => void | MutationEvent
66
+ ): MutationEvent;
60
67
  }
61
68
 
62
- declare class ModelMetadata {
63
- constructor(init: ModelInit<ModelMetadata>);
64
- static copyOf(
65
- src: ModelMetadata,
66
- mutator: (draft: MutableModel<ModelMetadata>) => void | ModelMetadata
67
- ): ModelMetadata;
69
+ export declare class ModelMetadata {
70
+ readonly [__modelMeta__]: {
71
+ identifier: ManagedIdentifier<ModelMetadata, 'id'>;
72
+ };
68
73
  public readonly id: string;
69
74
  public readonly namespace: string;
70
75
  public readonly model: string;
@@ -72,6 +77,11 @@ declare class ModelMetadata {
72
77
  public readonly lastSync?: number;
73
78
  public readonly lastFullSync?: number;
74
79
  public readonly lastSyncPredicate?: null | string;
80
+ constructor(init: ModelInit<ModelMetadata>);
81
+ static copyOf(
82
+ src: ModelMetadata,
83
+ mutator: (draft: MutableModel<ModelMetadata>) => void | ModelMetadata
84
+ ): ModelMetadata;
75
85
  }
76
86
 
77
87
  export enum ControlMessage {
@@ -123,7 +133,7 @@ export class SyncEngine {
123
133
  ) {
124
134
  const MutationEvent = this.modelClasses[
125
135
  'MutationEvent'
126
- ] as PersistentModelConstructor<any>;
136
+ ] as PersistentModelConstructor<MutationEvent>;
127
137
 
128
138
  this.outbox = new MutationEventOutbox(
129
139
  this.schema,
@@ -296,7 +306,7 @@ export class SyncEngine {
296
306
  );
297
307
 
298
308
  this.storage.runExclusive(storage =>
299
- this.modelMerger.merge(storage, model)
309
+ this.modelMerger.merge(storage, model, modelDefinition)
300
310
  );
301
311
 
302
312
  observer.next({
@@ -333,7 +343,7 @@ export class SyncEngine {
333
343
  );
334
344
 
335
345
  this.storage.runExclusive(storage =>
336
- this.modelMerger.merge(storage, model)
346
+ this.modelMerger.merge(storage, model, modelDefinition)
337
347
  );
338
348
  }
339
349
  )
@@ -362,7 +372,6 @@ export class SyncEngine {
362
372
  .observe(null, null, ownSymbol)
363
373
  .filter(({ model }) => {
364
374
  const modelDefinition = this.getModelDefinition(model);
365
-
366
375
  return modelDefinition.syncable === true;
367
376
  })
368
377
  .subscribe({
@@ -372,7 +381,11 @@ export class SyncEngine {
372
381
  const MutationEventConstructor = this.modelClasses[
373
382
  'MutationEvent'
374
383
  ] as PersistentModelConstructor<MutationEvent>;
375
- const graphQLCondition = predicateToGraphQLCondition(condition);
384
+ const modelDefinition = this.getModelDefinition(model);
385
+ const graphQLCondition = predicateToGraphQLCondition(
386
+ condition,
387
+ modelDefinition
388
+ );
376
389
  const mutationEvent = createMutationInstanceFromModelOperation(
377
390
  namespace.relationships,
378
391
  this.getModelDefinition(model),
@@ -537,7 +550,9 @@ export class SyncEngine {
537
550
 
538
551
  const oneByOne: ModelInstanceMetadata[] = [];
539
552
  const page = items.filter(item => {
540
- if (!idsInOutbox.has(item.id)) {
553
+ const itemId = getIdentifierValue(modelDefinition, item);
554
+
555
+ if (!idsInOutbox.has(itemId)) {
541
556
  return true;
542
557
  }
543
558
 
@@ -550,7 +565,8 @@ export class SyncEngine {
550
565
  for (const item of oneByOne) {
551
566
  const opType = await this.modelMerger.merge(
552
567
  storage,
553
- item
568
+ item,
569
+ modelDefinition
554
570
  );
555
571
 
556
572
  if (opType !== undefined) {
@@ -562,7 +578,8 @@ export class SyncEngine {
562
578
  ...(await this.modelMerger.mergePage(
563
579
  storage,
564
580
  modelConstructor,
565
- page
581
+ page,
582
+ modelDefinition
566
583
  ))
567
584
  );
568
585
 
@@ -608,7 +625,7 @@ export class SyncEngine {
608
625
 
609
626
  modelMetadata = (
610
627
  this.modelClasses
611
- .ModelMetadata as PersistentModelConstructor<any>
628
+ .ModelMetadata as PersistentModelConstructor<ModelMetadata>
612
629
  ).copyOf(modelMetadata, draft => {
613
630
  draft.lastSync = startedAt;
614
631
  draft.lastFullSync = isFullSync
@@ -709,7 +726,7 @@ export class SyncEngine {
709
726
 
710
727
  private async setupModels(params: StartParams) {
711
728
  const { fullSyncInterval } = params;
712
- const ModelMetadata = this.modelClasses
729
+ const ModelMetadataConstructor = this.modelClasses
713
730
  .ModelMetadata as PersistentModelConstructor<ModelMetadata>;
714
731
 
715
732
  const models: [string, SchemaModel][] = [];
@@ -741,7 +758,7 @@ export class SyncEngine {
741
758
 
742
759
  if (modelMetadata === undefined) {
743
760
  [[savedModel]] = await this.storage.save(
744
- this.modelInstanceCreator(ModelMetadata, {
761
+ this.modelInstanceCreator(ModelMetadataConstructor, {
745
762
  model: model.name,
746
763
  namespace,
747
764
  lastSync: null,
@@ -759,9 +776,7 @@ export class SyncEngine {
759
776
  const syncPredicateUpdated = prevSyncPredicate !== lastSyncPredicate;
760
777
 
761
778
  [[savedModel]] = await this.storage.save(
762
- (
763
- this.modelClasses.ModelMetadata as PersistentModelConstructor<any>
764
- ).copyOf(modelMetadata, draft => {
779
+ ModelMetadataConstructor.copyOf(modelMetadata, draft => {
765
780
  draft.fullSyncInterval = fullSyncInterval;
766
781
  // perform a base sync if the syncPredicate changed in between calls to DataStore.start
767
782
  // ensures that the local store contains all the data specified by the syncExpression
@@ -3,8 +3,10 @@ import {
3
3
  ModelInstanceMetadata,
4
4
  OpType,
5
5
  PersistentModelConstructor,
6
+ SchemaModel,
6
7
  } from '../types';
7
8
  import { MutationEventOutbox } from './outbox';
9
+ import { getIdentifierValue } from './utils';
8
10
 
9
11
  // https://github.com/aws-amplify/amplify-js/blob/datastore-docs/packages/datastore/docs/sync-engine.md#merger
10
12
  class ModelMerger {
@@ -15,10 +17,15 @@ class ModelMerger {
15
17
 
16
18
  public async merge<T extends ModelInstanceMetadata>(
17
19
  storage: Storage,
18
- model: T
20
+ model: T,
21
+ modelDefinition: SchemaModel
19
22
  ): Promise<OpType> {
20
23
  let result: OpType;
21
- const mutationsForModel = await this.outbox.getForModel(storage, model);
24
+ const mutationsForModel = await this.outbox.getForModel(
25
+ storage,
26
+ model,
27
+ modelDefinition
28
+ );
22
29
 
23
30
  const isDelete = model._deleted;
24
31
 
@@ -37,13 +44,16 @@ class ModelMerger {
37
44
  public async mergePage(
38
45
  storage: Storage,
39
46
  modelConstructor: PersistentModelConstructor<any>,
40
- items: ModelInstanceMetadata[]
47
+ items: ModelInstanceMetadata[],
48
+ modelDefinition: SchemaModel
41
49
  ): Promise<[ModelInstanceMetadata, OpType][]> {
42
50
  const itemsMap: Map<string, ModelInstanceMetadata> = new Map();
43
51
 
44
52
  for (const item of items) {
45
53
  // merge items by model id. Latest record for a given id remains.
46
- itemsMap.set(item.id, item);
54
+ const modelId = getIdentifierValue(modelDefinition, item);
55
+
56
+ itemsMap.set(modelId, item);
47
57
  }
48
58
 
49
59
  const page = [...itemsMap.values()];
@@ -11,9 +11,10 @@ import {
11
11
  PersistentModel,
12
12
  PersistentModelConstructor,
13
13
  QueryOne,
14
+ SchemaModel,
14
15
  } from '../types';
15
16
  import { USER, SYNC, valuesEqual } from '../util';
16
- import { TransformerMutationType } from './utils';
17
+ import { getIdentifierValue, TransformerMutationType } from './utils';
17
18
 
18
19
  // TODO: Persist deleted ids
19
20
  // https://github.com/aws-amplify/amplify-js/blob/datastore-docs/packages/datastore/docs/sync-engine.md#outbox
@@ -35,6 +36,8 @@ class MutationEventOutbox {
35
36
  const mutationEventModelDefinition =
36
37
  this.schema.namespaces[SYNC].models['MutationEvent'];
37
38
 
39
+ // `id` is the key for the record in the mutationEvent;
40
+ // `modelId` is the key for the actual record that was mutated
38
41
  const predicate = ModelPredicateCreator.createFromExisting<MutationEvent>(
39
42
  mutationEventModelDefinition,
40
43
  c =>
@@ -43,13 +46,16 @@ class MutationEventOutbox {
43
46
  .id('ne', this.inProgressMutationEventId)
44
47
  );
45
48
 
49
+ // Check if there are any other records with same id
46
50
  const [first] = await s.query(this.MutationEvent, predicate);
47
51
 
52
+ // No other record with same modelId, so enqueue
48
53
  if (first === undefined) {
49
54
  await s.save(mutationEvent, undefined, this.ownSymbol);
50
55
  return;
51
56
  }
52
57
 
58
+ // There was an enqueued mutation for the modelId, so continue
53
59
  const { operation: incomingMutationType } = mutationEvent;
54
60
 
55
61
  if (first.operation === TransformerMutationType.CREATE) {
@@ -122,16 +128,19 @@ class MutationEventOutbox {
122
128
 
123
129
  public async getForModel<T extends PersistentModel>(
124
130
  storage: StorageFacade,
125
- model: T
131
+ model: T,
132
+ userModelDefinition: SchemaModel
126
133
  ): Promise<MutationEvent[]> {
127
134
  const mutationEventModelDefinition =
128
135
  this.schema.namespaces[SYNC].models.MutationEvent;
129
136
 
137
+ const modelId = getIdentifierValue(userModelDefinition, model);
138
+
130
139
  const mutationEvents = await storage.query(
131
140
  this.MutationEvent,
132
141
  ModelPredicateCreator.createFromExisting(
133
142
  mutationEventModelDefinition,
134
- c => c.modelId('eq', model.id)
143
+ c => c.modelId('eq', modelId)
135
144
  )
136
145
  );
137
146
 
@@ -187,9 +196,14 @@ class MutationEventOutbox {
187
196
  const mutationEventModelDefinition =
188
197
  this.schema.namespaces[SYNC].models['MutationEvent'];
189
198
 
199
+ const userModelDefinition =
200
+ this.schema.namespaces['user'].models[head.model];
201
+
202
+ const recordId = getIdentifierValue(userModelDefinition, record);
203
+
190
204
  const predicate = ModelPredicateCreator.createFromExisting<MutationEvent>(
191
205
  mutationEventModelDefinition,
192
- c => c.modelId('eq', record.id).id('ne', this.inProgressMutationEventId)
206
+ c => c.modelId('eq', recordId).id('ne', this.inProgressMutationEventId)
193
207
  );
194
208
 
195
209
  const outdatedMutations = await storage.query(
@@ -224,11 +238,11 @@ class MutationEventOutbox {
224
238
  previous: MutationEvent,
225
239
  current: MutationEvent
226
240
  ): MutationEvent {
227
- const { _version, id, _lastChangedAt, _deleted, ...previousData } =
228
- JSON.parse(previous.data);
241
+ const { _version, _lastChangedAt, _deleted, ...previousData } = JSON.parse(
242
+ previous.data
243
+ );
229
244
 
230
245
  const {
231
- id: __id,
232
246
  _version: __version,
233
247
  _lastChangedAt: __lastChangedAt,
234
248
  _deleted: __deleted,
@@ -236,7 +250,6 @@ class MutationEventOutbox {
236
250
  } = JSON.parse(current.data);
237
251
 
238
252
  const data = JSON.stringify({
239
- id,
240
253
  _version,
241
254
  _lastChangedAt,
242
255
  _deleted,
@@ -27,7 +27,13 @@ import {
27
27
  ProcessName,
28
28
  AmplifyContext,
29
29
  } from '../../types';
30
- import { exhaustiveCheck, USER, USER_AGENT_SUFFIX_DATASTORE } from '../../util';
30
+ import {
31
+ exhaustiveCheck,
32
+ extractTargetNamesFromSrc,
33
+ USER,
34
+ USER_AGENT_SUFFIX_DATASTORE,
35
+ ID,
36
+ } from '../../util';
31
37
  import { MutationEventOutbox } from '../outbox';
32
38
  import {
33
39
  buildGraphQLOperation,
@@ -452,63 +458,61 @@ class MutationProcessor {
452
458
 
453
459
  // include all the fields that comprise a custom PK if one is specified
454
460
  const deleteInput = {};
455
- if (primaryKey && primaryKey.length) {
461
+ if (primaryKey?.length) {
456
462
  for (const pkField of primaryKey) {
457
463
  deleteInput[pkField] = parsedData[pkField];
458
464
  }
459
465
  } else {
460
- deleteInput['id'] = parsedData.id;
466
+ deleteInput[ID] = (<any>parsedData).id;
461
467
  }
462
468
 
463
- const filteredData =
464
- operation === TransformerMutationType.DELETE
465
- ? <ModelInstanceMetadata>deleteInput // For DELETE mutations, only PK is sent
466
- : Object.values(modelDefinition.fields)
467
- .filter(({ name, type, association }) => {
468
- // connections
469
- if (isModelFieldType(type)) {
470
- // BELONGS_TO
471
- if (
472
- isTargetNameAssociation(association) &&
473
- association.connectionType === 'BELONGS_TO'
474
- ) {
475
- return true;
476
- }
469
+ let mutationInput;
477
470
 
478
- // All other connections
479
- return false;
480
- }
481
-
482
- if (operation === TransformerMutationType.UPDATE) {
483
- // this limits the update mutation input to changed fields only
484
- return parsedData.hasOwnProperty(name);
471
+ if (operation === TransformerMutationType.DELETE) {
472
+ // For DELETE mutations, only the key(s) are included in the input
473
+ mutationInput = <ModelInstanceMetadata>deleteInput;
474
+ } else {
475
+ // Otherwise, we construct the mutation input with the following logic
476
+ mutationInput = {};
477
+ const modelFields = Object.values(modelDefinition.fields);
478
+
479
+ for (const { name, type, association } of modelFields) {
480
+ // model fields should be stripped out from the input
481
+ if (isModelFieldType(type)) {
482
+ // except for belongs to relations - we need to replace them with the correct foreign key(s)
483
+ if (
484
+ isTargetNameAssociation(association) &&
485
+ association.connectionType === 'BELONGS_TO'
486
+ ) {
487
+ const targetNames: string[] | undefined =
488
+ extractTargetNamesFromSrc(association);
489
+
490
+ if (targetNames) {
491
+ // instead of including the connected model itself, we add its key(s) to the mutation input
492
+ for (const targetName of targetNames) {
493
+ mutationInput[targetName] = parsedData[targetName];
485
494
  }
495
+ }
496
+ }
497
+ continue;
498
+ }
499
+ // scalar fields / non-model types
486
500
 
487
- // scalars and non-model types
488
- return true;
489
- })
490
- .map(({ name, type, association }) => {
491
- let fieldName = name;
492
- let val = parsedData[name];
493
-
494
- if (
495
- isModelFieldType(type) &&
496
- isTargetNameAssociation(association)
497
- ) {
498
- fieldName = association.targetName;
499
- val = parsedData[fieldName];
500
- }
501
+ if (operation === TransformerMutationType.UPDATE) {
502
+ if (!parsedData.hasOwnProperty(name)) {
503
+ // for update mutations - strip out a field if it's unchanged
504
+ continue;
505
+ }
506
+ }
501
507
 
502
- return [fieldName, val];
503
- })
504
- .reduce((acc, [k, v]) => {
505
- acc[k] = v;
506
- return acc;
507
- }, <typeof parsedData>{});
508
+ // all other fields are added to the input object
509
+ mutationInput[name] = parsedData[name];
510
+ }
511
+ }
508
512
 
509
513
  // Build mutation variables input object
510
514
  const input: ModelInstanceMetadata = {
511
- ...filteredData,
515
+ ...mutationInput,
512
516
  _version,
513
517
  };
514
518
 
@@ -393,7 +393,6 @@ class SubscriptionProcessor {
393
393
  Observable<{
394
394
  value: GraphQLResult<Record<string, PersistentModel>>;
395
395
  }>
396
-
397
396
  >(<unknown>this.amplifyContext.API.graphql({ query, variables, ...{ authMode }, authToken, userAgentSuffix }));
398
397
 
399
398
  let subscriptionReadyCallback: () => void;
@@ -85,9 +85,7 @@ class SyncProcessor {
85
85
  return predicateToGraphQLFilter(predicatesGroup);
86
86
  }
87
87
 
88
- private async retrievePage<
89
- T extends ModelInstanceMetadata = ModelInstanceMetadata
90
- >(
88
+ private async retrievePage<T extends ModelInstanceMetadata>(
91
89
  modelDefinition: SchemaModel,
92
90
  lastSync: number,
93
91
  nextToken: string,