@aws-amplify/datastore 3.12.6-next.13 → 3.12.6-next.32
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 +58 -0
- package/lib/authModeStrategies/multiAuthStrategy.js +17 -64
- package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib/datastore/datastore.js +682 -469
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/index.js +2 -4
- package/lib/index.js.map +1 -1
- package/lib/predicates/index.js +12 -2
- package/lib/predicates/index.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageAdapter.js +393 -298
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.js +97 -122
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/InMemoryStore.js +16 -67
- package/lib/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib/storage/adapter/InMemoryStore.native.js +2 -4
- package/lib/storage/adapter/InMemoryStore.native.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.js +497 -404
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/getDefaultAdapter/index.js +3 -5
- package/lib/storage/adapter/getDefaultAdapter/index.js.map +1 -1
- package/lib/storage/adapter/getDefaultAdapter/index.native.js +2 -4
- package/lib/storage/adapter/getDefaultAdapter/index.native.js.map +1 -1
- package/lib/storage/storage.js +129 -151
- package/lib/storage/storage.js.map +1 -1
- package/lib/sync/datastoreConnectivity.js +13 -17
- package/lib/sync/datastoreConnectivity.js.map +1 -1
- package/lib/sync/datastoreReachability/index.native.js +2 -4
- package/lib/sync/datastoreReachability/index.native.js.map +1 -1
- package/lib/sync/index.js +544 -488
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/merger.js +21 -80
- package/lib/sync/merger.js.map +1 -1
- package/lib/sync/outbox.js +95 -162
- package/lib/sync/outbox.js.map +1 -1
- package/lib/sync/processors/errorMaps.js +4 -34
- package/lib/sync/processors/errorMaps.js.map +1 -1
- package/lib/sync/processors/mutation.js +285 -312
- package/lib/sync/processors/mutation.js.map +1 -1
- package/lib/sync/processors/subscription.js +218 -259
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.js +141 -212
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/sync/utils.js +50 -61
- package/lib/sync/utils.js.map +1 -1
- package/lib/types.js +13 -39
- package/lib/types.js.map +1 -1
- package/lib/util.js +429 -242
- package/lib/util.js.map +1 -1
- package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +11 -0
- package/lib-esm/authModeStrategies/multiAuthStrategy.js +13 -57
- package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib-esm/datastore/datastore.d.ts +107 -17
- package/lib-esm/datastore/datastore.js +649 -433
- package/lib-esm/datastore/datastore.js.map +1 -1
- package/lib-esm/index.d.ts +3 -19
- package/lib-esm/predicates/index.d.ts +3 -2
- package/lib-esm/predicates/index.js +13 -3
- package/lib-esm/predicates/index.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +356 -258
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js +67 -92
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/InMemoryStore.js +1 -52
- package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +26 -4
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +446 -346
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/index.d.ts +1 -1
- package/lib-esm/storage/storage.d.ts +1 -1
- package/lib-esm/storage/storage.js +94 -113
- package/lib-esm/storage/storage.js.map +1 -1
- package/lib-esm/sync/datastoreConnectivity.d.ts +1 -0
- package/lib-esm/sync/datastoreConnectivity.js +10 -11
- package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
- package/lib-esm/sync/index.d.ts +31 -5
- package/lib-esm/sync/index.js +525 -466
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/merger.d.ts +9 -3
- package/lib-esm/sync/merger.js +14 -73
- package/lib-esm/sync/merger.js.map +1 -1
- package/lib-esm/sync/outbox.d.ts +2 -2
- package/lib-esm/sync/outbox.js +79 -146
- package/lib-esm/sync/outbox.js.map +1 -1
- package/lib-esm/sync/processors/errorMaps.js +1 -31
- package/lib-esm/sync/processors/errorMaps.js.map +1 -1
- package/lib-esm/sync/processors/mutation.d.ts +2 -0
- package/lib-esm/sync/processors/mutation.js +271 -295
- package/lib-esm/sync/processors/mutation.js.map +1 -1
- package/lib-esm/sync/processors/subscription.d.ts +2 -0
- package/lib-esm/sync/processors/subscription.js +214 -245
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.d.ts +2 -1
- package/lib-esm/sync/processors/sync.js +127 -195
- package/lib-esm/sync/processors/sync.js.map +1 -1
- package/lib-esm/sync/utils.d.ts +3 -2
- package/lib-esm/sync/utils.js +45 -57
- package/lib-esm/sync/utils.js.map +1 -1
- package/lib-esm/types.d.ts +65 -26
- package/lib-esm/types.js +10 -38
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +67 -24
- package/lib-esm/util.js +420 -233
- package/lib-esm/util.js.map +1 -1
- package/package.json +14 -7
- package/src/authModeStrategies/multiAuthStrategy.ts +12 -1
- package/src/datastore/datastore.ts +798 -397
- package/src/predicates/index.ts +32 -10
- package/src/storage/adapter/AsyncStorageAdapter.ts +309 -93
- package/src/storage/adapter/AsyncStorageDatabase.ts +74 -26
- package/src/storage/adapter/IndexedDBAdapter.ts +358 -134
- package/src/storage/adapter/index.ts +1 -1
- package/src/storage/storage.ts +69 -22
- package/src/sync/datastoreConnectivity.ts +6 -0
- package/src/sync/index.ts +521 -412
- package/src/sync/merger.ts +20 -4
- package/src/sync/outbox.ts +22 -9
- package/src/sync/processors/mutation.ts +188 -150
- package/src/sync/processors/subscription.ts +289 -253
- package/src/sync/processors/sync.ts +151 -138
- package/src/sync/utils.ts +67 -12
- package/src/types.ts +182 -30
- package/src/util.ts +505 -176
- package/build.js +0 -5
- package/dist/aws-amplify-datastore.js +0 -98255
- package/dist/aws-amplify-datastore.js.map +0 -1
- package/dist/aws-amplify-datastore.min.js +0 -66
- package/dist/aws-amplify-datastore.min.js.map +0 -1
- package/index.js +0 -7
- package/lib/authModeStrategies/defaultAuthStrategy.d.ts +0 -2
- package/lib/authModeStrategies/index.d.ts +0 -2
- package/lib/authModeStrategies/multiAuthStrategy.d.ts +0 -2
- package/lib/datastore/datastore.d.ts +0 -66
- package/lib/index.d.ts +0 -31
- package/lib/predicates/index.d.ts +0 -15
- package/lib/predicates/sort.d.ts +0 -8
- package/lib/ssr/index.d.ts +0 -3
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +0 -40
- package/lib/storage/adapter/AsyncStorageDatabase.d.ts +0 -29
- package/lib/storage/adapter/InMemoryStore.d.ts +0 -11
- package/lib/storage/adapter/InMemoryStore.native.d.ts +0 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +0 -37
- package/lib/storage/adapter/getDefaultAdapter/index.d.ts +0 -3
- package/lib/storage/adapter/getDefaultAdapter/index.native.d.ts +0 -3
- package/lib/storage/adapter/index.d.ts +0 -9
- package/lib/storage/storage.d.ts +0 -49
- package/lib/sync/datastoreConnectivity.d.ts +0 -15
- package/lib/sync/datastoreReachability/index.d.ts +0 -3
- package/lib/sync/datastoreReachability/index.native.d.ts +0 -3
- package/lib/sync/index.d.ts +0 -63
- package/lib/sync/merger.d.ts +0 -11
- package/lib/sync/outbox.d.ts +0 -27
- package/lib/sync/processors/errorMaps.d.ts +0 -17
- package/lib/sync/processors/mutation.d.ts +0 -56
- package/lib/sync/processors/subscription.d.ts +0 -31
- package/lib/sync/processors/sync.d.ts +0 -27
- package/lib/sync/utils.d.ts +0 -41
- package/lib/types.d.ts +0 -462
- package/lib/util.d.ts +0 -113
- package/webpack.config.dev.js +0 -6
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import API from '@aws-amplify/api';
|
|
2
|
-
import { Amplify, ConsoleLogger as Logger, Hub, JS } from '@aws-amplify/core';
|
|
1
|
+
import { API } from '@aws-amplify/api';
|
|
3
2
|
import { Auth } from '@aws-amplify/auth';
|
|
4
|
-
import Cache from '@aws-amplify/cache';
|
|
3
|
+
import { BrowserStorageCache as Cache } from '@aws-amplify/cache';
|
|
4
|
+
import {
|
|
5
|
+
Amplify,
|
|
6
|
+
ConsoleLogger as Logger,
|
|
7
|
+
Hub,
|
|
8
|
+
browserOrNode,
|
|
9
|
+
BackgroundProcessManager,
|
|
10
|
+
} from '@aws-amplify/core';
|
|
5
11
|
import {
|
|
6
12
|
Draft,
|
|
7
13
|
immerable,
|
|
@@ -29,6 +35,7 @@ import {
|
|
|
29
35
|
GraphQLScalarType,
|
|
30
36
|
InternalSchema,
|
|
31
37
|
isGraphQLScalarType,
|
|
38
|
+
isSchemaModelWithAttributes,
|
|
32
39
|
ModelFieldType,
|
|
33
40
|
ModelInit,
|
|
34
41
|
ModelInstanceMetadata,
|
|
@@ -57,10 +64,18 @@ import {
|
|
|
57
64
|
isNonModelFieldType,
|
|
58
65
|
isModelFieldType,
|
|
59
66
|
ObserveQueryOptions,
|
|
67
|
+
ManagedIdentifier,
|
|
68
|
+
PersistentModelMetaData,
|
|
69
|
+
IdentifierFieldOrIdentifierObject,
|
|
70
|
+
isIdentifierObject,
|
|
60
71
|
AmplifyContext,
|
|
61
72
|
} from '../types';
|
|
73
|
+
// tslint:disable:no-duplicate-imports
|
|
74
|
+
import type { __modelMeta__ } from '../types';
|
|
75
|
+
|
|
62
76
|
import {
|
|
63
77
|
DATASTORE,
|
|
78
|
+
errorMessages,
|
|
64
79
|
establishRelationAndKeys,
|
|
65
80
|
exhaustiveCheck,
|
|
66
81
|
isModelConstructor,
|
|
@@ -73,9 +88,15 @@ import {
|
|
|
73
88
|
registerNonModelClass,
|
|
74
89
|
sortCompareFunction,
|
|
75
90
|
DeferredCallbackResolver,
|
|
91
|
+
extractPrimaryKeyFieldNames,
|
|
92
|
+
extractPrimaryKeysAndValues,
|
|
93
|
+
isIdManaged,
|
|
94
|
+
isIdOptionallyManaged,
|
|
76
95
|
validatePredicate,
|
|
77
96
|
mergePatches,
|
|
78
97
|
} from '../util';
|
|
98
|
+
import { getIdentifierValue } from '../sync/utils';
|
|
99
|
+
import DataStoreConnectivity from '../sync/datastoreConnectivity';
|
|
79
100
|
|
|
80
101
|
setAutoFreeze(true);
|
|
81
102
|
enablePatches();
|
|
@@ -83,13 +104,18 @@ enablePatches();
|
|
|
83
104
|
const logger = new Logger('DataStore');
|
|
84
105
|
|
|
85
106
|
const ulid = monotonicUlidFactory(Date.now());
|
|
86
|
-
const { isNode } =
|
|
107
|
+
const { isNode } = browserOrNode();
|
|
87
108
|
|
|
109
|
+
type SettingMetaData = {
|
|
110
|
+
identifier: ManagedIdentifier<Setting, 'id'>;
|
|
111
|
+
readOnlyFields: never;
|
|
112
|
+
};
|
|
88
113
|
declare class Setting {
|
|
89
|
-
|
|
114
|
+
public readonly [__modelMeta__]: SettingMetaData;
|
|
115
|
+
constructor(init: ModelInit<Setting, SettingMetaData>);
|
|
90
116
|
static copyOf(
|
|
91
117
|
src: Setting,
|
|
92
|
-
mutator: (draft: MutableModel<Setting>) => void | Setting
|
|
118
|
+
mutator: (draft: MutableModel<Setting, SettingMetaData>) => void | Setting
|
|
93
119
|
): Setting;
|
|
94
120
|
public readonly id: string;
|
|
95
121
|
public readonly key: string;
|
|
@@ -228,10 +254,14 @@ const initSchema = (userSchema: Schema) => {
|
|
|
228
254
|
return userClasses;
|
|
229
255
|
};
|
|
230
256
|
|
|
231
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Throws an exception if the schema has *not* been initialized
|
|
259
|
+
* by `initSchema()`.
|
|
232
260
|
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
261
|
+
* **To be called before trying to access schema.**
|
|
262
|
+
*
|
|
263
|
+
* Currently this only needs to be called in `start()` and `clear()` because
|
|
264
|
+
* all other functions will call start first.
|
|
235
265
|
*/
|
|
236
266
|
const checkSchemaInitialized = () => {
|
|
237
267
|
if (schema === undefined) {
|
|
@@ -264,18 +294,31 @@ const createTypeClasses: (
|
|
|
264
294
|
return classes;
|
|
265
295
|
};
|
|
266
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Constructs a model and records it with its metadata in a weakset. Allows for
|
|
299
|
+
* the separate storage of core model fields and Amplify/DataStore metadata
|
|
300
|
+
* fields that the customer app does not want exposed.
|
|
301
|
+
*
|
|
302
|
+
* @param modelConstructor The model constructor.
|
|
303
|
+
* @param init Init data that would normally be passed to the constructor.
|
|
304
|
+
* @returns The initialized model.
|
|
305
|
+
*/
|
|
267
306
|
export declare type ModelInstanceCreator = typeof modelInstanceCreator;
|
|
268
307
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Collection of instantiated models to allow storage of metadata apart from
|
|
310
|
+
* the model visible to the consuming app -- in case the app doesn't have
|
|
311
|
+
* metadata fields (_version, _deleted, etc.) exposed on the model itself.
|
|
312
|
+
*/
|
|
313
|
+
const instancesMetadata = new WeakSet<ModelInit<unknown, unknown>>();
|
|
314
|
+
|
|
315
|
+
function modelInstanceCreator<T extends PersistentModel>(
|
|
273
316
|
modelConstructor: PersistentModelConstructor<T>,
|
|
274
|
-
init:
|
|
317
|
+
init: Partial<T>
|
|
275
318
|
): T {
|
|
276
319
|
instancesMetadata.add(init);
|
|
277
320
|
|
|
278
|
-
return
|
|
321
|
+
return new modelConstructor(<ModelInit<T, PersistentModelMetaData<T>>>init);
|
|
279
322
|
}
|
|
280
323
|
|
|
281
324
|
const validateModelFields =
|
|
@@ -293,6 +336,17 @@ const validateModelFields =
|
|
|
293
336
|
throw new Error(`Field ${name} is required`);
|
|
294
337
|
}
|
|
295
338
|
|
|
339
|
+
if (
|
|
340
|
+
isSchemaModelWithAttributes(modelDefinition) &&
|
|
341
|
+
!isIdManaged(modelDefinition)
|
|
342
|
+
) {
|
|
343
|
+
const keys = extractPrimaryKeyFieldNames(modelDefinition);
|
|
344
|
+
if (keys.includes(k) && v === '') {
|
|
345
|
+
logger.error(errorMessages.idEmptyString, { k, value: v });
|
|
346
|
+
throw new Error(errorMessages.idEmptyString);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
296
350
|
if (isGraphQLScalarType(type)) {
|
|
297
351
|
const jsType = GraphQLScalarType.getJSType(type);
|
|
298
352
|
const validateScalar = GraphQLScalarType.getValidationFunction(type);
|
|
@@ -370,6 +424,56 @@ const validateModelFields =
|
|
|
370
424
|
`Field ${name} should be of type ${type}, validation failed. ${v}`
|
|
371
425
|
);
|
|
372
426
|
}
|
|
427
|
+
} else if (isNonModelFieldType(type)) {
|
|
428
|
+
// do not check non model fields if undefined or null
|
|
429
|
+
if (!isNullOrUndefined(v)) {
|
|
430
|
+
const subNonModelDefinition =
|
|
431
|
+
schema.namespaces.user.nonModels[type.nonModel];
|
|
432
|
+
const modelValidator = validateModelFields(subNonModelDefinition);
|
|
433
|
+
|
|
434
|
+
if (isArray) {
|
|
435
|
+
let errorTypeText: string = type.nonModel;
|
|
436
|
+
if (!isRequired) {
|
|
437
|
+
errorTypeText = `${type.nonModel} | null | undefined`;
|
|
438
|
+
}
|
|
439
|
+
if (!Array.isArray(v)) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`Field ${name} should be of type [${errorTypeText}], ${typeof v} received. ${v}`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
v.forEach(item => {
|
|
446
|
+
if (
|
|
447
|
+
(isNullOrUndefined(item) && isRequired) ||
|
|
448
|
+
(typeof item !== 'object' && typeof item !== 'undefined')
|
|
449
|
+
) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`All elements in the ${name} array should be of type ${
|
|
452
|
+
type.nonModel
|
|
453
|
+
}, [${typeof item}] received. ${item}`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!isNullOrUndefined(item)) {
|
|
458
|
+
Object.keys(subNonModelDefinition.fields).forEach(subKey => {
|
|
459
|
+
modelValidator(subKey, item[subKey]);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
} else {
|
|
464
|
+
if (typeof v !== 'object') {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`Field ${name} should be of type ${
|
|
467
|
+
type.nonModel
|
|
468
|
+
}, ${typeof v} recieved. ${v}`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
Object.keys(subNonModelDefinition.fields).forEach(subKey => {
|
|
473
|
+
modelValidator(subKey, v[subKey]);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
373
477
|
}
|
|
374
478
|
}
|
|
375
479
|
};
|
|
@@ -403,7 +507,7 @@ const castInstanceType = (
|
|
|
403
507
|
return v;
|
|
404
508
|
};
|
|
405
509
|
|
|
406
|
-
const initializeInstance = <T>(
|
|
510
|
+
const initializeInstance = <T extends PersistentModel>(
|
|
407
511
|
init: ModelInit<T>,
|
|
408
512
|
modelDefinition: SchemaModel | SchemaNonModel,
|
|
409
513
|
draft: Draft<T & ModelInstanceMetadata>
|
|
@@ -427,31 +531,39 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
427
531
|
(draft: Draft<T & ModelInstanceMetadata>) => {
|
|
428
532
|
initializeInstance(init, modelDefinition, draft);
|
|
429
533
|
|
|
534
|
+
// model is initialized inside a DataStore component (e.g. by Sync Engine, Storage Engine, etc.)
|
|
535
|
+
const isInternallyInitialized = instancesMetadata.has(init);
|
|
536
|
+
|
|
430
537
|
const modelInstanceMetadata: ModelInstanceMetadata =
|
|
431
|
-
|
|
538
|
+
isInternallyInitialized
|
|
432
539
|
? <ModelInstanceMetadata>(<unknown>init)
|
|
433
540
|
: <ModelInstanceMetadata>{};
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (
|
|
541
|
+
|
|
542
|
+
type ModelWithIDIdentifier = { id: string };
|
|
543
|
+
|
|
544
|
+
const { id: _id } =
|
|
545
|
+
modelInstanceMetadata as unknown as ModelWithIDIdentifier;
|
|
546
|
+
|
|
547
|
+
if (isIdManaged(modelDefinition)) {
|
|
548
|
+
const isInternalModel = _id !== null && _id !== undefined;
|
|
549
|
+
|
|
550
|
+
const id = isInternalModel
|
|
551
|
+
? _id
|
|
552
|
+
: modelDefinition.syncable
|
|
553
|
+
? uuid4()
|
|
554
|
+
: ulid();
|
|
555
|
+
|
|
556
|
+
(<ModelWithIDIdentifier>(<unknown>draft)).id = id;
|
|
557
|
+
} else if (isIdOptionallyManaged(modelDefinition)) {
|
|
558
|
+
// only auto-populate if the id was not provided
|
|
559
|
+
(<ModelWithIDIdentifier>(<unknown>draft)).id = draft.id || uuid4();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (!isInternallyInitialized) {
|
|
451
563
|
checkReadOnlyPropertyOnCreate(draft, modelDefinition);
|
|
452
564
|
}
|
|
453
565
|
|
|
454
|
-
|
|
566
|
+
const { _version, _lastChangedAt, _deleted } = modelInstanceMetadata;
|
|
455
567
|
|
|
456
568
|
if (modelDefinition.syncable) {
|
|
457
569
|
draft._version = _version;
|
|
@@ -476,8 +588,12 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
476
588
|
const model = produce(
|
|
477
589
|
source,
|
|
478
590
|
draft => {
|
|
479
|
-
fn(<MutableModel<T>>
|
|
480
|
-
|
|
591
|
+
fn(<MutableModel<T>>draft);
|
|
592
|
+
|
|
593
|
+
const keyNames = extractPrimaryKeyFieldNames(modelDefinition);
|
|
594
|
+
// Keys are immutable
|
|
595
|
+
keyNames.forEach(key => ((draft as Object)[key] = source[key]));
|
|
596
|
+
|
|
481
597
|
const modelValidator = validateModelFields(modelDefinition);
|
|
482
598
|
Object.entries(draft).forEach(([k, v]) => {
|
|
483
599
|
const parsedValue = castInstanceType(modelDefinition, k, v);
|
|
@@ -489,6 +605,7 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
489
605
|
);
|
|
490
606
|
|
|
491
607
|
const hasExistingPatches = modelPatchesMap.has(source);
|
|
608
|
+
|
|
492
609
|
if (patches.length || hasExistingPatches) {
|
|
493
610
|
if (hasExistingPatches) {
|
|
494
611
|
const [existingPatches, existingSource] = modelPatchesMap.get(source);
|
|
@@ -516,6 +633,7 @@ const createModelClass = <T extends PersistentModel>(
|
|
|
516
633
|
}
|
|
517
634
|
|
|
518
635
|
const instance = modelInstanceCreator(clazz, json);
|
|
636
|
+
|
|
519
637
|
const modelValidator = validateModelFields(modelDefinition);
|
|
520
638
|
|
|
521
639
|
Object.entries(instance).forEach(([k, v]) => {
|
|
@@ -563,7 +681,9 @@ const checkReadOnlyPropertyOnUpdate = (
|
|
|
563
681
|
});
|
|
564
682
|
};
|
|
565
683
|
|
|
566
|
-
const createNonModelClass = <T>(
|
|
684
|
+
const createNonModelClass = <T extends PersistentModel>(
|
|
685
|
+
typeDefinition: SchemaNonModel
|
|
686
|
+
) => {
|
|
567
687
|
const clazz = <NonModelTypeConstructor<T>>(<unknown>class Model {
|
|
568
688
|
constructor(init: ModelInit<T>) {
|
|
569
689
|
const instance = produce(
|
|
@@ -634,6 +754,18 @@ function getModelConstructorByModelName(
|
|
|
634
754
|
}
|
|
635
755
|
}
|
|
636
756
|
|
|
757
|
+
/**
|
|
758
|
+
* Queries the DataStore metadata tables to see if they are the expected
|
|
759
|
+
* version. If not, clobbers the whole DB. If so, leaves them alone.
|
|
760
|
+
* Otherwise, simply writes the schema version.
|
|
761
|
+
*
|
|
762
|
+
* SIDE EFFECT:
|
|
763
|
+
* 1. Creates a transaction
|
|
764
|
+
* 1. Updates data.
|
|
765
|
+
*
|
|
766
|
+
* @param storage Storage adapter containing the metadata.
|
|
767
|
+
* @param version The expected schema version.
|
|
768
|
+
*/
|
|
637
769
|
async function checkSchemaVersion(
|
|
638
770
|
storage: Storage,
|
|
639
771
|
version: string
|
|
@@ -647,7 +779,6 @@ async function checkSchemaVersion(
|
|
|
647
779
|
const [schemaVersionSetting] = await s.query(
|
|
648
780
|
Setting,
|
|
649
781
|
ModelPredicateCreator.createFromExisting(modelDefinition, c =>
|
|
650
|
-
// @ts-ignore Argument of type '"eq"' is not assignable to parameter of type 'never'.
|
|
651
782
|
c.key('eq', SETTING_SCHEMA_VERSION)
|
|
652
783
|
),
|
|
653
784
|
{ page: 0, limit: 1 }
|
|
@@ -713,6 +844,14 @@ function getNamespace(): SchemaNamespace {
|
|
|
713
844
|
return namespace;
|
|
714
845
|
}
|
|
715
846
|
|
|
847
|
+
enum DataStoreState {
|
|
848
|
+
NotRunning = 'Not Running',
|
|
849
|
+
Starting = 'Starting',
|
|
850
|
+
Running = 'Running',
|
|
851
|
+
Stopping = 'Stopping',
|
|
852
|
+
Clearing = 'Clearing',
|
|
853
|
+
}
|
|
854
|
+
|
|
716
855
|
class DataStore {
|
|
717
856
|
// reference to configured category instances. Used for preserving SSR context
|
|
718
857
|
private Auth = Auth;
|
|
@@ -724,12 +863,12 @@ class DataStore {
|
|
|
724
863
|
private conflictHandler: ConflictHandler;
|
|
725
864
|
private errorHandler: (error: SyncError<PersistentModel>) => void;
|
|
726
865
|
private fullSyncInterval: number;
|
|
727
|
-
private initialized
|
|
866
|
+
private initialized?: Promise<void>;
|
|
728
867
|
private initReject: Function;
|
|
729
868
|
private initResolve: Function;
|
|
730
869
|
private maxRecordsToSync: number;
|
|
731
|
-
private storage
|
|
732
|
-
private sync
|
|
870
|
+
private storage?: Storage;
|
|
871
|
+
private sync?: SyncEngine;
|
|
733
872
|
private syncPageSize: number;
|
|
734
873
|
private syncExpressions: SyncExpression[];
|
|
735
874
|
private syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>> =
|
|
@@ -742,104 +881,197 @@ class DataStore {
|
|
|
742
881
|
API: this.API,
|
|
743
882
|
Cache: this.Cache,
|
|
744
883
|
};
|
|
884
|
+
private connectivityMonitor?: DataStoreConnectivity;
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* **IMPORTANT!**
|
|
888
|
+
*
|
|
889
|
+
* Accumulator for background things that can **and MUST** be called when
|
|
890
|
+
* DataStore stops.
|
|
891
|
+
*
|
|
892
|
+
* These jobs **MUST** be *idempotent promises* that resolve ONLY
|
|
893
|
+
* once the intended jobs are completely finished and/or otherwise destroyed
|
|
894
|
+
* and cleaned up with ZERO outstanding:
|
|
895
|
+
*
|
|
896
|
+
* 1. side effects (e.g., state changes)
|
|
897
|
+
* 1. callbacks
|
|
898
|
+
* 1. subscriptions
|
|
899
|
+
* 1. calls to storage
|
|
900
|
+
* 1. *etc.*
|
|
901
|
+
*
|
|
902
|
+
* Methods that create pending promises, subscriptions, callbacks, or any
|
|
903
|
+
* type of side effect **MUST** be registered with the manager. And, a new
|
|
904
|
+
* manager must be created after each `exit()`.
|
|
905
|
+
*
|
|
906
|
+
* Failure to comply will put DataStore into a highly unpredictable state
|
|
907
|
+
* when it needs to stop or clear -- which occurs when restarting with new
|
|
908
|
+
* sync expressions, during testing, and potentially during app code
|
|
909
|
+
* recovery handling, etc..
|
|
910
|
+
*
|
|
911
|
+
* It is up to the discretion of each disposer whether to wait for job
|
|
912
|
+
* completion or to cancel operations and issue failures *as long as the
|
|
913
|
+
* disposer returns in a reasonable amount of time.*
|
|
914
|
+
*
|
|
915
|
+
* (Reasonable = *seconds*, not minutes.)
|
|
916
|
+
*/
|
|
917
|
+
private runningProcesses = new BackgroundProcessManager();
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Indicates what state DataStore is in.
|
|
921
|
+
*
|
|
922
|
+
* Not [yet?] used for actual state management; but for messaging
|
|
923
|
+
* when errors occur, to help troubleshoot.
|
|
924
|
+
*/
|
|
925
|
+
private state: DataStoreState = DataStoreState.NotRunning;
|
|
745
926
|
|
|
746
927
|
getModuleName() {
|
|
747
928
|
return 'DataStore';
|
|
748
929
|
}
|
|
749
930
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
931
|
+
/**
|
|
932
|
+
* Builds a function to capture `BackgroundManagerNotOpenError`'s to produce friendlier,
|
|
933
|
+
* more instructive errors for customers.
|
|
934
|
+
*
|
|
935
|
+
* @param operation The name of the operation (usually a Datastore method) the customer
|
|
936
|
+
* tried to call.
|
|
937
|
+
*/
|
|
938
|
+
handleAddProcError(operation: string) {
|
|
939
|
+
/**
|
|
940
|
+
* If the tested error is a `BackgroundManagerNotOpenError`, it will be captured
|
|
941
|
+
* and replaced with a friendlier message that instructs the App Developer.
|
|
942
|
+
*
|
|
943
|
+
* @param err An error to test.
|
|
944
|
+
*/
|
|
945
|
+
const handler = (err: Error) => {
|
|
946
|
+
if (err.message.startsWith('BackgroundManagerNotOpenError')) {
|
|
947
|
+
throw new Error(
|
|
948
|
+
[
|
|
949
|
+
`DataStoreStateError: Tried to execute \`${operation}\` while DataStore was "${this.state}".`,
|
|
950
|
+
`This can only be done while DataStore is "Started" or "Stopped". To remedy:`,
|
|
951
|
+
'Ensure all calls to `stop()` and `clear()` have completed first.',
|
|
952
|
+
'If this is not possible, retry the operation until it succeeds.',
|
|
953
|
+
].join('\n')
|
|
954
|
+
);
|
|
955
|
+
} else {
|
|
956
|
+
throw err;
|
|
957
|
+
}
|
|
958
|
+
};
|
|
759
959
|
|
|
760
|
-
|
|
761
|
-
|
|
960
|
+
return handler;
|
|
961
|
+
}
|
|
762
962
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
963
|
+
/**
|
|
964
|
+
* If not already done:
|
|
965
|
+
* 1. Attaches and initializes storage.
|
|
966
|
+
* 1. Loads the schema and records metadata.
|
|
967
|
+
* 1. If `this.amplifyConfig.aws_appsync_graphqlEndpoint` contains a URL,
|
|
968
|
+
* attaches a sync engine, starts it, and subscribes.
|
|
969
|
+
*/
|
|
970
|
+
start = async (): Promise<void> => {
|
|
971
|
+
return this.runningProcesses
|
|
972
|
+
.add(async () => {
|
|
973
|
+
this.state = DataStoreState.Starting;
|
|
974
|
+
if (this.initialized === undefined) {
|
|
975
|
+
logger.debug('Starting DataStore');
|
|
976
|
+
this.initialized = new Promise((res, rej) => {
|
|
977
|
+
this.initResolve = res;
|
|
978
|
+
this.initReject = rej;
|
|
979
|
+
});
|
|
980
|
+
} else {
|
|
981
|
+
await this.initialized;
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
771
984
|
|
|
772
|
-
|
|
985
|
+
this.storage = new Storage(
|
|
986
|
+
schema,
|
|
987
|
+
namespaceResolver,
|
|
988
|
+
getModelConstructorByModelName,
|
|
989
|
+
modelInstanceCreator,
|
|
990
|
+
this.storageAdapter,
|
|
991
|
+
this.sessionId
|
|
992
|
+
);
|
|
773
993
|
|
|
774
|
-
|
|
775
|
-
await checkSchemaVersion(this.storage, schema.version);
|
|
994
|
+
await this.storage.init();
|
|
776
995
|
|
|
777
|
-
|
|
996
|
+
checkSchemaInitialized();
|
|
997
|
+
await checkSchemaVersion(this.storage, schema.version);
|
|
778
998
|
|
|
779
|
-
|
|
780
|
-
logger.debug('GraphQL endpoint available', aws_appsync_graphqlEndpoint);
|
|
999
|
+
const { aws_appsync_graphqlEndpoint } = this.amplifyConfig;
|
|
781
1000
|
|
|
782
|
-
|
|
1001
|
+
if (aws_appsync_graphqlEndpoint) {
|
|
1002
|
+
logger.debug(
|
|
1003
|
+
'GraphQL endpoint available',
|
|
1004
|
+
aws_appsync_graphqlEndpoint
|
|
1005
|
+
);
|
|
783
1006
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1007
|
+
this.syncPredicates = await this.processSyncExpressions();
|
|
1008
|
+
|
|
1009
|
+
this.sync = new SyncEngine(
|
|
1010
|
+
schema,
|
|
1011
|
+
namespaceResolver,
|
|
1012
|
+
syncClasses,
|
|
1013
|
+
userClasses,
|
|
1014
|
+
this.storage,
|
|
1015
|
+
modelInstanceCreator,
|
|
1016
|
+
this.conflictHandler,
|
|
1017
|
+
this.errorHandler,
|
|
1018
|
+
this.syncPredicates,
|
|
1019
|
+
this.amplifyConfig,
|
|
1020
|
+
this.authModeStrategy,
|
|
1021
|
+
this.amplifyContext,
|
|
1022
|
+
this.connectivityMonitor
|
|
1023
|
+
);
|
|
798
1024
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1025
|
+
const fullSyncIntervalInMilliseconds =
|
|
1026
|
+
this.fullSyncInterval * 1000 * 60; // fullSyncInterval from param is in minutes
|
|
1027
|
+
syncSubscription = this.sync
|
|
1028
|
+
.start({ fullSyncInterval: fullSyncIntervalInMilliseconds })
|
|
1029
|
+
.subscribe({
|
|
1030
|
+
next: ({ type, data }) => {
|
|
1031
|
+
// In Node, we need to wait for queries to be synced to prevent returning empty arrays.
|
|
1032
|
+
// In the Browser, we can begin returning data once subscriptions are in place.
|
|
1033
|
+
const readyType = isNode
|
|
1034
|
+
? ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
|
|
1035
|
+
: ControlMessage.SYNC_ENGINE_STORAGE_SUBSCRIBED;
|
|
1036
|
+
|
|
1037
|
+
if (type === readyType) {
|
|
1038
|
+
this.initResolve();
|
|
1039
|
+
}
|
|
814
1040
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1041
|
+
Hub.dispatch('datastore', {
|
|
1042
|
+
event: type,
|
|
1043
|
+
data,
|
|
1044
|
+
});
|
|
1045
|
+
},
|
|
1046
|
+
error: err => {
|
|
1047
|
+
logger.warn('Sync error', err);
|
|
1048
|
+
this.initReject();
|
|
1049
|
+
},
|
|
818
1050
|
});
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
logger.warn(
|
|
827
|
-
"Data won't be synchronized. No GraphQL endpoint configured. Did you forget `Amplify.configure(awsconfig)`?",
|
|
828
|
-
{
|
|
829
|
-
config: this.amplifyConfig,
|
|
830
|
-
}
|
|
831
|
-
);
|
|
1051
|
+
} else {
|
|
1052
|
+
logger.warn(
|
|
1053
|
+
"Data won't be synchronized. No GraphQL endpoint configured. Did you forget `Amplify.configure(awsconfig)`?",
|
|
1054
|
+
{
|
|
1055
|
+
config: this.amplifyConfig,
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
832
1058
|
|
|
833
|
-
|
|
834
|
-
|
|
1059
|
+
this.initResolve();
|
|
1060
|
+
}
|
|
835
1061
|
|
|
836
|
-
|
|
1062
|
+
await this.initialized;
|
|
1063
|
+
this.state = DataStoreState.Running;
|
|
1064
|
+
}, 'datastore start')
|
|
1065
|
+
.catch(this.handleAddProcError('DataStore.start()'));
|
|
837
1066
|
};
|
|
838
1067
|
|
|
839
1068
|
query: {
|
|
840
1069
|
<T extends PersistentModel>(
|
|
841
1070
|
modelConstructor: PersistentModelConstructor<T>,
|
|
842
|
-
|
|
1071
|
+
identifier: IdentifierFieldOrIdentifierObject<
|
|
1072
|
+
T,
|
|
1073
|
+
PersistentModelMetaData<T>
|
|
1074
|
+
>
|
|
843
1075
|
): Promise<T | undefined>;
|
|
844
1076
|
<T extends PersistentModel>(
|
|
845
1077
|
modelConstructor: PersistentModelConstructor<T>,
|
|
@@ -848,112 +1080,142 @@ class DataStore {
|
|
|
848
1080
|
): Promise<T[]>;
|
|
849
1081
|
} = async <T extends PersistentModel>(
|
|
850
1082
|
modelConstructor: PersistentModelConstructor<T>,
|
|
851
|
-
|
|
1083
|
+
identifierOrCriteria?:
|
|
1084
|
+
| IdentifierFieldOrIdentifierObject<T, PersistentModelMetaData<T>>
|
|
1085
|
+
| ProducerModelPredicate<T>
|
|
1086
|
+
| typeof PredicateAll,
|
|
852
1087
|
paginationProducer?: ProducerPaginationInput<T>
|
|
853
1088
|
): Promise<T | T[] | undefined> => {
|
|
854
|
-
|
|
1089
|
+
return this.runningProcesses
|
|
1090
|
+
.add(async () => {
|
|
1091
|
+
await this.start();
|
|
855
1092
|
|
|
856
|
-
|
|
1093
|
+
//#region Input validation
|
|
857
1094
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1095
|
+
if (!isValidModelConstructor(modelConstructor)) {
|
|
1096
|
+
const msg = 'Constructor is not for a valid model';
|
|
1097
|
+
logger.error(msg, { modelConstructor });
|
|
861
1098
|
|
|
862
|
-
|
|
863
|
-
|
|
1099
|
+
throw new Error(msg);
|
|
1100
|
+
}
|
|
864
1101
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1102
|
+
if (typeof identifierOrCriteria === 'string') {
|
|
1103
|
+
if (paginationProducer !== undefined) {
|
|
1104
|
+
logger.warn('Pagination is ignored when querying by id');
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
870
1107
|
|
|
871
|
-
|
|
872
|
-
|
|
1108
|
+
const modelDefinition = getModelDefinition(modelConstructor);
|
|
1109
|
+
const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
|
|
873
1110
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1111
|
+
let predicate: ModelPredicate<T>;
|
|
1112
|
+
|
|
1113
|
+
if (isQueryOne(identifierOrCriteria)) {
|
|
1114
|
+
if (keyFields.length > 1) {
|
|
1115
|
+
const msg = errorMessages.queryByPkWithCompositeKeyPresent;
|
|
1116
|
+
logger.error(msg, { keyFields });
|
|
1117
|
+
|
|
1118
|
+
throw new Error(msg);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
predicate = ModelPredicateCreator.createForSingleField<T>(
|
|
1122
|
+
modelDefinition,
|
|
1123
|
+
keyFields[0],
|
|
1124
|
+
identifierOrCriteria
|
|
1125
|
+
);
|
|
1126
|
+
} else {
|
|
1127
|
+
// Object is being queried using object literal syntax
|
|
1128
|
+
if (isIdentifierObject(<T>identifierOrCriteria, modelDefinition)) {
|
|
1129
|
+
predicate = ModelPredicateCreator.createForPk<T>(
|
|
1130
|
+
modelDefinition,
|
|
1131
|
+
<T>identifierOrCriteria
|
|
1132
|
+
);
|
|
1133
|
+
} else if (isPredicatesAll(identifierOrCriteria)) {
|
|
1134
|
+
// Predicates.ALL means "all records", so no predicate (undefined)
|
|
1135
|
+
predicate = undefined;
|
|
1136
|
+
} else {
|
|
1137
|
+
predicate = ModelPredicateCreator.createFromExisting(
|
|
1138
|
+
modelDefinition,
|
|
1139
|
+
<any>identifierOrCriteria
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
const pagination = this.processPagination(
|
|
885
1145
|
modelDefinition,
|
|
886
|
-
|
|
1146
|
+
paginationProducer
|
|
887
1147
|
);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
1148
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1149
|
+
//#endregion
|
|
1150
|
+
|
|
1151
|
+
logger.debug('params ready', {
|
|
1152
|
+
modelConstructor,
|
|
1153
|
+
predicate: ModelPredicateCreator.getPredicates(predicate, false),
|
|
1154
|
+
pagination: {
|
|
1155
|
+
...pagination,
|
|
1156
|
+
sort: ModelSortPredicateCreator.getPredicates(
|
|
1157
|
+
pagination && pagination.sort,
|
|
1158
|
+
false
|
|
1159
|
+
),
|
|
1160
|
+
},
|
|
1161
|
+
});
|
|
895
1162
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
pagination: {
|
|
902
|
-
...pagination,
|
|
903
|
-
sort: ModelSortPredicateCreator.getPredicates(
|
|
904
|
-
pagination && pagination.sort,
|
|
905
|
-
false
|
|
906
|
-
),
|
|
907
|
-
},
|
|
908
|
-
});
|
|
1163
|
+
const result = await this.storage.query(
|
|
1164
|
+
modelConstructor,
|
|
1165
|
+
predicate,
|
|
1166
|
+
pagination
|
|
1167
|
+
);
|
|
909
1168
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
pagination
|
|
914
|
-
);
|
|
1169
|
+
const returnOne =
|
|
1170
|
+
isQueryOne(identifierOrCriteria) ||
|
|
1171
|
+
isIdentifierObject(identifierOrCriteria, modelDefinition);
|
|
915
1172
|
|
|
916
|
-
|
|
1173
|
+
return returnOne ? result[0] : result;
|
|
1174
|
+
}, 'datastore query')
|
|
1175
|
+
.catch(this.handleAddProcError('DataStore.query()'));
|
|
917
1176
|
};
|
|
918
1177
|
|
|
919
1178
|
save = async <T extends PersistentModel>(
|
|
920
1179
|
model: T,
|
|
921
1180
|
condition?: ProducerModelPredicate<T>
|
|
922
1181
|
): Promise<T> => {
|
|
923
|
-
|
|
1182
|
+
return this.runningProcesses
|
|
1183
|
+
.add(async () => {
|
|
1184
|
+
await this.start();
|
|
924
1185
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1186
|
+
// Immer patches for constructing a correct update mutation input
|
|
1187
|
+
// Allows us to only include changed fields for updates
|
|
1188
|
+
const patchesTuple = modelPatchesMap.get(model);
|
|
928
1189
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
: undefined;
|
|
1190
|
+
const modelConstructor: PersistentModelConstructor<T> | undefined =
|
|
1191
|
+
model ? <PersistentModelConstructor<T>>model.constructor : undefined;
|
|
932
1192
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1193
|
+
if (!isValidModelConstructor(modelConstructor)) {
|
|
1194
|
+
const msg = 'Object is not an instance of a valid model';
|
|
1195
|
+
logger.error(msg, { model });
|
|
936
1196
|
|
|
937
|
-
|
|
938
|
-
|
|
1197
|
+
throw new Error(msg);
|
|
1198
|
+
}
|
|
939
1199
|
|
|
940
|
-
|
|
1200
|
+
const modelDefinition = getModelDefinition(modelConstructor);
|
|
941
1201
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1202
|
+
const producedCondition = ModelPredicateCreator.createFromExisting(
|
|
1203
|
+
modelDefinition,
|
|
1204
|
+
condition!
|
|
1205
|
+
);
|
|
946
1206
|
|
|
947
|
-
|
|
948
|
-
|
|
1207
|
+
const [savedModel] = await this.storage.runExclusive(async s => {
|
|
1208
|
+
await s.save(model, producedCondition, undefined, patchesTuple);
|
|
949
1209
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1210
|
+
return s.query<T>(
|
|
1211
|
+
modelConstructor,
|
|
1212
|
+
ModelPredicateCreator.createForPk(modelDefinition, model)
|
|
1213
|
+
);
|
|
1214
|
+
});
|
|
955
1215
|
|
|
956
|
-
|
|
1216
|
+
return savedModel;
|
|
1217
|
+
}, 'datastore save')
|
|
1218
|
+
.catch(this.handleAddProcError('DataStore.save()'));
|
|
957
1219
|
};
|
|
958
1220
|
|
|
959
1221
|
setConflictHandler = (config: DataStoreConfig): ConflictHandler => {
|
|
@@ -989,126 +1251,171 @@ class DataStore {
|
|
|
989
1251
|
};
|
|
990
1252
|
|
|
991
1253
|
delete: {
|
|
992
|
-
<T extends PersistentModel>(
|
|
993
|
-
model: T,
|
|
994
|
-
condition?: ProducerModelPredicate<T>
|
|
995
|
-
): Promise<T>;
|
|
996
1254
|
<T extends PersistentModel>(
|
|
997
1255
|
modelConstructor: PersistentModelConstructor<T>,
|
|
998
|
-
|
|
1256
|
+
identifier: IdentifierFieldOrIdentifierObject<
|
|
1257
|
+
T,
|
|
1258
|
+
PersistentModelMetaData<T>
|
|
1259
|
+
>
|
|
999
1260
|
): Promise<T[]>;
|
|
1000
1261
|
<T extends PersistentModel>(
|
|
1001
1262
|
modelConstructor: PersistentModelConstructor<T>,
|
|
1002
1263
|
condition: ProducerModelPredicate<T> | typeof PredicateAll
|
|
1003
1264
|
): Promise<T[]>;
|
|
1265
|
+
<T extends PersistentModel>(
|
|
1266
|
+
model: T,
|
|
1267
|
+
condition?: ProducerModelPredicate<T>
|
|
1268
|
+
): Promise<T>;
|
|
1004
1269
|
} = async <T extends PersistentModel>(
|
|
1005
1270
|
modelOrConstructor: T | PersistentModelConstructor<T>,
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1271
|
+
identifierOrCriteria?:
|
|
1272
|
+
| IdentifierFieldOrIdentifierObject<T, PersistentModelMetaData<T>>
|
|
1273
|
+
| ProducerModelPredicate<T>
|
|
1274
|
+
| typeof PredicateAll
|
|
1275
|
+
): Promise<T | T[]> => {
|
|
1276
|
+
return this.runningProcesses
|
|
1277
|
+
.add(async () => {
|
|
1278
|
+
await this.start();
|
|
1009
1279
|
|
|
1010
|
-
|
|
1280
|
+
let condition: ModelPredicate<T>;
|
|
1011
1281
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1282
|
+
if (!modelOrConstructor) {
|
|
1283
|
+
const msg = 'Model or Model Constructor required';
|
|
1284
|
+
logger.error(msg, { modelOrConstructor });
|
|
1015
1285
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1286
|
+
throw new Error(msg);
|
|
1287
|
+
}
|
|
1018
1288
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1289
|
+
if (isValidModelConstructor<T>(modelOrConstructor)) {
|
|
1290
|
+
const modelConstructor = modelOrConstructor;
|
|
1021
1291
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1292
|
+
if (!identifierOrCriteria) {
|
|
1293
|
+
const msg =
|
|
1294
|
+
'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL';
|
|
1295
|
+
logger.error(msg, { identifierOrCriteria });
|
|
1026
1296
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1297
|
+
throw new Error(msg);
|
|
1298
|
+
}
|
|
1029
1299
|
|
|
1030
|
-
|
|
1031
|
-
condition = ModelPredicateCreator.createForId<T>(
|
|
1032
|
-
getModelDefinition(modelConstructor),
|
|
1033
|
-
idOrCriteria
|
|
1034
|
-
);
|
|
1035
|
-
} else {
|
|
1036
|
-
condition = ModelPredicateCreator.createFromExisting(
|
|
1037
|
-
getModelDefinition(modelConstructor),
|
|
1038
|
-
/**
|
|
1039
|
-
* idOrCriteria is always a ProducerModelPredicate<T>, never a symbol.
|
|
1040
|
-
* The symbol is used only for typing purposes. e.g. see Predicates.ALL
|
|
1041
|
-
*/
|
|
1042
|
-
idOrCriteria as ProducerModelPredicate<T>
|
|
1043
|
-
);
|
|
1300
|
+
const modelDefinition = getModelDefinition(modelConstructor);
|
|
1044
1301
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
'Criteria required. Do you want to delete all? Pass Predicates.ALL';
|
|
1048
|
-
logger.error(msg, { condition });
|
|
1302
|
+
if (typeof identifierOrCriteria === 'string') {
|
|
1303
|
+
const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
|
|
1049
1304
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1305
|
+
if (keyFields.length > 1) {
|
|
1306
|
+
const msg = errorMessages.deleteByPkWithCompositeKeyPresent;
|
|
1307
|
+
logger.error(msg, { keyFields });
|
|
1053
1308
|
|
|
1054
|
-
|
|
1309
|
+
throw new Error(msg);
|
|
1310
|
+
}
|
|
1055
1311
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1312
|
+
condition = ModelPredicateCreator.createForSingleField<T>(
|
|
1313
|
+
getModelDefinition(modelConstructor),
|
|
1314
|
+
keyFields[0],
|
|
1315
|
+
identifierOrCriteria
|
|
1316
|
+
);
|
|
1317
|
+
} else {
|
|
1318
|
+
if (isIdentifierObject(identifierOrCriteria, modelDefinition)) {
|
|
1319
|
+
condition = ModelPredicateCreator.createForPk<T>(
|
|
1320
|
+
modelDefinition,
|
|
1321
|
+
<T>identifierOrCriteria
|
|
1322
|
+
);
|
|
1323
|
+
} else {
|
|
1324
|
+
condition = ModelPredicateCreator.createFromExisting(
|
|
1325
|
+
modelDefinition,
|
|
1326
|
+
/**
|
|
1327
|
+
* idOrCriteria is always a ProducerModelPredicate<T>, never a symbol.
|
|
1328
|
+
* The symbol is used only for typing purposes. e.g. see Predicates.ALL
|
|
1329
|
+
*/
|
|
1330
|
+
identifierOrCriteria as ProducerModelPredicate<T>
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1061
1333
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1334
|
+
if (
|
|
1335
|
+
!condition ||
|
|
1336
|
+
!ModelPredicateCreator.isValidPredicate(condition)
|
|
1337
|
+
) {
|
|
1338
|
+
const msg =
|
|
1339
|
+
'Criteria required. Do you want to delete all? Pass Predicates.ALL';
|
|
1340
|
+
logger.error(msg, { condition });
|
|
1065
1341
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1342
|
+
throw new Error(msg);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1068
1345
|
|
|
1069
|
-
|
|
1346
|
+
const [deleted] = await this.storage.delete(
|
|
1347
|
+
modelConstructor,
|
|
1348
|
+
condition
|
|
1349
|
+
);
|
|
1070
1350
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1351
|
+
return deleted;
|
|
1352
|
+
} else {
|
|
1353
|
+
const model = modelOrConstructor;
|
|
1354
|
+
const modelConstructor = Object.getPrototypeOf(model || {})
|
|
1355
|
+
.constructor as PersistentModelConstructor<T>;
|
|
1075
1356
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
logger.error(msg, { idOrCriteria });
|
|
1357
|
+
if (!isValidModelConstructor(modelConstructor)) {
|
|
1358
|
+
const msg = 'Object is not an instance of a valid model';
|
|
1359
|
+
logger.error(msg, { model });
|
|
1080
1360
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1361
|
+
throw new Error(msg);
|
|
1362
|
+
}
|
|
1083
1363
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1364
|
+
const modelDefinition = getModelDefinition(modelConstructor);
|
|
1365
|
+
|
|
1366
|
+
const pkPredicate = ModelPredicateCreator.createForPk<T>(
|
|
1367
|
+
modelDefinition,
|
|
1368
|
+
model
|
|
1369
|
+
);
|
|
1088
1370
|
|
|
1089
|
-
|
|
1371
|
+
if (identifierOrCriteria) {
|
|
1372
|
+
if (typeof identifierOrCriteria !== 'function') {
|
|
1373
|
+
const msg = 'Invalid criteria';
|
|
1374
|
+
logger.error(msg, { identifierOrCriteria });
|
|
1090
1375
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1376
|
+
throw new Error(msg);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
condition = (<ProducerModelPredicate<T>>identifierOrCriteria)(
|
|
1380
|
+
pkPredicate
|
|
1381
|
+
);
|
|
1382
|
+
} else {
|
|
1383
|
+
condition = pkPredicate;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
const [[deleted]] = await this.storage.delete(model, condition);
|
|
1387
|
+
|
|
1388
|
+
return deleted;
|
|
1389
|
+
}
|
|
1390
|
+
}, 'datastore delete')
|
|
1391
|
+
.catch(this.handleAddProcError('DataStore.delete()'));
|
|
1093
1392
|
};
|
|
1094
1393
|
|
|
1095
1394
|
observe: {
|
|
1096
1395
|
(): Observable<SubscriptionMessage<PersistentModel>>;
|
|
1097
1396
|
|
|
1098
|
-
<T extends PersistentModel>(
|
|
1397
|
+
<T extends PersistentModel>(
|
|
1398
|
+
modelConstructor: PersistentModelConstructor<T>,
|
|
1399
|
+
identifier: string
|
|
1400
|
+
): Observable<SubscriptionMessage<T>>;
|
|
1099
1401
|
|
|
1100
1402
|
<T extends PersistentModel>(
|
|
1101
1403
|
modelConstructor: PersistentModelConstructor<T>,
|
|
1102
|
-
criteria?:
|
|
1404
|
+
criteria?: ProducerModelPredicate<T> | typeof PredicateAll
|
|
1103
1405
|
): Observable<SubscriptionMessage<T>>;
|
|
1104
|
-
|
|
1406
|
+
|
|
1407
|
+
<T extends PersistentModel>(model: T): Observable<SubscriptionMessage<T>>;
|
|
1408
|
+
} = <T extends PersistentModel>(
|
|
1105
1409
|
modelOrConstructor?: T | PersistentModelConstructor<T>,
|
|
1106
|
-
|
|
1410
|
+
identifierOrCriteria?:
|
|
1411
|
+
| string
|
|
1412
|
+
| ProducerModelPredicate<T>
|
|
1413
|
+
| typeof PredicateAll
|
|
1107
1414
|
): Observable<SubscriptionMessage<T>> => {
|
|
1108
1415
|
let predicate: ModelPredicate<T>;
|
|
1109
1416
|
|
|
1110
|
-
const modelConstructor: PersistentModelConstructor<T> =
|
|
1111
|
-
modelOrConstructor && isValidModelConstructor(modelOrConstructor)
|
|
1417
|
+
const modelConstructor: PersistentModelConstructor<T> | undefined =
|
|
1418
|
+
modelOrConstructor && isValidModelConstructor<T>(modelOrConstructor)
|
|
1112
1419
|
? modelOrConstructor
|
|
1113
1420
|
: undefined;
|
|
1114
1421
|
|
|
@@ -1118,10 +1425,10 @@ class DataStore {
|
|
|
1118
1425
|
model && (<Object>Object.getPrototypeOf(model)).constructor;
|
|
1119
1426
|
|
|
1120
1427
|
if (isValidModelConstructor<T>(modelConstructor)) {
|
|
1121
|
-
if (
|
|
1428
|
+
if (identifierOrCriteria) {
|
|
1122
1429
|
logger.warn('idOrCriteria is ignored when using a model instance', {
|
|
1123
1430
|
model,
|
|
1124
|
-
|
|
1431
|
+
identifierOrCriteria,
|
|
1125
1432
|
});
|
|
1126
1433
|
}
|
|
1127
1434
|
|
|
@@ -1135,9 +1442,24 @@ class DataStore {
|
|
|
1135
1442
|
}
|
|
1136
1443
|
}
|
|
1137
1444
|
|
|
1138
|
-
|
|
1445
|
+
// observe should not accept object literal syntax
|
|
1446
|
+
if (
|
|
1447
|
+
identifierOrCriteria &&
|
|
1448
|
+
modelConstructor &&
|
|
1449
|
+
isIdentifierObject(
|
|
1450
|
+
identifierOrCriteria,
|
|
1451
|
+
getModelDefinition(modelConstructor)
|
|
1452
|
+
)
|
|
1453
|
+
) {
|
|
1454
|
+
const msg = errorMessages.observeWithObjectLiteral;
|
|
1455
|
+
logger.error(msg, { objectLiteral: identifierOrCriteria });
|
|
1456
|
+
|
|
1457
|
+
throw new Error(msg);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (identifierOrCriteria !== undefined && modelConstructor === undefined) {
|
|
1139
1461
|
const msg = 'Cannot provide criteria without a modelConstructor';
|
|
1140
|
-
logger.error(msg,
|
|
1462
|
+
logger.error(msg, identifierOrCriteria);
|
|
1141
1463
|
throw new Error(msg);
|
|
1142
1464
|
}
|
|
1143
1465
|
|
|
@@ -1148,64 +1470,89 @@ class DataStore {
|
|
|
1148
1470
|
throw new Error(msg);
|
|
1149
1471
|
}
|
|
1150
1472
|
|
|
1151
|
-
if (typeof
|
|
1152
|
-
|
|
1473
|
+
if (typeof identifierOrCriteria === 'string') {
|
|
1474
|
+
const modelDefinition = getModelDefinition(modelConstructor);
|
|
1475
|
+
const [keyField] = extractPrimaryKeyFieldNames(modelDefinition);
|
|
1476
|
+
|
|
1477
|
+
predicate = ModelPredicateCreator.createForSingleField<T>(
|
|
1153
1478
|
getModelDefinition(modelConstructor),
|
|
1154
|
-
|
|
1479
|
+
keyField,
|
|
1480
|
+
identifierOrCriteria
|
|
1155
1481
|
);
|
|
1156
1482
|
} else {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1483
|
+
if (isPredicatesAll(identifierOrCriteria)) {
|
|
1484
|
+
predicate = undefined;
|
|
1485
|
+
} else {
|
|
1486
|
+
predicate =
|
|
1487
|
+
modelConstructor &&
|
|
1488
|
+
ModelPredicateCreator.createFromExisting<T>(
|
|
1489
|
+
getModelDefinition(modelConstructor),
|
|
1490
|
+
identifierOrCriteria
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1163
1493
|
}
|
|
1164
1494
|
|
|
1165
1495
|
return new Observable<SubscriptionMessage<T>>(observer => {
|
|
1166
1496
|
let handle: ZenObservable.Subscription;
|
|
1167
1497
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
.
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
item.
|
|
1189
|
-
item.
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1498
|
+
this.runningProcesses
|
|
1499
|
+
.add(async () => {
|
|
1500
|
+
await this.start();
|
|
1501
|
+
|
|
1502
|
+
// Filter the events returned by Storage according to namespace,
|
|
1503
|
+
// append original element data, and subscribe to the observable
|
|
1504
|
+
handle = this.storage
|
|
1505
|
+
.observe(modelConstructor, predicate)
|
|
1506
|
+
.filter(({ model }) => namespaceResolver(model) === USER)
|
|
1507
|
+
.subscribe({
|
|
1508
|
+
next: item =>
|
|
1509
|
+
this.runningProcesses.isOpen &&
|
|
1510
|
+
this.runningProcesses.add(async () => {
|
|
1511
|
+
// the `element` doesn't necessarily contain all item details or
|
|
1512
|
+
// have related records attached consistently with that of a query()
|
|
1513
|
+
// result item. for consistency, we attach them here.
|
|
1514
|
+
|
|
1515
|
+
let message = item;
|
|
1516
|
+
|
|
1517
|
+
// as long as we're not dealing with a DELETE, we need to fetch a fresh
|
|
1518
|
+
// item from storage to ensure it's fully populated.
|
|
1519
|
+
if (item.opType !== 'DELETE') {
|
|
1520
|
+
const modelDefinition = getModelDefinition(item.model);
|
|
1521
|
+
const keyFields =
|
|
1522
|
+
extractPrimaryKeyFieldNames(modelDefinition);
|
|
1523
|
+
const primaryKeysAndValues = extractPrimaryKeysAndValues(
|
|
1524
|
+
item.element,
|
|
1525
|
+
keyFields
|
|
1526
|
+
);
|
|
1527
|
+
const freshElement = await this.query(
|
|
1528
|
+
item.model,
|
|
1529
|
+
primaryKeysAndValues
|
|
1530
|
+
);
|
|
1531
|
+
message = {
|
|
1532
|
+
...message,
|
|
1533
|
+
element: freshElement as T,
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
observer.next(message as SubscriptionMessage<T>);
|
|
1538
|
+
}, 'datastore observe message handler'),
|
|
1539
|
+
error: err => observer.error(err),
|
|
1540
|
+
complete: () => observer.complete(),
|
|
1541
|
+
});
|
|
1542
|
+
}, 'datastore observe observable initialization')
|
|
1543
|
+
.catch(this.handleAddProcError('DataStore.observe()'))
|
|
1544
|
+
.catch(error => {
|
|
1545
|
+
observer.error(error);
|
|
1546
|
+
});
|
|
1203
1547
|
|
|
1204
|
-
|
|
1548
|
+
// better than no cleaner, but if the subscriber is handling the
|
|
1549
|
+
// complete() message async and not registering with the context,
|
|
1550
|
+
// this will still be problematic.
|
|
1551
|
+
return this.runningProcesses.addCleaner(async () => {
|
|
1205
1552
|
if (handle) {
|
|
1206
1553
|
handle.unsubscribe();
|
|
1207
1554
|
}
|
|
1208
|
-
};
|
|
1555
|
+
}, 'DataStore.observe() cleanup');
|
|
1209
1556
|
});
|
|
1210
1557
|
};
|
|
1211
1558
|
|
|
@@ -1215,7 +1562,7 @@ class DataStore {
|
|
|
1215
1562
|
criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
|
|
1216
1563
|
paginationProducer?: ObserveQueryOptions<T>
|
|
1217
1564
|
): Observable<DataStoreSnapshot<T>>;
|
|
1218
|
-
} = <T extends PersistentModel
|
|
1565
|
+
} = <T extends PersistentModel>(
|
|
1219
1566
|
model: PersistentModelConstructor<T>,
|
|
1220
1567
|
criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
|
|
1221
1568
|
options?: ObserveQueryOptions<T>
|
|
@@ -1254,9 +1601,12 @@ class DataStore {
|
|
|
1254
1601
|
const sortOptions = sort ? { sort } : undefined;
|
|
1255
1602
|
|
|
1256
1603
|
const modelDefinition = getModelDefinition(model);
|
|
1604
|
+
const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
|
|
1605
|
+
|
|
1257
1606
|
if (isQueryOne(criteria)) {
|
|
1258
|
-
predicate = ModelPredicateCreator.
|
|
1607
|
+
predicate = ModelPredicateCreator.createForSingleField<T>(
|
|
1259
1608
|
modelDefinition,
|
|
1609
|
+
keyFields[0],
|
|
1260
1610
|
criteria
|
|
1261
1611
|
);
|
|
1262
1612
|
} else {
|
|
@@ -1275,68 +1625,79 @@ class DataStore {
|
|
|
1275
1625
|
ModelPredicateCreator.getPredicates(predicate, false) || {};
|
|
1276
1626
|
const hasPredicate = !!predicates;
|
|
1277
1627
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1628
|
+
this.runningProcesses
|
|
1629
|
+
.add(async () => {
|
|
1630
|
+
try {
|
|
1631
|
+
// first, query and return any locally-available records
|
|
1632
|
+
(await this.query(model, criteria, sortOptions)).forEach(item => {
|
|
1633
|
+
const itemModelDefinition = getModelDefinition(model);
|
|
1634
|
+
const idOrPk = getIdentifierValue(itemModelDefinition, item);
|
|
1635
|
+
items.set(idOrPk, item);
|
|
1636
|
+
});
|
|
1284
1637
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
!validatePredicate(element, predicateGroupType, predicates)
|
|
1294
|
-
) {
|
|
1638
|
+
// Observe the model and send a stream of updates (debounced).
|
|
1639
|
+
// We need to post-filter results instead of passing criteria through
|
|
1640
|
+
// to have visibility into items that move from in-set to out-of-set.
|
|
1641
|
+
// We need to explicitly remove those items from the existing snapshot.
|
|
1642
|
+
handle = this.observe(model).subscribe(
|
|
1643
|
+
({ element, model, opType }) => {
|
|
1644
|
+
const itemModelDefinition = getModelDefinition(model);
|
|
1645
|
+
const idOrPk = getIdentifierValue(itemModelDefinition, element);
|
|
1295
1646
|
if (
|
|
1296
|
-
|
|
1297
|
-
(
|
|
1647
|
+
hasPredicate &&
|
|
1648
|
+
!validatePredicate(element, predicateGroupType, predicates)
|
|
1298
1649
|
) {
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1650
|
+
if (
|
|
1651
|
+
opType === 'UPDATE' &&
|
|
1652
|
+
(items.has(idOrPk) || itemsChanged.has(idOrPk))
|
|
1653
|
+
) {
|
|
1654
|
+
// tracking as a "deleted item" will include the item in
|
|
1655
|
+
// page limit calculations and ensure it is removed from the
|
|
1656
|
+
// final items collection, regardless of which collection(s)
|
|
1657
|
+
// it is currently in. (I mean, it could be in both, right!?)
|
|
1658
|
+
deletedItemIds.push(idOrPk);
|
|
1659
|
+
} else {
|
|
1660
|
+
// ignore updates for irrelevant/filtered items.
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// Flag items which have been recently deleted
|
|
1666
|
+
// NOTE: Merging of separate operations to the same model instance is handled upstream
|
|
1667
|
+
// in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
|
|
1668
|
+
// depends on the LATEST record (for a given id).
|
|
1669
|
+
if (opType === 'DELETE') {
|
|
1670
|
+
deletedItemIds.push(idOrPk);
|
|
1304
1671
|
} else {
|
|
1305
|
-
|
|
1306
|
-
return;
|
|
1672
|
+
itemsChanged.set(idOrPk, element);
|
|
1307
1673
|
}
|
|
1308
|
-
}
|
|
1309
1674
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
// in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
|
|
1313
|
-
// depends on the LATEST record (for a given id).
|
|
1314
|
-
if (opType === 'DELETE') {
|
|
1315
|
-
deletedItemIds.push(element.id);
|
|
1316
|
-
} else {
|
|
1317
|
-
itemsChanged.set(element.id, element);
|
|
1318
|
-
}
|
|
1675
|
+
const isSynced =
|
|
1676
|
+
this.sync?.getModelSyncedStatus(model) ?? false;
|
|
1319
1677
|
|
|
1320
|
-
|
|
1678
|
+
const limit =
|
|
1679
|
+
itemsChanged.size - deletedItemIds.length >=
|
|
1680
|
+
this.syncPageSize;
|
|
1321
1681
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1682
|
+
if (limit || isSynced) {
|
|
1683
|
+
limitTimerRace.resolve();
|
|
1684
|
+
}
|
|
1324
1685
|
|
|
1325
|
-
|
|
1326
|
-
limitTimerRace.
|
|
1686
|
+
// kicks off every subsequent race as results sync down
|
|
1687
|
+
limitTimerRace.start();
|
|
1327
1688
|
}
|
|
1689
|
+
);
|
|
1328
1690
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
observer.error(
|
|
1338
|
-
}
|
|
1339
|
-
})();
|
|
1691
|
+
// returns a set of initial/locally-available results
|
|
1692
|
+
generateAndEmitSnapshot();
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
observer.error(err);
|
|
1695
|
+
}
|
|
1696
|
+
}, 'datastore observequery startup')
|
|
1697
|
+
.catch(this.handleAddProcError('DataStore.observeQuery()'))
|
|
1698
|
+
.catch(error => {
|
|
1699
|
+
observer.error(error);
|
|
1700
|
+
});
|
|
1340
1701
|
|
|
1341
1702
|
/**
|
|
1342
1703
|
* Combines the `items`, `itemsChanged`, and `deletedItemIds` collections into
|
|
@@ -1356,10 +1717,14 @@ class DataStore {
|
|
|
1356
1717
|
}
|
|
1357
1718
|
|
|
1358
1719
|
items.clear();
|
|
1359
|
-
itemsArray.forEach(item =>
|
|
1720
|
+
itemsArray.forEach(item => {
|
|
1721
|
+
const itemModelDefinition = getModelDefinition(model);
|
|
1722
|
+
const idOrPk = getIdentifierValue(itemModelDefinition, item);
|
|
1723
|
+
items.set(idOrPk, item);
|
|
1724
|
+
});
|
|
1360
1725
|
|
|
1361
1726
|
// remove deleted items from the final result set
|
|
1362
|
-
deletedItemIds.forEach(
|
|
1727
|
+
deletedItemIds.forEach(idOrPk => items.delete(idOrPk));
|
|
1363
1728
|
|
|
1364
1729
|
return {
|
|
1365
1730
|
items: Array.from(items.values()),
|
|
@@ -1376,7 +1741,8 @@ class DataStore {
|
|
|
1376
1741
|
* @param snapshot The generated items data to emit.
|
|
1377
1742
|
*/
|
|
1378
1743
|
const emitSnapshot = (snapshot: DataStoreSnapshot<T>): void => {
|
|
1379
|
-
// send the generated snapshot to the primary subscription
|
|
1744
|
+
// send the generated snapshot to the primary subscription.
|
|
1745
|
+
// NOTE: This observer's handler *could* be async ...
|
|
1380
1746
|
observer.next(snapshot);
|
|
1381
1747
|
|
|
1382
1748
|
// reset the changed items sets
|
|
@@ -1419,16 +1785,16 @@ class DataStore {
|
|
|
1419
1785
|
data?.model?.name === model.name
|
|
1420
1786
|
) {
|
|
1421
1787
|
generateAndEmitSnapshot();
|
|
1422
|
-
Hub.remove('
|
|
1788
|
+
Hub.remove('datastore', hubCallback);
|
|
1423
1789
|
}
|
|
1424
1790
|
};
|
|
1425
1791
|
Hub.listen('datastore', hubCallback);
|
|
1426
1792
|
|
|
1427
|
-
return () => {
|
|
1793
|
+
return this.runningProcesses.addCleaner(async () => {
|
|
1428
1794
|
if (handle) {
|
|
1429
1795
|
handle.unsubscribe();
|
|
1430
1796
|
}
|
|
1431
|
-
};
|
|
1797
|
+
}, 'datastore observequery cleaner');
|
|
1432
1798
|
});
|
|
1433
1799
|
};
|
|
1434
1800
|
|
|
@@ -1515,11 +1881,21 @@ class DataStore {
|
|
|
1515
1881
|
this.storageAdapter ||
|
|
1516
1882
|
undefined;
|
|
1517
1883
|
|
|
1518
|
-
this.sessionId = this.retrieveSessionId()
|
|
1884
|
+
this.sessionId = this.retrieveSessionId()!;
|
|
1519
1885
|
};
|
|
1520
1886
|
|
|
1521
|
-
|
|
1887
|
+
/**
|
|
1888
|
+
* Clears all data from storage and removes all data, schema info, other
|
|
1889
|
+
* initialization details, and then stops DataStore.
|
|
1890
|
+
*
|
|
1891
|
+
* That said, reinitialization is required after clearing. This can be done
|
|
1892
|
+
* by explicitiliy calling `start()` or any method that implicitly starts
|
|
1893
|
+
* DataStore, such as `query()`, `save()`, or `delete()`.
|
|
1894
|
+
*/
|
|
1895
|
+
async clear() {
|
|
1522
1896
|
checkSchemaInitialized();
|
|
1897
|
+
this.state = DataStoreState.Clearing;
|
|
1898
|
+
await this.runningProcesses.close();
|
|
1523
1899
|
if (this.storage === undefined) {
|
|
1524
1900
|
// connect to storage so that it can be cleared without fully starting DataStore
|
|
1525
1901
|
this.storage = new Storage(
|
|
@@ -1537,35 +1913,53 @@ class DataStore {
|
|
|
1537
1913
|
syncSubscription.unsubscribe();
|
|
1538
1914
|
}
|
|
1539
1915
|
|
|
1540
|
-
await this.storage.clear();
|
|
1541
|
-
|
|
1542
1916
|
if (this.sync) {
|
|
1543
|
-
this.sync.
|
|
1917
|
+
await this.sync.stop();
|
|
1544
1918
|
}
|
|
1545
1919
|
|
|
1920
|
+
await this.storage!.clear();
|
|
1921
|
+
|
|
1546
1922
|
this.initialized = undefined; // Should re-initialize when start() is called.
|
|
1547
1923
|
this.storage = undefined;
|
|
1548
1924
|
this.sync = undefined;
|
|
1549
1925
|
this.syncPredicates = new WeakMap<SchemaModel, ModelPredicate<any>>();
|
|
1550
|
-
};
|
|
1551
1926
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1927
|
+
await this.runningProcesses.open();
|
|
1928
|
+
this.state = DataStoreState.NotRunning;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* Stops all DataStore sync activities.
|
|
1933
|
+
*
|
|
1934
|
+
* TODO: "Waits for graceful termination of
|
|
1935
|
+
* running queries and terminates subscriptions."
|
|
1936
|
+
*/
|
|
1937
|
+
async stop(this: InstanceType<typeof DataStore>) {
|
|
1938
|
+
this.state = DataStoreState.Stopping;
|
|
1939
|
+
|
|
1940
|
+
await this.runningProcesses.close();
|
|
1556
1941
|
|
|
1557
1942
|
if (syncSubscription && !syncSubscription.closed) {
|
|
1558
1943
|
syncSubscription.unsubscribe();
|
|
1559
1944
|
}
|
|
1560
1945
|
|
|
1561
1946
|
if (this.sync) {
|
|
1562
|
-
this.sync.
|
|
1947
|
+
await this.sync.stop();
|
|
1563
1948
|
}
|
|
1564
1949
|
|
|
1565
1950
|
this.initialized = undefined; // Should re-initialize when start() is called.
|
|
1566
1951
|
this.sync = undefined;
|
|
1567
|
-
|
|
1952
|
+
await this.runningProcesses.open();
|
|
1953
|
+
this.state = DataStoreState.NotRunning;
|
|
1954
|
+
}
|
|
1568
1955
|
|
|
1956
|
+
/**
|
|
1957
|
+
* Validates given pagination input from a query and creates a pagination
|
|
1958
|
+
* argument for use against the storage layer.
|
|
1959
|
+
*
|
|
1960
|
+
* @param modelDefinition
|
|
1961
|
+
* @param paginationProducer
|
|
1962
|
+
*/
|
|
1569
1963
|
private processPagination<T extends PersistentModel>(
|
|
1570
1964
|
modelDefinition: SchemaModel,
|
|
1571
1965
|
paginationProducer: ProducerPaginationInput<T>
|
|
@@ -1615,6 +2009,10 @@ class DataStore {
|
|
|
1615
2009
|
};
|
|
1616
2010
|
}
|
|
1617
2011
|
|
|
2012
|
+
/**
|
|
2013
|
+
* Examines the configured `syncExpressions` and produces a WeakMap of
|
|
2014
|
+
* SchemaModel -> predicate to use during sync.
|
|
2015
|
+
*/
|
|
1618
2016
|
private async processSyncExpressions(): Promise<
|
|
1619
2017
|
WeakMap<SchemaModel, ModelPredicate<any>>
|
|
1620
2018
|
> {
|
|
@@ -1700,7 +2098,10 @@ class DataStore {
|
|
|
1700
2098
|
}, new WeakMap<SchemaModel, ModelPredicate<any>>());
|
|
1701
2099
|
}
|
|
1702
2100
|
|
|
1703
|
-
|
|
2101
|
+
/**
|
|
2102
|
+
* A session ID to allow CMS to open databases against multiple apps.
|
|
2103
|
+
* This session ID is only expected be set by AWS Amplify Studio.
|
|
2104
|
+
*/
|
|
1704
2105
|
private retrieveSessionId(): string | undefined {
|
|
1705
2106
|
try {
|
|
1706
2107
|
const sessionId = sessionStorage.getItem('datastoreSessionId');
|
|
@@ -1713,9 +2114,9 @@ class DataStore {
|
|
|
1713
2114
|
|
|
1714
2115
|
return `${sessionId}-${appSyncId}`;
|
|
1715
2116
|
}
|
|
1716
|
-
} catch {
|
|
1717
|
-
|
|
1718
|
-
|
|
2117
|
+
} catch {}
|
|
2118
|
+
|
|
2119
|
+
return undefined;
|
|
1719
2120
|
}
|
|
1720
2121
|
}
|
|
1721
2122
|
|