@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.
- package/lib/datastore/datastore.d.ts +29 -3
- package/lib/datastore/datastore.js +308 -147
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/predicates/index.d.ts +77 -7
- package/lib/predicates/index.js +142 -122
- package/lib/predicates/index.js.map +1 -1
- package/lib/predicates/next.d.ts +51 -10
- package/lib/predicates/next.js +111 -91
- package/lib/predicates/next.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
- package/lib/storage/adapter/AsyncStorageAdapter.js +135 -532
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +28 -29
- package/lib/storage/adapter/IndexedDBAdapter.js +490 -885
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/StorageAdapterBase.d.ts +134 -0
- package/lib/storage/adapter/StorageAdapterBase.js +439 -0
- package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/lib/storage/relationship.d.ts +9 -0
- package/lib/storage/relationship.js +9 -0
- package/lib/storage/relationship.js.map +1 -1
- package/lib/storage/storage.d.ts +1 -1
- package/lib/storage/storage.js +4 -3
- package/lib/storage/storage.js.map +1 -1
- package/lib/sync/index.d.ts +15 -1
- package/lib/sync/index.js +80 -13
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/outbox.js +14 -7
- package/lib/sync/outbox.js.map +1 -1
- package/lib/sync/processors/mutation.d.ts +10 -1
- package/lib/sync/processors/mutation.js +33 -12
- package/lib/sync/processors/mutation.js.map +1 -1
- package/lib/sync/processors/subscription.d.ts +7 -1
- package/lib/sync/processors/subscription.js +196 -135
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.d.ts +1 -1
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/sync/utils.d.ts +66 -2
- package/lib/sync/utils.js +264 -16
- package/lib/sync/utils.js.map +1 -1
- package/lib/types.d.ts +9 -1
- package/lib/types.js.map +1 -1
- package/lib/util.d.ts +16 -0
- package/lib/util.js +31 -2
- package/lib/util.js.map +1 -1
- package/lib-esm/datastore/datastore.d.ts +29 -3
- package/lib-esm/datastore/datastore.js +310 -149
- package/lib-esm/datastore/datastore.js.map +1 -1
- package/lib-esm/predicates/index.d.ts +77 -7
- package/lib-esm/predicates/index.js +143 -123
- package/lib-esm/predicates/index.js.map +1 -1
- package/lib-esm/predicates/next.d.ts +51 -10
- package/lib-esm/predicates/next.js +111 -91
- package/lib-esm/predicates/next.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +138 -535
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +28 -29
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +489 -884
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +134 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js +437 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/lib-esm/storage/relationship.d.ts +9 -0
- package/lib-esm/storage/relationship.js +9 -0
- package/lib-esm/storage/relationship.js.map +1 -1
- package/lib-esm/storage/storage.d.ts +1 -1
- package/lib-esm/storage/storage.js +4 -3
- package/lib-esm/storage/storage.js.map +1 -1
- package/lib-esm/sync/index.d.ts +15 -1
- package/lib-esm/sync/index.js +82 -15
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/outbox.js +14 -7
- package/lib-esm/sync/outbox.js.map +1 -1
- package/lib-esm/sync/processors/mutation.d.ts +10 -1
- package/lib-esm/sync/processors/mutation.js +33 -12
- package/lib-esm/sync/processors/mutation.js.map +1 -1
- package/lib-esm/sync/processors/subscription.d.ts +7 -1
- package/lib-esm/sync/processors/subscription.js +197 -136
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.d.ts +1 -1
- package/lib-esm/sync/processors/sync.js.map +1 -1
- package/lib-esm/sync/utils.d.ts +66 -2
- package/lib-esm/sync/utils.js +261 -18
- package/lib-esm/sync/utils.js.map +1 -1
- package/lib-esm/types.d.ts +9 -1
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +16 -0
- package/lib-esm/util.js +32 -3
- package/lib-esm/util.js.map +1 -1
- package/package.json +12 -11
- package/src/datastore/datastore.ts +288 -159
- package/src/predicates/index.ts +145 -175
- package/src/predicates/next.ts +114 -81
- package/src/storage/adapter/AsyncStorageAdapter.ts +97 -563
- package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
- package/src/storage/adapter/IndexedDBAdapter.ts +318 -770
- package/src/storage/adapter/StorageAdapterBase.ts +545 -0
- package/src/storage/relationship.ts +9 -0
- package/src/storage/storage.ts +12 -9
- package/src/sync/index.ts +108 -20
- package/src/sync/outbox.ts +17 -11
- package/src/sync/processors/mutation.ts +35 -4
- package/src/sync/processors/subscription.ts +124 -10
- package/src/sync/processors/sync.ts +4 -1
- package/src/sync/utils.ts +285 -15
- package/src/types.ts +15 -2
- package/src/util.ts +40 -1
- 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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
391
|
-
|
|
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:
|
|
401
|
-
schema:
|
|
402
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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:
|
|
916
|
-
if (!model ||
|
|
987
|
+
set(model: T | undefined | null) {
|
|
988
|
+
if (!(typeof model === 'object' || typeof model === 'undefined'))
|
|
989
|
+
return;
|
|
917
990
|
|
|
918
|
-
//
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
-
|
|
930
|
-
|
|
1004
|
+
throw new Error(msg);
|
|
1005
|
+
}
|
|
931
1006
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1184
|
-
|
|
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
|
-
*
|
|
1377
|
-
*
|
|
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.
|
|
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:
|
|
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
|
|
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.
|
|
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>(
|
|
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)
|
|
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:
|
|
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
|
|
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
|
|
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)) {
|