@aws-amplify/datastore 3.4.8 → 3.4.9-in-app-messaging.48
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/CHANGELOG.md +104 -445
- package/dist/aws-amplify-datastore.js +28167 -13268
- package/dist/aws-amplify-datastore.js.map +1 -1
- package/dist/aws-amplify-datastore.min.js +8 -8
- package/dist/aws-amplify-datastore.min.js.map +1 -1
- package/lib/.tsbuildinfo +1 -1
- package/lib/datastore/datastore.d.ts +4 -1
- package/lib/datastore/datastore.js +129 -4
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/sync/index.d.ts +3 -1
- package/lib/sync/index.js +10 -1
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/processors/subscription.js +2 -2
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.d.ts +1 -3
- package/lib/sync/processors/sync.js +8 -19
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/types.d.ts +14 -0
- package/lib/types.js +5 -0
- package/lib/types.js.map +1 -1
- package/lib/util.d.ts +23 -1
- package/lib/util.js +77 -0
- package/lib/util.js.map +1 -1
- package/lib-esm/.tsbuildinfo +1 -1
- package/lib-esm/datastore/datastore.d.ts +4 -1
- package/lib-esm/datastore/datastore.js +130 -5
- package/lib-esm/datastore/datastore.js.map +1 -1
- package/lib-esm/sync/index.d.ts +3 -1
- package/lib-esm/sync/index.js +11 -2
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/processors/subscription.js +2 -2
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.d.ts +1 -3
- package/lib-esm/sync/processors/sync.js +8 -19
- package/lib-esm/sync/processors/sync.js.map +1 -1
- package/lib-esm/types.d.ts +14 -0
- package/lib-esm/types.js +5 -0
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +23 -1
- package/lib-esm/util.js +78 -1
- package/lib-esm/util.js.map +1 -1
- package/package.json +7 -7
- package/src/datastore/datastore.ts +267 -121
- package/src/sync/index.ts +19 -3
- package/src/sync/processors/subscription.ts +1 -1
- package/src/sync/processors/sync.ts +1 -17
- package/src/types.ts +31 -12
- package/src/util.ts +80 -0
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
SchemaNamespace,
|
|
45
45
|
SchemaNonModel,
|
|
46
46
|
SubscriptionMessage,
|
|
47
|
+
DataStoreSnapshot,
|
|
47
48
|
SyncConflict,
|
|
48
49
|
SyncError,
|
|
49
50
|
TypeConstructorMap,
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
AuthModeStrategyType,
|
|
53
54
|
isNonModelFieldType,
|
|
54
55
|
isModelFieldType,
|
|
56
|
+
ObserveQueryOptions,
|
|
55
57
|
} from '../types';
|
|
56
58
|
import {
|
|
57
59
|
DATASTORE,
|
|
@@ -65,6 +67,8 @@ import {
|
|
|
65
67
|
USER,
|
|
66
68
|
isNullOrUndefined,
|
|
67
69
|
registerNonModelClass,
|
|
70
|
+
sortCompareFunction,
|
|
71
|
+
DeferredCallbackResolver,
|
|
68
72
|
} from '../util';
|
|
69
73
|
|
|
70
74
|
setAutoFreeze(true);
|
|
@@ -115,7 +119,7 @@ const isValidModelConstructor = <T extends PersistentModel>(
|
|
|
115
119
|
return isModelConstructor(obj) && modelNamespaceMap.has(obj);
|
|
116
120
|
};
|
|
117
121
|
|
|
118
|
-
const namespaceResolver: NamespaceResolver = modelConstructor =>
|
|
122
|
+
const namespaceResolver: NamespaceResolver = (modelConstructor) =>
|
|
119
123
|
modelNamespaceMap.get(modelConstructor);
|
|
120
124
|
|
|
121
125
|
// exporting syncClasses for testing outbox.test.ts
|
|
@@ -160,7 +164,7 @@ const initSchema = (userSchema: Schema) => {
|
|
|
160
164
|
version: userSchema.version,
|
|
161
165
|
};
|
|
162
166
|
|
|
163
|
-
Object.keys(schema.namespaces).forEach(namespace => {
|
|
167
|
+
Object.keys(schema.namespaces).forEach((namespace) => {
|
|
164
168
|
const [relations, keys] = establishRelationAndKeys(
|
|
165
169
|
schema.namespaces[namespace]
|
|
166
170
|
);
|
|
@@ -170,17 +174,17 @@ const initSchema = (userSchema: Schema) => {
|
|
|
170
174
|
|
|
171
175
|
const modelAssociations = new Map<string, string[]>();
|
|
172
176
|
|
|
173
|
-
Object.values(schema.namespaces[namespace].models).forEach(model => {
|
|
177
|
+
Object.values(schema.namespaces[namespace].models).forEach((model) => {
|
|
174
178
|
const connectedModels: string[] = [];
|
|
175
179
|
|
|
176
180
|
Object.values(model.fields)
|
|
177
181
|
.filter(
|
|
178
|
-
field =>
|
|
182
|
+
(field) =>
|
|
179
183
|
field.association &&
|
|
180
184
|
field.association.connectionType === 'BELONGS_TO' &&
|
|
181
185
|
(<ModelFieldType>field.type).model !== model.name
|
|
182
186
|
)
|
|
183
|
-
.forEach(field =>
|
|
187
|
+
.forEach((field) =>
|
|
184
188
|
connectedModels.push((<ModelFieldType>field.type).model)
|
|
185
189
|
);
|
|
186
190
|
|
|
@@ -204,12 +208,12 @@ const initSchema = (userSchema: Schema) => {
|
|
|
204
208
|
for (const modelName of Array.from(modelAssociations.keys())) {
|
|
205
209
|
const parents = modelAssociations.get(modelName);
|
|
206
210
|
|
|
207
|
-
if (parents.every(x => result.has(x))) {
|
|
211
|
+
if (parents.every((x) => result.has(x))) {
|
|
208
212
|
result.set(modelName, parents);
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
Array.from(result.keys()).forEach(x => modelAssociations.delete(x));
|
|
216
|
+
Array.from(result.keys()).forEach((x) => modelAssociations.delete(x));
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
schema.namespaces[namespace].modelTopologicalOrdering = result;
|
|
@@ -218,9 +222,9 @@ const initSchema = (userSchema: Schema) => {
|
|
|
218
222
|
return userClasses;
|
|
219
223
|
};
|
|
220
224
|
|
|
221
|
-
const createTypeClasses: (
|
|
222
|
-
namespace
|
|
223
|
-
) =>
|
|
225
|
+
const createTypeClasses: (namespace: SchemaNamespace) => TypeConstructorMap = (
|
|
226
|
+
namespace
|
|
227
|
+
) => {
|
|
224
228
|
const classes: TypeConstructorMap = {};
|
|
225
229
|
|
|
226
230
|
Object.entries(namespace.models).forEach(([modelName, modelDefinition]) => {
|
|
@@ -254,108 +258,101 @@ function modelInstanceCreator<T extends PersistentModel = PersistentModel>(
|
|
|
254
258
|
return <T>new modelConstructor(init);
|
|
255
259
|
}
|
|
256
260
|
|
|
257
|
-
const validateModelFields =
|
|
258
|
-
k: string,
|
|
259
|
-
|
|
260
|
-
) => {
|
|
261
|
-
const fieldDefinition = modelDefinition.fields[k];
|
|
261
|
+
const validateModelFields =
|
|
262
|
+
(modelDefinition: SchemaModel | SchemaNonModel) => (k: string, v: any) => {
|
|
263
|
+
const fieldDefinition = modelDefinition.fields[k];
|
|
262
264
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
isRequired,
|
|
267
|
-
isArrayNullable,
|
|
268
|
-
name,
|
|
269
|
-
isArray,
|
|
270
|
-
} = fieldDefinition;
|
|
265
|
+
if (fieldDefinition !== undefined) {
|
|
266
|
+
const { type, isRequired, isArrayNullable, name, isArray } =
|
|
267
|
+
fieldDefinition;
|
|
271
268
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
269
|
+
if (
|
|
270
|
+
((!isArray && isRequired) || (isArray && !isArrayNullable)) &&
|
|
271
|
+
(v === null || v === undefined)
|
|
272
|
+
) {
|
|
273
|
+
throw new Error(`Field ${name} is required`);
|
|
274
|
+
}
|
|
278
275
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
if (isGraphQLScalarType(type)) {
|
|
277
|
+
const jsType = GraphQLScalarType.getJSType(type);
|
|
278
|
+
const validateScalar = GraphQLScalarType.getValidationFunction(type);
|
|
282
279
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (typeof v === 'string') {
|
|
288
|
-
try {
|
|
289
|
-
JSON.parse(v);
|
|
280
|
+
if (type === 'AWSJSON') {
|
|
281
|
+
if (typeof v === jsType) {
|
|
290
282
|
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
283
|
+
}
|
|
284
|
+
if (typeof v === 'string') {
|
|
285
|
+
try {
|
|
286
|
+
JSON.parse(v);
|
|
287
|
+
return;
|
|
288
|
+
} catch (error) {
|
|
289
|
+
throw new Error(`Field ${name} is an invalid JSON object. ${v}`);
|
|
290
|
+
}
|
|
293
291
|
}
|
|
294
292
|
}
|
|
295
|
-
}
|
|
296
293
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
294
|
+
if (isArray) {
|
|
295
|
+
let errorTypeText: string = jsType;
|
|
296
|
+
if (!isRequired) {
|
|
297
|
+
errorTypeText = `${jsType} | null | undefined`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!Array.isArray(v) && !isArrayNullable) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Field ${name} should be of type [${errorTypeText}], ${typeof v} received. ${v}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (
|
|
307
|
+
!isNullOrUndefined(v) &&
|
|
308
|
+
(<[]>v).some((e) =>
|
|
309
|
+
isNullOrUndefined(e) ? isRequired : typeof e !== jsType
|
|
310
|
+
)
|
|
311
|
+
) {
|
|
312
|
+
const elemTypes = (<[]>v)
|
|
313
|
+
.map((e) => (e === null ? 'null' : typeof e))
|
|
314
|
+
.join(',');
|
|
302
315
|
|
|
303
|
-
|
|
316
|
+
throw new Error(
|
|
317
|
+
`All elements in the ${name} array should be of type ${errorTypeText}, [${elemTypes}] received. ${v}`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (validateScalar && !isNullOrUndefined(v)) {
|
|
322
|
+
const validationStatus = (<[]>v).map((e) => {
|
|
323
|
+
if (!isNullOrUndefined(e)) {
|
|
324
|
+
return validateScalar(e);
|
|
325
|
+
} else if (isNullOrUndefined(e) && !isRequired) {
|
|
326
|
+
return true;
|
|
327
|
+
} else {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (!validationStatus.every((s) => s)) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
`All elements in the ${name} array should be of type ${type}, validation failed for one or more elements. ${v}`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} else if (!isRequired && v === undefined) {
|
|
339
|
+
return;
|
|
340
|
+
} else if (typeof v !== jsType && v !== null) {
|
|
304
341
|
throw new Error(
|
|
305
|
-
`Field ${name} should be of type
|
|
342
|
+
`Field ${name} should be of type ${jsType}, ${typeof v} received. ${v}`
|
|
306
343
|
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (
|
|
344
|
+
} else if (
|
|
310
345
|
!isNullOrUndefined(v) &&
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
)
|
|
346
|
+
validateScalar &&
|
|
347
|
+
!validateScalar(v)
|
|
314
348
|
) {
|
|
315
|
-
const elemTypes = (<[]>v)
|
|
316
|
-
.map(e => (e === null ? 'null' : typeof e))
|
|
317
|
-
.join(',');
|
|
318
|
-
|
|
319
349
|
throw new Error(
|
|
320
|
-
`
|
|
350
|
+
`Field ${name} should be of type ${type}, validation failed. ${v}`
|
|
321
351
|
);
|
|
322
352
|
}
|
|
323
|
-
|
|
324
|
-
if (validateScalar && !isNullOrUndefined(v)) {
|
|
325
|
-
const validationStatus = (<[]>v).map(e => {
|
|
326
|
-
if (!isNullOrUndefined(e)) {
|
|
327
|
-
return validateScalar(e);
|
|
328
|
-
} else if (isNullOrUndefined(e) && !isRequired) {
|
|
329
|
-
return true;
|
|
330
|
-
} else {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
if (!validationStatus.every(s => s)) {
|
|
336
|
-
throw new Error(
|
|
337
|
-
`All elements in the ${name} array should be of type ${type}, validation failed for one or more elements. ${v}`
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
} else if (!isRequired && v === undefined) {
|
|
342
|
-
return;
|
|
343
|
-
} else if (typeof v !== jsType && v !== null) {
|
|
344
|
-
throw new Error(
|
|
345
|
-
`Field ${name} should be of type ${jsType}, ${typeof v} received. ${v}`
|
|
346
|
-
);
|
|
347
|
-
} else if (
|
|
348
|
-
!isNullOrUndefined(v) &&
|
|
349
|
-
validateScalar &&
|
|
350
|
-
!validateScalar(v)
|
|
351
|
-
) {
|
|
352
|
-
throw new Error(
|
|
353
|
-
`Field ${name} should be of type ${type}, validation failed. ${v}`
|
|
354
|
-
);
|
|
355
353
|
}
|
|
356
354
|
}
|
|
357
|
-
}
|
|
358
|
-
};
|
|
355
|
+
};
|
|
359
356
|
|
|
360
357
|
const castInstanceType = (
|
|
361
358
|
modelDefinition: SchemaModel | SchemaNonModel,
|
|
@@ -410,11 +407,10 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
410
407
|
(draft: Draft<T & ModelInstanceMetadata>) => {
|
|
411
408
|
initializeInstance(init, modelDefinition, draft);
|
|
412
409
|
|
|
413
|
-
const modelInstanceMetadata: ModelInstanceMetadata =
|
|
414
|
-
init
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
: <ModelInstanceMetadata>{};
|
|
410
|
+
const modelInstanceMetadata: ModelInstanceMetadata =
|
|
411
|
+
instancesMetadata.has(init)
|
|
412
|
+
? <ModelInstanceMetadata>(<unknown>init)
|
|
413
|
+
: <ModelInstanceMetadata>{};
|
|
418
414
|
const {
|
|
419
415
|
id: _id,
|
|
420
416
|
_version,
|
|
@@ -459,7 +455,7 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
459
455
|
let patches;
|
|
460
456
|
const model = produce(
|
|
461
457
|
source,
|
|
462
|
-
draft => {
|
|
458
|
+
(draft) => {
|
|
463
459
|
fn(<MutableModel<T>>(draft as unknown));
|
|
464
460
|
draft.id = source.id;
|
|
465
461
|
const modelValidator = validateModelFields(modelDefinition);
|
|
@@ -469,7 +465,7 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
469
465
|
modelValidator(k, parsedValue);
|
|
470
466
|
});
|
|
471
467
|
},
|
|
472
|
-
p => (patches = p)
|
|
468
|
+
(p) => (patches = p)
|
|
473
469
|
);
|
|
474
470
|
|
|
475
471
|
if (patches.length) {
|
|
@@ -484,7 +480,7 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
484
480
|
// to gain access to `modelInstanceCreator` and `clazz` for persisting IDs from server to client.
|
|
485
481
|
static fromJSON(json: T | T[]) {
|
|
486
482
|
if (Array.isArray(json)) {
|
|
487
|
-
return json.map(init => this.fromJSON(init));
|
|
483
|
+
return json.map((init) => this.fromJSON(init));
|
|
488
484
|
}
|
|
489
485
|
|
|
490
486
|
const instance = modelInstanceCreator(clazz, json);
|
|
@@ -512,7 +508,7 @@ const checkReadOnlyPropertyOnCreate = <T extends PersistentModel>(
|
|
|
512
508
|
const modelKeys = Object.keys(draft);
|
|
513
509
|
const { fields } = modelDefinition;
|
|
514
510
|
|
|
515
|
-
modelKeys.forEach(key => {
|
|
511
|
+
modelKeys.forEach((key) => {
|
|
516
512
|
if (fields[key] && fields[key].isReadOnly) {
|
|
517
513
|
throw new Error(`${key} is read-only.`);
|
|
518
514
|
}
|
|
@@ -523,7 +519,7 @@ const checkReadOnlyPropertyOnUpdate = (
|
|
|
523
519
|
patches: Patch[],
|
|
524
520
|
modelDefinition: SchemaModel
|
|
525
521
|
) => {
|
|
526
|
-
const patchArray = patches.map(p => [p.path[0], p.value]);
|
|
522
|
+
const patchArray = patches.map((p) => [p.path[0], p.value]);
|
|
527
523
|
const { fields } = modelDefinition;
|
|
528
524
|
|
|
529
525
|
patchArray.forEach(([key, val]) => {
|
|
@@ -610,16 +606,15 @@ async function checkSchemaVersion(
|
|
|
610
606
|
storage: Storage,
|
|
611
607
|
version: string
|
|
612
608
|
): Promise<void> {
|
|
613
|
-
const Setting =
|
|
614
|
-
Setting
|
|
615
|
-
>;
|
|
609
|
+
const Setting =
|
|
610
|
+
dataStoreClasses.Setting as PersistentModelConstructor<Setting>;
|
|
616
611
|
|
|
617
612
|
const modelDefinition = schema.namespaces[DATASTORE].models.Setting;
|
|
618
613
|
|
|
619
|
-
await storage.runExclusive(async s => {
|
|
614
|
+
await storage.runExclusive(async (s) => {
|
|
620
615
|
const [schemaVersionSetting] = await s.query(
|
|
621
616
|
Setting,
|
|
622
|
-
ModelPredicateCreator.createFromExisting(modelDefinition, c =>
|
|
617
|
+
ModelPredicateCreator.createFromExisting(modelDefinition, (c) =>
|
|
623
618
|
// @ts-ignore Argument of type '"eq"' is not assignable to parameter of type 'never'.
|
|
624
619
|
c.key('eq', SETTING_SCHEMA_VERSION)
|
|
625
620
|
),
|
|
@@ -700,10 +695,8 @@ class DataStore {
|
|
|
700
695
|
private sync: SyncEngine;
|
|
701
696
|
private syncPageSize: number;
|
|
702
697
|
private syncExpressions: SyncExpression[];
|
|
703
|
-
private syncPredicates: WeakMap<
|
|
704
|
-
SchemaModel,
|
|
705
|
-
ModelPredicate<any>
|
|
706
|
-
> = new WeakMap<SchemaModel, ModelPredicate<any>>();
|
|
698
|
+
private syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>> =
|
|
699
|
+
new WeakMap<SchemaModel, ModelPredicate<any>>();
|
|
707
700
|
private sessionId: string;
|
|
708
701
|
private storageAdapter: Adapter;
|
|
709
702
|
|
|
@@ -781,7 +774,7 @@ class DataStore {
|
|
|
781
774
|
data,
|
|
782
775
|
});
|
|
783
776
|
},
|
|
784
|
-
error: err => {
|
|
777
|
+
error: (err) => {
|
|
785
778
|
logger.warn('Sync error', err);
|
|
786
779
|
this.initReject();
|
|
787
780
|
},
|
|
@@ -908,7 +901,7 @@ class DataStore {
|
|
|
908
901
|
condition
|
|
909
902
|
);
|
|
910
903
|
|
|
911
|
-
const [savedModel] = await this.storage.runExclusive(async s => {
|
|
904
|
+
const [savedModel] = await this.storage.runExclusive(async (s) => {
|
|
912
905
|
await s.save(model, producedCondition, undefined, patchesTuple);
|
|
913
906
|
|
|
914
907
|
return s.query(
|
|
@@ -1126,7 +1119,7 @@ class DataStore {
|
|
|
1126
1119
|
);
|
|
1127
1120
|
}
|
|
1128
1121
|
|
|
1129
|
-
return new Observable<SubscriptionMessage<T>>(observer => {
|
|
1122
|
+
return new Observable<SubscriptionMessage<T>>((observer) => {
|
|
1130
1123
|
let handle: ZenObservable.Subscription;
|
|
1131
1124
|
|
|
1132
1125
|
(async () => {
|
|
@@ -1146,6 +1139,150 @@ class DataStore {
|
|
|
1146
1139
|
});
|
|
1147
1140
|
};
|
|
1148
1141
|
|
|
1142
|
+
observeQuery: {
|
|
1143
|
+
<T extends PersistentModel>(
|
|
1144
|
+
modelConstructor: PersistentModelConstructor<T>,
|
|
1145
|
+
criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
|
|
1146
|
+
paginationProducer?: ObserveQueryOptions<T>
|
|
1147
|
+
): Observable<DataStoreSnapshot<T>>;
|
|
1148
|
+
} = <T extends PersistentModel = PersistentModel>(
|
|
1149
|
+
model: PersistentModelConstructor<T>,
|
|
1150
|
+
criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
|
|
1151
|
+
options?: ObserveQueryOptions<T>
|
|
1152
|
+
): Observable<DataStoreSnapshot<T>> => {
|
|
1153
|
+
return new Observable<DataStoreSnapshot<T>>((observer) => {
|
|
1154
|
+
const items = new Map<string, T>();
|
|
1155
|
+
const itemsChanged = new Map<string, T>();
|
|
1156
|
+
let deletedItemIds: string[] = [];
|
|
1157
|
+
let handle: ZenObservable.Subscription;
|
|
1158
|
+
|
|
1159
|
+
const generateAndEmitSnapshot = (): void => {
|
|
1160
|
+
const snapshot = generateSnapshot();
|
|
1161
|
+
emitSnapshot(snapshot);
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
// a mechanism to return data after X amount of seconds OR after the
|
|
1165
|
+
// "limit" (itemsChanged >= this.syncPageSize) has been reached, whichever comes first
|
|
1166
|
+
const limitTimerRace = new DeferredCallbackResolver({
|
|
1167
|
+
callback: generateAndEmitSnapshot,
|
|
1168
|
+
errorHandler: observer.error,
|
|
1169
|
+
maxInterval: 2000,
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
const { sort } = options || {};
|
|
1173
|
+
const sortOptions = sort ? { sort } : undefined;
|
|
1174
|
+
|
|
1175
|
+
(async () => {
|
|
1176
|
+
try {
|
|
1177
|
+
// first, query and return any locally-available records
|
|
1178
|
+
(await this.query(model, criteria, sortOptions)).forEach((item) =>
|
|
1179
|
+
items.set(item.id, item)
|
|
1180
|
+
);
|
|
1181
|
+
|
|
1182
|
+
// observe the model and send a stream of updates (debounced)
|
|
1183
|
+
handle = this.observe(
|
|
1184
|
+
model,
|
|
1185
|
+
// @ts-ignore TODO: fix this TSlint error
|
|
1186
|
+
criteria
|
|
1187
|
+
).subscribe(({ element, model, opType }) => {
|
|
1188
|
+
// Flag items which have been recently deleted
|
|
1189
|
+
// NOTE: Merging of separate operations to the same model instance is handled upstream
|
|
1190
|
+
// in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
|
|
1191
|
+
// depends on the LATEST record (for a given id).
|
|
1192
|
+
if (opType === 'DELETE') {
|
|
1193
|
+
deletedItemIds.push(element.id);
|
|
1194
|
+
} else {
|
|
1195
|
+
itemsChanged.set(element.id, element);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const isSynced = this.sync.getModelSyncedStatus(model);
|
|
1199
|
+
|
|
1200
|
+
const limit =
|
|
1201
|
+
itemsChanged.size - deletedItemIds.length >= this.syncPageSize;
|
|
1202
|
+
|
|
1203
|
+
if (limit || isSynced) {
|
|
1204
|
+
limitTimerRace.resolve();
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// kicks off every subsequent race as results sync down
|
|
1208
|
+
limitTimerRace.start();
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
// returns a set of initial/locally-available results
|
|
1212
|
+
generateAndEmitSnapshot();
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
observer.error(err);
|
|
1215
|
+
}
|
|
1216
|
+
})();
|
|
1217
|
+
|
|
1218
|
+
// TODO: abstract this function into a util file to be able to write better unit tests
|
|
1219
|
+
const generateSnapshot = (): DataStoreSnapshot<T> => {
|
|
1220
|
+
const isSynced = this.sync.getModelSyncedStatus(model);
|
|
1221
|
+
const itemsArray = [
|
|
1222
|
+
...Array.from(items.values()),
|
|
1223
|
+
...Array.from(itemsChanged.values()),
|
|
1224
|
+
];
|
|
1225
|
+
|
|
1226
|
+
if (options?.sort) {
|
|
1227
|
+
sortItems(itemsArray);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
items.clear();
|
|
1231
|
+
itemsArray.forEach((item) => items.set(item.id, item));
|
|
1232
|
+
|
|
1233
|
+
// remove deleted items from the final result set
|
|
1234
|
+
deletedItemIds.forEach((id) => items.delete(id));
|
|
1235
|
+
|
|
1236
|
+
return {
|
|
1237
|
+
items: Array.from(items.values()),
|
|
1238
|
+
isSynced,
|
|
1239
|
+
};
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
const emitSnapshot = (snapshot: DataStoreSnapshot<T>): void => {
|
|
1243
|
+
// send the generated snapshot to the primary subscription
|
|
1244
|
+
observer.next(snapshot);
|
|
1245
|
+
|
|
1246
|
+
// reset the changed items sets
|
|
1247
|
+
itemsChanged.clear();
|
|
1248
|
+
deletedItemIds = [];
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1251
|
+
const sortItems = (itemsToSort: T[]): void => {
|
|
1252
|
+
const modelDefinition = getModelDefinition(model);
|
|
1253
|
+
const pagination = this.processPagination(modelDefinition, options);
|
|
1254
|
+
|
|
1255
|
+
const sortPredicates = ModelSortPredicateCreator.getPredicates(
|
|
1256
|
+
pagination.sort
|
|
1257
|
+
);
|
|
1258
|
+
|
|
1259
|
+
if (sortPredicates.length) {
|
|
1260
|
+
const compareFn = sortCompareFunction(sortPredicates);
|
|
1261
|
+
itemsToSort.sort(compareFn);
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
// send one last snapshot when the model is fully synced
|
|
1266
|
+
const hubCallback = ({ payload }): void => {
|
|
1267
|
+
const { event, data } = payload;
|
|
1268
|
+
if (
|
|
1269
|
+
event === ControlMessage.SYNC_ENGINE_MODEL_SYNCED &&
|
|
1270
|
+
data?.model?.name === model.name
|
|
1271
|
+
) {
|
|
1272
|
+
generateAndEmitSnapshot();
|
|
1273
|
+
Hub.remove('api', hubCallback);
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
Hub.listen('datastore', hubCallback);
|
|
1277
|
+
|
|
1278
|
+
return () => {
|
|
1279
|
+
if (handle) {
|
|
1280
|
+
handle.unsubscribe();
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
});
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1149
1286
|
configure = (config: DataStoreConfig = {}) => {
|
|
1150
1287
|
const {
|
|
1151
1288
|
DataStore: configDataStore,
|
|
@@ -1161,7 +1298,10 @@ class DataStore {
|
|
|
1161
1298
|
...configFromAmplify
|
|
1162
1299
|
} = config;
|
|
1163
1300
|
|
|
1164
|
-
this.amplifyConfig = {
|
|
1301
|
+
this.amplifyConfig = {
|
|
1302
|
+
...configFromAmplify,
|
|
1303
|
+
...this.amplifyConfig,
|
|
1304
|
+
};
|
|
1165
1305
|
|
|
1166
1306
|
this.conflictHandler = this.setConflictHandler(config);
|
|
1167
1307
|
this.errorHandler = this.setErrorHandler(config);
|
|
@@ -1194,13 +1334,19 @@ class DataStore {
|
|
|
1194
1334
|
|
|
1195
1335
|
this.maxRecordsToSync =
|
|
1196
1336
|
(configDataStore && configDataStore.maxRecordsToSync) ||
|
|
1197
|
-
|
|
1198
|
-
|
|
1337
|
+
configMaxRecordsToSync ||
|
|
1338
|
+
10000;
|
|
1339
|
+
|
|
1340
|
+
// store on config object, so that Sync, Subscription, and Mutation processors can have access
|
|
1341
|
+
this.amplifyConfig.maxRecordsToSync = this.maxRecordsToSync;
|
|
1199
1342
|
|
|
1200
1343
|
this.syncPageSize =
|
|
1201
1344
|
(configDataStore && configDataStore.syncPageSize) ||
|
|
1202
|
-
|
|
1203
|
-
|
|
1345
|
+
configSyncPageSize ||
|
|
1346
|
+
1000;
|
|
1347
|
+
|
|
1348
|
+
// store on config object, so that Sync, Subscription, and Mutation processors can have access
|
|
1349
|
+
this.amplifyConfig.syncPageSize = this.syncPageSize;
|
|
1204
1350
|
|
|
1205
1351
|
this.fullSyncInterval =
|
|
1206
1352
|
(configDataStore && configDataStore.fullSyncInterval) ||
|
package/src/sync/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
ModelPredicate,
|
|
23
23
|
AuthModeStrategy,
|
|
24
24
|
} from '../types';
|
|
25
|
-
import { exhaustiveCheck, getNow, SYNC } from '../util';
|
|
25
|
+
import { exhaustiveCheck, getNow, SYNC, USER } from '../util';
|
|
26
26
|
import DataStoreConnectivity from './datastoreConnectivity';
|
|
27
27
|
import { ModelMerger } from './merger';
|
|
28
28
|
import { MutationEventOutbox } from './outbox';
|
|
@@ -95,6 +95,16 @@ export class SyncEngine {
|
|
|
95
95
|
private readonly modelMerger: ModelMerger;
|
|
96
96
|
private readonly outbox: MutationEventOutbox;
|
|
97
97
|
private readonly datastoreConnectivity: DataStoreConnectivity;
|
|
98
|
+
private readonly modelSyncedStatus: WeakMap<
|
|
99
|
+
PersistentModelConstructor<any>,
|
|
100
|
+
boolean
|
|
101
|
+
> = new WeakMap();
|
|
102
|
+
|
|
103
|
+
public getModelSyncedStatus(
|
|
104
|
+
modelConstructor: PersistentModelConstructor<any>
|
|
105
|
+
): boolean {
|
|
106
|
+
return this.modelSyncedStatus.get(modelConstructor);
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
constructor(
|
|
100
110
|
private readonly schema: InternalSchema,
|
|
@@ -126,8 +136,6 @@ export class SyncEngine {
|
|
|
126
136
|
|
|
127
137
|
this.syncQueriesProcessor = new SyncProcessor(
|
|
128
138
|
this.schema,
|
|
129
|
-
this.maxRecordsToSync,
|
|
130
|
-
this.syncPageSize,
|
|
131
139
|
this.syncPredicates,
|
|
132
140
|
this.amplifyConfig,
|
|
133
141
|
this.authModeStrategy
|
|
@@ -615,6 +623,8 @@ export class SyncEngine {
|
|
|
615
623
|
|
|
616
624
|
const counts = count.get(modelConstructor);
|
|
617
625
|
|
|
626
|
+
this.modelSyncedStatus.set(modelConstructor, true);
|
|
627
|
+
|
|
618
628
|
observer.next({
|
|
619
629
|
type: ControlMessage.SYNC_ENGINE_MODEL_SYNCED,
|
|
620
630
|
data: {
|
|
@@ -708,6 +718,12 @@ export class SyncEngine {
|
|
|
708
718
|
.filter(({ syncable }) => syncable)
|
|
709
719
|
.forEach(model => {
|
|
710
720
|
models.push([namespace.name, model]);
|
|
721
|
+
if (namespace.name === USER) {
|
|
722
|
+
const modelConstructor = this.userModelClasses[
|
|
723
|
+
model.name
|
|
724
|
+
] as PersistentModelConstructor<any>;
|
|
725
|
+
this.modelSyncedStatus.set(modelConstructor, false);
|
|
726
|
+
}
|
|
711
727
|
});
|
|
712
728
|
});
|
|
713
729
|
|
|
@@ -533,7 +533,7 @@ class SubscriptionProcessor {
|
|
|
533
533
|
})();
|
|
534
534
|
|
|
535
535
|
return () => {
|
|
536
|
-
Object.keys(subscriptions).
|
|
536
|
+
Object.keys(subscriptions).forEach(modelName => {
|
|
537
537
|
subscriptions[modelName][
|
|
538
538
|
TransformerMutationType.CREATE
|
|
539
539
|
].forEach(subscription => subscription.unsubscribe());
|
|
@@ -25,9 +25,6 @@ import {
|
|
|
25
25
|
} from '@aws-amplify/core';
|
|
26
26
|
import { ModelPredicateCreator } from '../../predicates';
|
|
27
27
|
|
|
28
|
-
const DEFAULT_PAGINATION_LIMIT = 1000;
|
|
29
|
-
const DEFAULT_MAX_RECORDS_TO_SYNC = 10000;
|
|
30
|
-
|
|
31
28
|
const opResultDefaults = {
|
|
32
29
|
items: [],
|
|
33
30
|
nextToken: null,
|
|
@@ -41,8 +38,6 @@ class SyncProcessor {
|
|
|
41
38
|
|
|
42
39
|
constructor(
|
|
43
40
|
private readonly schema: InternalSchema,
|
|
44
|
-
private readonly maxRecordsToSync: number = DEFAULT_MAX_RECORDS_TO_SYNC,
|
|
45
|
-
private readonly syncPageSize: number = DEFAULT_PAGINATION_LIMIT,
|
|
46
41
|
private readonly syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>,
|
|
47
42
|
private readonly amplifyConfig: Record<string, any> = {},
|
|
48
43
|
private readonly authModeStrategy: AuthModeStrategy
|
|
@@ -289,19 +284,8 @@ class SyncProcessor {
|
|
|
289
284
|
typesLastSync: Map<SchemaModel, [string, number]>
|
|
290
285
|
): Observable<SyncModelPage> {
|
|
291
286
|
let processing = true;
|
|
292
|
-
|
|
293
|
-
const maxRecordsToSync =
|
|
294
|
-
this.maxRecordsToSync !== undefined
|
|
295
|
-
? this.maxRecordsToSync
|
|
296
|
-
: DEFAULT_MAX_RECORDS_TO_SYNC;
|
|
297
|
-
|
|
298
|
-
const syncPageSize =
|
|
299
|
-
this.syncPageSize !== undefined
|
|
300
|
-
? this.syncPageSize
|
|
301
|
-
: DEFAULT_PAGINATION_LIMIT;
|
|
302
|
-
|
|
287
|
+
const { maxRecordsToSync, syncPageSize } = this.amplifyConfig;
|
|
303
288
|
const parentPromises = new Map<string, Promise<void>>();
|
|
304
|
-
|
|
305
289
|
const observable = new Observable<SyncModelPage>(observer => {
|
|
306
290
|
const sortedTypesLastSyncs = Object.values(this.schema.namespaces).reduce(
|
|
307
291
|
(map, namespace) => {
|