@aws-amplify/datastore 4.0.12 → 4.0.13-push-notification-dryrun.43

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 (111) hide show
  1. package/lib/datastore/datastore.d.ts +29 -3
  2. package/lib/datastore/datastore.js +308 -147
  3. package/lib/datastore/datastore.js.map +1 -1
  4. package/lib/predicates/index.d.ts +77 -7
  5. package/lib/predicates/index.js +142 -122
  6. package/lib/predicates/index.js.map +1 -1
  7. package/lib/predicates/next.d.ts +51 -10
  8. package/lib/predicates/next.js +111 -91
  9. package/lib/predicates/next.js.map +1 -1
  10. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
  11. package/lib/storage/adapter/AsyncStorageAdapter.js +135 -532
  12. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  13. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  14. package/lib/storage/adapter/IndexedDBAdapter.d.ts +28 -29
  15. package/lib/storage/adapter/IndexedDBAdapter.js +490 -885
  16. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  17. package/lib/storage/adapter/StorageAdapterBase.d.ts +134 -0
  18. package/lib/storage/adapter/StorageAdapterBase.js +439 -0
  19. package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
  20. package/lib/storage/relationship.d.ts +9 -0
  21. package/lib/storage/relationship.js +9 -0
  22. package/lib/storage/relationship.js.map +1 -1
  23. package/lib/storage/storage.d.ts +1 -1
  24. package/lib/storage/storage.js +4 -3
  25. package/lib/storage/storage.js.map +1 -1
  26. package/lib/sync/index.d.ts +15 -1
  27. package/lib/sync/index.js +80 -13
  28. package/lib/sync/index.js.map +1 -1
  29. package/lib/sync/outbox.js +14 -7
  30. package/lib/sync/outbox.js.map +1 -1
  31. package/lib/sync/processors/mutation.d.ts +10 -1
  32. package/lib/sync/processors/mutation.js +33 -12
  33. package/lib/sync/processors/mutation.js.map +1 -1
  34. package/lib/sync/processors/subscription.d.ts +7 -1
  35. package/lib/sync/processors/subscription.js +196 -135
  36. package/lib/sync/processors/subscription.js.map +1 -1
  37. package/lib/sync/processors/sync.d.ts +1 -1
  38. package/lib/sync/processors/sync.js.map +1 -1
  39. package/lib/sync/utils.d.ts +66 -2
  40. package/lib/sync/utils.js +264 -16
  41. package/lib/sync/utils.js.map +1 -1
  42. package/lib/types.d.ts +9 -1
  43. package/lib/types.js.map +1 -1
  44. package/lib/util.d.ts +16 -0
  45. package/lib/util.js +31 -2
  46. package/lib/util.js.map +1 -1
  47. package/lib-esm/datastore/datastore.d.ts +29 -3
  48. package/lib-esm/datastore/datastore.js +310 -149
  49. package/lib-esm/datastore/datastore.js.map +1 -1
  50. package/lib-esm/predicates/index.d.ts +77 -7
  51. package/lib-esm/predicates/index.js +143 -123
  52. package/lib-esm/predicates/index.js.map +1 -1
  53. package/lib-esm/predicates/next.d.ts +51 -10
  54. package/lib-esm/predicates/next.js +111 -91
  55. package/lib-esm/predicates/next.js.map +1 -1
  56. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
  57. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +138 -535
  58. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  59. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  60. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +28 -29
  61. package/lib-esm/storage/adapter/IndexedDBAdapter.js +489 -884
  62. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  63. package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +134 -0
  64. package/lib-esm/storage/adapter/StorageAdapterBase.js +437 -0
  65. package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
  66. package/lib-esm/storage/relationship.d.ts +9 -0
  67. package/lib-esm/storage/relationship.js +9 -0
  68. package/lib-esm/storage/relationship.js.map +1 -1
  69. package/lib-esm/storage/storage.d.ts +1 -1
  70. package/lib-esm/storage/storage.js +4 -3
  71. package/lib-esm/storage/storage.js.map +1 -1
  72. package/lib-esm/sync/index.d.ts +15 -1
  73. package/lib-esm/sync/index.js +82 -15
  74. package/lib-esm/sync/index.js.map +1 -1
  75. package/lib-esm/sync/outbox.js +14 -7
  76. package/lib-esm/sync/outbox.js.map +1 -1
  77. package/lib-esm/sync/processors/mutation.d.ts +10 -1
  78. package/lib-esm/sync/processors/mutation.js +33 -12
  79. package/lib-esm/sync/processors/mutation.js.map +1 -1
  80. package/lib-esm/sync/processors/subscription.d.ts +7 -1
  81. package/lib-esm/sync/processors/subscription.js +197 -136
  82. package/lib-esm/sync/processors/subscription.js.map +1 -1
  83. package/lib-esm/sync/processors/sync.d.ts +1 -1
  84. package/lib-esm/sync/processors/sync.js.map +1 -1
  85. package/lib-esm/sync/utils.d.ts +66 -2
  86. package/lib-esm/sync/utils.js +261 -18
  87. package/lib-esm/sync/utils.js.map +1 -1
  88. package/lib-esm/types.d.ts +9 -1
  89. package/lib-esm/types.js.map +1 -1
  90. package/lib-esm/util.d.ts +16 -0
  91. package/lib-esm/util.js +32 -3
  92. package/lib-esm/util.js.map +1 -1
  93. package/package.json +12 -11
  94. package/src/datastore/datastore.ts +288 -159
  95. package/src/predicates/index.ts +145 -175
  96. package/src/predicates/next.ts +114 -81
  97. package/src/storage/adapter/AsyncStorageAdapter.ts +97 -563
  98. package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
  99. package/src/storage/adapter/IndexedDBAdapter.ts +318 -770
  100. package/src/storage/adapter/StorageAdapterBase.ts +545 -0
  101. package/src/storage/relationship.ts +9 -0
  102. package/src/storage/storage.ts +12 -9
  103. package/src/sync/index.ts +108 -20
  104. package/src/sync/outbox.ts +17 -11
  105. package/src/sync/processors/mutation.ts +35 -4
  106. package/src/sync/processors/subscription.ts +124 -10
  107. package/src/sync/processors/sync.ts +4 -1
  108. package/src/sync/utils.ts +285 -15
  109. package/src/types.ts +15 -2
  110. package/src/util.ts +40 -1
  111. package/CHANGELOG.md +0 -904
@@ -98,6 +98,7 @@ import {
98
98
  isIdManaged,
99
99
  isIdOptionallyManaged,
100
100
  mergePatches,
101
+ getTimestampFields,
101
102
  } from '../util';
102
103
  import {
103
104
  recursivePredicateFor,
@@ -139,9 +140,13 @@ const modelNamespaceMap = new WeakMap<
139
140
  PersistentModelConstructor<any>,
140
141
  string
141
142
  >();
142
- // stores data for crafting the correct update mutation input for a model
143
- // Patch[] - array of changed fields and metadata
144
- // PersistentModel - the source model, used for diffing object-type fields
143
+
144
+ /**
145
+ * Stores data for crafting the correct update mutation input for a model.
146
+ *
147
+ * - `Patch[]` - array of changed fields and metadata.
148
+ * - `PersistentModel` - the source model, used for diffing object-type fields.
149
+ */
145
150
  const modelPatchesMap = new WeakMap<
146
151
  PersistentModel,
147
152
  [Patch[], PersistentModel]
@@ -155,60 +160,19 @@ const getModelDefinition = (
155
160
  ? schema.namespaces[namespace].models[modelConstructor.name]
156
161
  : undefined;
157
162
 
158
- // compatibility with legacy/pre-PK codegen for lazy loading to inject
159
- // index fields into the model definition.
160
- if (definition) {
161
- const indexes =
162
- schema.namespaces[namespace].relationships![modelConstructor.name]
163
- .indexes;
164
-
165
- const indexFields = new Set<string>();
166
- for (const index of indexes) {
167
- for (const indexField of index[1]) {
168
- indexFields.add(indexField);
169
- }
170
- }
171
-
172
- definition.fields = {
173
- ...Object.fromEntries(
174
- [...indexFields.values()].map(
175
- name => [
176
- name,
177
- {
178
- name,
179
- type: 'ID',
180
- isArray: false,
181
- },
182
- ],
183
- []
184
- )
185
- ),
186
- ...definition.fields,
187
- };
188
- }
189
-
190
163
  return definition;
191
164
  };
192
165
 
193
- const getModelPKFieldName = (
194
- modelConstructor: PersistentModelConstructor<any>
195
- ) => {
196
- const namespace = modelNamespaceMap.get(modelConstructor);
197
- return (
198
- (namespace &&
199
- schema.namespaces?.[namespace]?.keys?.[modelConstructor.name]
200
- .primaryKey) || ['id']
201
- );
202
- };
203
-
166
+ /**
167
+ * Determines whether the given object is a Model Constructor that DataStore can
168
+ * safely use to construct objects and discover related metadata.
169
+ *
170
+ * @param obj The object to test.
171
+ */
204
172
  const isValidModelConstructor = <T extends PersistentModel>(
205
173
  obj: any
206
174
  ): obj is PersistentModelConstructor<T> => {
207
- if (isModelConstructor(obj) && modelNamespaceMap.has(obj)) {
208
- return true;
209
- } else {
210
- return false;
211
- }
175
+ return isModelConstructor(obj) && modelNamespaceMap.has(obj);
212
176
  };
213
177
 
214
178
  const namespaceResolver: NamespaceResolver = modelConstructor => {
@@ -221,6 +185,25 @@ const namespaceResolver: NamespaceResolver = modelConstructor => {
221
185
  return resolver;
222
186
  };
223
187
 
188
+ /**
189
+ * Creates a predicate without any conditions that can be passed to customer
190
+ * code to have conditions added to it.
191
+ *
192
+ * For example, in this query:
193
+ *
194
+ * ```ts
195
+ * await DataStore.query(
196
+ * Model,
197
+ * item => item.field.eq('value')
198
+ * );
199
+ * ```
200
+ *
201
+ * `buildSeedPredicate(Model)` is used to create `item`, which is passed to the
202
+ * predicate function, which in turn uses that "seed" predicate (`item`) to build
203
+ * a predicate tree.
204
+ *
205
+ * @param modelConstructor The model the predicate will query.
206
+ */
224
207
  const buildSeedPredicate = <T extends PersistentModel>(
225
208
  modelConstructor: PersistentModelConstructor<T>
226
209
  ) => {
@@ -231,9 +214,7 @@ const buildSeedPredicate = <T extends PersistentModel>(
231
214
  );
232
215
  if (!modelSchema) throw new Error('Missing modelSchema');
233
216
 
234
- const pks = getModelPKFieldName(
235
- modelConstructor as PersistentModelConstructor<T>
236
- );
217
+ const pks = extractPrimaryKeyFieldNames(modelSchema);
237
218
  if (!pks) throw new Error('Could not determine PK');
238
219
 
239
220
  return recursivePredicateFor<T>({
@@ -386,32 +367,56 @@ const initSchema = (userSchema: Schema) => {
386
367
 
387
368
  modelAssociations.set(model.name, connectedModels);
388
369
 
370
+ // Precompute model info (such as pk fields) so that downstream schema consumers
371
+ // (such as predicate builders) don't have to reach back into "DataStore" space
372
+ // to go looking for it.
389
373
  Object.values(model.fields).forEach(field => {
390
- if (
391
- typeof field.type === 'object' &&
392
- !Object.getOwnPropertyDescriptor(
393
- <ModelFieldType>field.type,
394
- 'modelConstructor'
395
- )
396
- ) {
374
+ const relatedModel = userClasses[(<ModelFieldType>field.type).model];
375
+ if (isModelConstructor(relatedModel)) {
397
376
  Object.defineProperty(field.type, 'modelConstructor', {
398
377
  get: () => {
378
+ const relatedModelDefinition = getModelDefinition(relatedModel);
379
+ if (!relatedModelDefinition)
380
+ throw new Error(
381
+ `Could not find model definition for ${relatedModel.name}`
382
+ );
399
383
  return {
400
- builder: userClasses[(<ModelFieldType>field.type).model],
401
- schema:
402
- schema.namespaces[namespace].models[
403
- (<ModelFieldType>field.type).model
404
- ],
405
- pkField: getModelPKFieldName(
406
- userClasses[
407
- (<ModelFieldType>field.type).model
408
- ] as PersistentModelConstructor<any>
409
- ),
384
+ builder: relatedModel,
385
+ schema: relatedModelDefinition,
386
+ pkField: extractPrimaryKeyFieldNames(relatedModelDefinition),
410
387
  };
411
388
  },
412
389
  });
413
390
  }
414
391
  });
392
+
393
+ // compatibility with legacy/pre-PK codegen for lazy loading to inject
394
+ // index fields into the model definition.
395
+ // definition.cloudFields = { ...definition.fields };
396
+
397
+ const indexes =
398
+ schema.namespaces[namespace].relationships![model.name].indexes;
399
+
400
+ const indexFields = new Set<string>();
401
+ for (const index of indexes) {
402
+ for (const indexField of index[1]) {
403
+ indexFields.add(indexField);
404
+ }
405
+ }
406
+
407
+ model.allFields = {
408
+ ...Object.fromEntries(
409
+ [...indexFields.values()].map(name => [
410
+ name,
411
+ {
412
+ name,
413
+ type: 'ID',
414
+ isArray: false,
415
+ },
416
+ ])
417
+ ),
418
+ ...model.fields,
419
+ };
415
420
  });
416
421
 
417
422
  const result = new Map<string, string[]>();
@@ -482,7 +487,6 @@ const checkSchemaInitialized = () => {
482
487
  * @param codegenVersion schema codegenVersion
483
488
  */
484
489
  const checkSchemaCodegenVersion = (codegenVersion: string) => {
485
- // TODO: set to correct version when released in codegen
486
490
  const majorVersion = 3;
487
491
  const minorVersion = 2;
488
492
  let isValid = false;
@@ -520,7 +524,7 @@ const createTypeClasses: (
520
524
 
521
525
  Object.entries(namespace.nonModels || {}).forEach(
522
526
  ([typeName, typeDefinition]) => {
523
- const clazz = createNonModelClass(typeDefinition) as any;
527
+ const clazz = createNonModelClass(typeDefinition);
524
528
  classes[typeName] = clazz;
525
529
  }
526
530
  );
@@ -563,8 +567,14 @@ const validateModelFields =
563
567
  const { type, isRequired, isArrayNullable, name, isArray } =
564
568
  fieldDefinition;
565
569
 
570
+ const timestamps = isSchemaModelWithAttributes(modelDefinition)
571
+ ? getTimestampFields(modelDefinition)
572
+ : {};
573
+ const isTimestampField = !!timestamps[name];
574
+
566
575
  if (
567
576
  ((!isArray && isRequired) || (isArray && !isArrayNullable)) &&
577
+ !isTimestampField &&
568
578
  (v === null || v === undefined)
569
579
  ) {
570
580
  throw new Error(`Field ${name} is required`);
@@ -741,6 +751,21 @@ const castInstanceType = (
741
751
  return v;
742
752
  };
743
753
 
754
+ /**
755
+ * Records the patches (as if against an empty object) used to initialize
756
+ * an instance of a Model. This can be used for determining which fields to
757
+ * send to the cloud durnig a CREATE mutation.
758
+ */
759
+ const initPatches = new WeakMap<PersistentModel, Patch[]>();
760
+
761
+ /**
762
+ * Attempts to apply type-aware, casted field values from a given `init`
763
+ * object to the given `draft`.
764
+ *
765
+ * @param init The initialization object to extract field values from.
766
+ * @param modelDefinition The definition describing the target object shape.
767
+ * @param draft The draft to apply field values to.
768
+ */
744
769
  const initializeInstance = <T extends PersistentModel>(
745
770
  init: ModelInit<T>,
746
771
  modelDefinition: SchemaModel | SchemaNonModel,
@@ -755,12 +780,44 @@ const initializeInstance = <T extends PersistentModel>(
755
780
  });
756
781
  };
757
782
 
783
+ /**
784
+ * Updates a draft to standardize its customer-defined fields so that they are
785
+ * consistent with the data as it would look after having been synchronized from
786
+ * Cloud storage.
787
+ *
788
+ * The exceptions to this are:
789
+ *
790
+ * 1. Non-schema/Internal [sync] metadata fields.
791
+ * 2. Cloud-managed fields, which are `null` until set by cloud storage.
792
+ *
793
+ * This function should be expanded if/when deviations between canonical Cloud
794
+ * storage data and locally managed data are found. For now, the known areas
795
+ * that require normalization are:
796
+ *
797
+ * 1. Ensuring all non-metadata fields are *defined*. (I.e., turn `undefined` -> `null`.)
798
+ *
799
+ * @param modelDefinition Definition for the draft. Used to discover all fields.
800
+ * @param draft The instance draft to apply normalizations to.
801
+ */
802
+ const normalize = <T extends PersistentModel>(
803
+ modelDefinition: SchemaModel | SchemaNonModel,
804
+ draft: Draft<T>
805
+ ) => {
806
+ for (const k of Object.keys(modelDefinition.fields)) {
807
+ if (draft[k] === undefined) (<any>draft)[k] = null;
808
+ }
809
+ };
810
+
758
811
  const createModelClass = <T extends PersistentModel>(
759
812
  modelDefinition: SchemaModel
760
813
  ) => {
761
814
  const clazz = <PersistentModelConstructor<T>>(<unknown>class Model {
762
815
  constructor(init: ModelInit<T>) {
763
- const instance = produce(
816
+ // we create a base instance first so we can distinguish which fields were explicitly
817
+ // set by customer code versus those set by normalization. only those fields
818
+ // which are explicitly set by customers should be part of create mutations.
819
+ let patches: Patch[] = [];
820
+ const baseInstance = produce(
764
821
  this,
765
822
  (draft: Draft<T & ModelInstanceMetadata>) => {
766
823
  initializeInstance(init, modelDefinition, draft);
@@ -804,10 +861,24 @@ const createModelClass = <T extends PersistentModel>(
804
861
  draft._lastChangedAt = _lastChangedAt;
805
862
  draft._deleted = _deleted;
806
863
  }
807
- }
864
+ },
865
+ p => (patches = p)
808
866
  );
809
867
 
810
- return instance;
868
+ // now that we have a list of patches that encapsulate the explicit, customer-provided
869
+ // fields, we can normalize. patches from normalization are ignored, because the changes
870
+ // are only create to provide a consistent view of the data for fields pre/post sync
871
+ // where possible. (not all fields can be normalized pre-sync, because they're generally
872
+ // "cloud managed" fields, like createdAt and updatedAt.)
873
+ const normalized = produce(
874
+ baseInstance,
875
+ (draft: Draft<T & ModelInstanceMetadata>) =>
876
+ normalize(modelDefinition, draft)
877
+ );
878
+
879
+ initPatches.set(normalized, patches);
880
+
881
+ return normalized;
811
882
  }
812
883
 
813
884
  static copyOf(source: T, fn: (draft: MutableModel<T>) => T) {
@@ -842,6 +913,8 @@ const createModelClass = <T extends PersistentModel>(
842
913
 
843
914
  modelValidator(k, parsedValue);
844
915
  });
916
+
917
+ normalize(modelDefinition, draft);
845
918
  },
846
919
  p => (patches = p)
847
920
  );
@@ -863,6 +936,14 @@ const createModelClass = <T extends PersistentModel>(
863
936
  modelPatchesMap.set(model, [patches, source]);
864
937
  checkReadOnlyPropertyOnUpdate(patches, modelDefinition);
865
938
  }
939
+ } else {
940
+ // always register patches when performing a copyOf, even if the
941
+ // patches list is empty. this allows `save()` to recognize when an
942
+ // instance is the result of a `copyOf()`. without more significant
943
+ // refactoring, this is the only way for `save()` to know which
944
+ // diffs (patches) are relevant for `storage` to use in building
945
+ // the list of "changed" fields for mutations.
946
+ modelPatchesMap.set(model, [[], source]);
866
947
  }
867
948
 
868
949
  return attached(model, ModelAttachment.DataStore);
@@ -891,88 +972,103 @@ const createModelClass = <T extends PersistentModel>(
891
972
 
892
973
  Object.defineProperty(clazz, 'name', { value: modelDefinition.name });
893
974
 
894
- for (const field in modelDefinition.fields) {
895
- if (!isFieldAssociation(modelDefinition, field)) {
896
- continue;
897
- }
898
-
899
- const {
900
- type,
901
- association: localAssociation,
902
- association: { targetName, targetNames },
903
- } = modelDefinition.fields[field] as Required<ModelField>;
904
-
905
- const relationship = new ModelRelationship(
906
- {
907
- builder: clazz,
908
- schema: modelDefinition,
909
- pkField: extractPrimaryKeyFieldNames(modelDefinition),
910
- },
911
- field
912
- );
975
+ // Add getters/setters for relationship fields.
976
+ // getter - for lazy loading
977
+ // setter - for FK management
978
+ const allModelRelationships = ModelRelationship.allFrom({
979
+ builder: clazz,
980
+ schema: modelDefinition,
981
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
982
+ });
983
+ for (const relationship of allModelRelationships) {
984
+ const field = relationship.field;
913
985
 
914
986
  Object.defineProperty(clazz.prototype, modelDefinition.fields[field].name, {
915
- set(model: PersistentModel) {
916
- if (!model || !(typeof model === 'object')) return;
987
+ set(model: T | undefined | null) {
988
+ if (!(typeof model === 'object' || typeof model === 'undefined'))
989
+ return;
917
990
 
918
- // Avoid validation error when processing AppSync response with nested
919
- // selection set. Nested entitites lack version field and can not be validated
920
- // TODO: explore a more reliable method to solve this
921
- if (model.hasOwnProperty('_version')) {
922
- const modelConstructor = Object.getPrototypeOf(model || {})
923
- .constructor as PersistentModelConstructor<T>;
991
+ // if model is undefined or null, the connection should be removed
992
+ if (model) {
993
+ // Avoid validation error when processing AppSync response with nested
994
+ // selection set. Nested entitites lack version field and can not be validated
995
+ // TODO: explore a more reliable method to solve this
996
+ if (model.hasOwnProperty('_version')) {
997
+ const modelConstructor = Object.getPrototypeOf(model || {})
998
+ .constructor as PersistentModelConstructor<T>;
924
999
 
925
- if (!isValidModelConstructor(modelConstructor)) {
926
- const msg = `Value passed to ${modelDefinition.name}.${field} is not a valid instance of a model`;
927
- logger.error(msg, { model });
1000
+ if (!isValidModelConstructor(modelConstructor)) {
1001
+ const msg = `Value passed to ${modelDefinition.name}.${field} is not a valid instance of a model`;
1002
+ logger.error(msg, { model });
928
1003
 
929
- throw new Error(msg);
930
- }
1004
+ throw new Error(msg);
1005
+ }
931
1006
 
932
- if (
933
- modelConstructor.name.toLowerCase() !==
934
- relationship.remoteModelConstructor.name.toLowerCase()
935
- ) {
936
- const msg = `Value passed to ${modelDefinition.name}.${field} is not an instance of ${relationship.remoteModelConstructor.name}`;
937
- logger.error(msg, { model });
1007
+ if (
1008
+ modelConstructor.name.toLowerCase() !==
1009
+ relationship.remoteModelConstructor.name.toLowerCase()
1010
+ ) {
1011
+ const msg = `Value passed to ${modelDefinition.name}.${field} is not an instance of ${relationship.remoteModelConstructor.name}`;
1012
+ logger.error(msg, { model });
938
1013
 
939
- throw new Error(msg);
1014
+ throw new Error(msg);
1015
+ }
940
1016
  }
941
1017
  }
942
1018
 
1019
+ // if the relationship can be managed automagically, set the FK's
943
1020
  if (relationship.isComplete) {
944
1021
  for (let i = 0; i < relationship.localJoinFields.length; i++) {
945
1022
  this[relationship.localJoinFields[i]] =
946
- model[relationship.remoteJoinFields[i]];
1023
+ model?.[relationship.remoteJoinFields[i]];
947
1024
  }
948
1025
  const instanceMemos = modelInstanceAssociationsMap.has(this)
949
1026
  ? modelInstanceAssociationsMap.get(this)!
950
1027
  : modelInstanceAssociationsMap.set(this, {}).get(this)!;
951
- instanceMemos[field] = model;
1028
+ instanceMemos[field] = model || undefined;
952
1029
  }
953
1030
  },
954
1031
  get() {
1032
+ /**
1033
+ * Bucket for holding related models instances specific to `this` instance.
1034
+ */
955
1035
  const instanceMemos = modelInstanceAssociationsMap.has(this)
956
1036
  ? modelInstanceAssociationsMap.get(this)!
957
1037
  : modelInstanceAssociationsMap.set(this, {}).get(this)!;
958
1038
 
1039
+ // if the memos already has a result for this field, we'll use it.
1040
+ // there is no "cache" invalidation of any kind; memos are permanent to
1041
+ // keep an immutable perception of the instance.
959
1042
  if (!instanceMemos.hasOwnProperty(field)) {
1043
+ // before we populate the memo, we need to know where to look for relatives.
1044
+ // today, this only supports DataStore. Models aren't managed elsewhere in Amplify.
960
1045
  if (getAttachment(this) === ModelAttachment.DataStore) {
1046
+ // when we fetch the results using a query constructed under the guidance
1047
+ // of the relationship metadata, we DO NOT AWAIT resolution. we want to
1048
+ // drop the promise into the memo's synchronously, eliminating the chance
1049
+ // for a race.
961
1050
  const resultPromise = instance.query(
962
1051
  relationship.remoteModelConstructor as PersistentModelConstructor<T>,
963
1052
  base =>
964
1053
  base.and(q => {
965
1054
  return relationship.remoteJoinFields.map((field, index) => {
966
- return (q[field] as any).eq(
1055
+ // TODO: anything we can use instead of `any` here?
1056
+ return (q[field] as T[typeof field]).eq(
967
1057
  this[relationship.localJoinFields[index]]
968
1058
  );
969
1059
  });
970
1060
  })
971
1061
  );
972
1062
 
1063
+ // results in hand, how we return them to the caller depends on the relationship type.
973
1064
  if (relationship.type === 'HAS_MANY') {
1065
+ // collections should support async iteration, even though we don't
1066
+ // leverage it fully [yet].
974
1067
  instanceMemos[field] = new AsyncCollection(resultPromise);
975
1068
  } else {
1069
+ // non-collections should only ever return 1 value *or nothing*.
1070
+ // if we have more than 1 record, something's amiss. it's not our job
1071
+ // pick a result for the customer. it's our job to say "something's wrong."
976
1072
  instanceMemos[field] = resultPromise.then(rows => {
977
1073
  if (rows.length > 1) {
978
1074
  // should never happen for a HAS_ONE or BELONGS_TO.
@@ -1006,8 +1102,16 @@ const createModelClass = <T extends PersistentModel>(
1006
1102
  return clazz;
1007
1103
  };
1008
1104
 
1105
+ /**
1106
+ * An eventually loaded related model instance.
1107
+ */
1009
1108
  export class AsyncItem<T> extends Promise<T> {}
1010
1109
 
1110
+ /**
1111
+ * A collection of related model instances.
1112
+ *
1113
+ * This collection can be async-iterated or turned directly into an array using `toArray()`.
1114
+ */
1011
1115
  export class AsyncCollection<T> implements AsyncIterable<T> {
1012
1116
  private values: Array<any> | Promise<Array<any>>;
1013
1117
 
@@ -1015,6 +1119,17 @@ export class AsyncCollection<T> implements AsyncIterable<T> {
1015
1119
  this.values = values;
1016
1120
  }
1017
1121
 
1122
+ /**
1123
+ * Facilitates async iteration.
1124
+ *
1125
+ * ```ts
1126
+ * for await (const item of collection) {
1127
+ * handle(item)
1128
+ * }
1129
+ * ```
1130
+ *
1131
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
1132
+ */
1018
1133
  [Symbol.asyncIterator](): AsyncIterator<T> {
1019
1134
  let values;
1020
1135
  let index = 0;
@@ -1037,6 +1152,14 @@ export class AsyncCollection<T> implements AsyncIterable<T> {
1037
1152
  };
1038
1153
  }
1039
1154
 
1155
+ /**
1156
+ * Turns the collection into an array, up to the amount specified in `max` param.
1157
+ *
1158
+ * ```ts
1159
+ * const all = await collection.toArray();
1160
+ * const first100 = await collection.toArray({max: 100});
1161
+ * ```
1162
+ */
1040
1163
  async toArray({
1041
1164
  max = Number.MAX_SAFE_INTEGER,
1042
1165
  }: { max?: number } = {}): Promise<T[]> {
@@ -1180,9 +1303,9 @@ async function checkSchemaVersion(
1180
1303
  await storage.runExclusive(async s => {
1181
1304
  const [schemaVersionSetting] = await s.query(
1182
1305
  Setting,
1183
- ModelPredicateCreator.createFromExisting(modelDefinition, c =>
1184
- c.key('eq', SETTING_SCHEMA_VERSION)
1185
- ),
1306
+ ModelPredicateCreator.createFromAST(modelDefinition, {
1307
+ and: { key: { eq: SETTING_SCHEMA_VERSION } },
1308
+ }),
1186
1309
  { page: 0, limit: 1 }
1187
1310
  );
1188
1311
 
@@ -1254,6 +1377,8 @@ enum DataStoreState {
1254
1377
  Clearing = 'Clearing',
1255
1378
  }
1256
1379
 
1380
+ // TODO: How can we get rid of the non-null assertions?
1381
+ // https://github.com/aws-amplify/amplify-js/pull/10477/files#r1007363485
1257
1382
  class DataStore {
1258
1383
  // reference to configured category instances. Used for preserving SSR context
1259
1384
  private Auth = Auth;
@@ -1281,7 +1406,7 @@ class DataStore {
1281
1406
  private sync?: SyncEngine;
1282
1407
  private syncPageSize!: number;
1283
1408
  private syncExpressions!: SyncExpression[];
1284
- private syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>> =
1409
+ private syncPredicates: WeakMap<SchemaModel, ModelPredicate<any> | null> =
1285
1410
  new WeakMap<SchemaModel, ModelPredicate<any>>();
1286
1411
  private sessionId?: string;
1287
1412
  private storageAdapter!: Adapter;
@@ -1373,8 +1498,8 @@ class DataStore {
1373
1498
  /**
1374
1499
  * If not already done:
1375
1500
  * 1. Attaches and initializes storage.
1376
- * 1. Loads the schema and records metadata.
1377
- * 1. If `this.amplifyConfig.aws_appsync_graphqlEndpoint` contains a URL,
1501
+ * 2. Loads the schema and records metadata.
1502
+ * 3. If `this.amplifyConfig.aws_appsync_graphqlEndpoint` contains a URL,
1378
1503
  * attaches a sync engine, starts it, and subscribes.
1379
1504
  */
1380
1505
  start = async (): Promise<void> => {
@@ -1542,10 +1667,9 @@ class DataStore {
1542
1667
  throw new Error(msg);
1543
1668
  }
1544
1669
 
1545
- const predicate = ModelPredicateCreator.createForSingleField<T>(
1670
+ const predicate = ModelPredicateCreator.createFromFlatEqualities<T>(
1546
1671
  modelDefinition,
1547
- keyFields[0],
1548
- identifierOrCriteria
1672
+ { [keyFields[0]]: identifierOrCriteria }
1549
1673
  );
1550
1674
 
1551
1675
  result = await this.storage.query<T>(
@@ -1578,7 +1702,7 @@ class DataStore {
1578
1702
  const seedPredicate = recursivePredicateFor<T>({
1579
1703
  builder: modelConstructor,
1580
1704
  schema: modelDefinition,
1581
- pkField: getModelPKFieldName(modelConstructor),
1705
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
1582
1706
  });
1583
1707
  const predicate = internals(
1584
1708
  (identifierOrCriteria as RecursiveModelPredicateExtender<T>)(
@@ -1618,7 +1742,24 @@ class DataStore {
1618
1742
 
1619
1743
  // Immer patches for constructing a correct update mutation input
1620
1744
  // Allows us to only include changed fields for updates
1621
- const patchesTuple = modelPatchesMap.get(model);
1745
+ const updatedPatchesTuple = modelPatchesMap.get(model);
1746
+
1747
+ // Immer patches for initial object construction. These are used if
1748
+ // there are no `update` patches under the assumption we're performing
1749
+ // a CREATE and wish to send only explicitly specified fields to the cloud.
1750
+ const initPatchesTuple = initPatches.has(model)
1751
+ ? ([initPatches.get(model)!, {}] as [
1752
+ Patch[],
1753
+ Readonly<Record<string, any>>
1754
+ ])
1755
+ : undefined;
1756
+
1757
+ // favor update patches over init/create patches, because init patches
1758
+ // are ALWAYS present, whereas update patches are only present if copyOf
1759
+ // was used to create the instance.
1760
+ const patchesTuple:
1761
+ | [Patch[], Readonly<Record<string, any>>]
1762
+ | undefined = updatedPatchesTuple || initPatchesTuple;
1622
1763
 
1623
1764
  const modelConstructor: PersistentModelConstructor<T> | undefined =
1624
1765
  model ? <PersistentModelConstructor<T>>model.constructor : undefined;
@@ -1798,10 +1939,9 @@ class DataStore {
1798
1939
  throw new Error(msg);
1799
1940
  }
1800
1941
 
1801
- condition = ModelPredicateCreator.createForSingleField<T>(
1942
+ condition = ModelPredicateCreator.createFromFlatEqualities<T>(
1802
1943
  modelDefinition,
1803
- keyFields[0],
1804
- identifierOrCriteria
1944
+ { [keyFields[0]]: identifierOrCriteria }
1805
1945
  );
1806
1946
  } else {
1807
1947
  if (isIdentifierObject(identifierOrCriteria, modelDefinition)) {
@@ -1880,7 +2020,7 @@ class DataStore {
1880
2020
  pkField: extractPrimaryKeyFieldNames(modelDefinition),
1881
2021
  })
1882
2022
  )
1883
- ).toStoragePredicate<T>(pkPredicate);
2023
+ ).toStoragePredicate<T>();
1884
2024
  } else {
1885
2025
  condition = pkPredicate;
1886
2026
  }
@@ -1980,7 +2120,7 @@ class DataStore {
1980
2120
  } else if (modelConstructor && typeof identifierOrCriteria === 'function') {
1981
2121
  executivePredicate = internals(
1982
2122
  (identifierOrCriteria as RecursiveModelPredicateExtender<T>)(
1983
- buildSeedPredicate(modelConstructor) as any
2123
+ buildSeedPredicate(modelConstructor)
1984
2124
  )
1985
2125
  );
1986
2126
  }
@@ -2206,10 +2346,6 @@ class DataStore {
2206
2346
  ...Array.from(itemsChanged.values()),
2207
2347
  ];
2208
2348
 
2209
- if (options?.sort) {
2210
- sortItems(itemsArray);
2211
- }
2212
-
2213
2349
  items.clear();
2214
2350
  itemsArray.forEach(item => {
2215
2351
  const itemModelDefinition = getModelDefinition(model);
@@ -2220,8 +2356,16 @@ class DataStore {
2220
2356
  // remove deleted items from the final result set
2221
2357
  deletedItemIds.forEach(idOrPk => items.delete(idOrPk));
2222
2358
 
2359
+ const snapshot = Array.from(items.values());
2360
+
2361
+ // we sort after we merge the snapshots (items, itemsChanged)
2362
+ // otherwise, the merge may not
2363
+ if (options?.sort) {
2364
+ sortItems(snapshot);
2365
+ }
2366
+
2223
2367
  return {
2224
- items: Array.from(items.values()),
2368
+ items: snapshot,
2225
2369
  isSynced,
2226
2370
  };
2227
2371
  };
@@ -2509,7 +2653,7 @@ class DataStore {
2509
2653
  * SchemaModel -> predicate to use during sync.
2510
2654
  */
2511
2655
  private async processSyncExpressions(): Promise<
2512
- WeakMap<SchemaModel, ModelPredicate<any>>
2656
+ WeakMap<SchemaModel, ModelPredicate<any> | null>
2513
2657
  > {
2514
2658
  if (!this.syncExpressions || !this.syncExpressions.length) {
2515
2659
  return new WeakMap<SchemaModel, ModelPredicate<any>>();
@@ -2519,7 +2663,7 @@ class DataStore {
2519
2663
  this.syncExpressions.map(
2520
2664
  async (
2521
2665
  syncExpression: SyncExpression
2522
- ): Promise<[SchemaModel, ModelPredicate<any>]> => {
2666
+ ): Promise<[SchemaModel, ModelPredicate<any> | null]> => {
2523
2667
  const { modelConstructor, conditionProducer } = await syncExpression;
2524
2668
  const modelDefinition = getModelDefinition(modelConstructor)!;
2525
2669
 
@@ -2527,7 +2671,7 @@ class DataStore {
2527
2671
  // OR a function/promise that returns a predicate
2528
2672
  const condition = await this.unwrapPromise(conditionProducer);
2529
2673
  if (isPredicatesAll(condition)) {
2530
- return [modelDefinition as any, null as any];
2674
+ return [modelDefinition, null];
2531
2675
  }
2532
2676
 
2533
2677
  const predicate = internals(
@@ -2540,7 +2684,7 @@ class DataStore {
2540
2684
  )
2541
2685
  ).toStoragePredicate<any>();
2542
2686
 
2543
- return [modelDefinition as any, predicate as any];
2687
+ return [modelDefinition, predicate];
2544
2688
  }
2545
2689
  )
2546
2690
  );
@@ -2548,21 +2692,6 @@ class DataStore {
2548
2692
  return this.weakMapFromEntries(syncPredicates);
2549
2693
  }
2550
2694
 
2551
- private createFromCondition(
2552
- modelDefinition: SchemaModel,
2553
- condition: ProducerModelPredicate<PersistentModel>
2554
- ) {
2555
- try {
2556
- return ModelPredicateCreator.createFromExisting(
2557
- modelDefinition,
2558
- condition
2559
- );
2560
- } catch (error) {
2561
- logger.error('Error creating Sync Predicate');
2562
- throw error;
2563
- }
2564
- }
2565
-
2566
2695
  private async unwrapPromise<T extends PersistentModel>(
2567
2696
  conditionProducer
2568
2697
  ): Promise<ModelPredicateExtender<T>> {
@@ -2578,7 +2707,7 @@ class DataStore {
2578
2707
  }
2579
2708
 
2580
2709
  private weakMapFromEntries(
2581
- entries: [SchemaModel, ModelPredicate<any>][]
2710
+ entries: [SchemaModel, ModelPredicate<any> | null][]
2582
2711
  ): WeakMap<SchemaModel, ModelPredicate<any>> {
2583
2712
  return entries.reduce((map, [modelDefinition, predicate]) => {
2584
2713
  if (map.has(modelDefinition)) {