#7001 feat(@aws-amplify/datastore): add Selective Sync

Merged Ivan Artemiev iartemiev
Coverage Reach
aws-amplify-react/src/Auth/Provider/withFacebook.tsx aws-amplify-react/src/Auth/Provider/withAuth0.tsx aws-amplify-react/src/Auth/Provider/withGoogle.tsx aws-amplify-react/src/Auth/Provider/withAmazon.tsx aws-amplify-react/src/Auth/Provider/withOAuth.tsx aws-amplify-react/src/Auth/Provider/index.tsx aws-amplify-react/src/Auth/Authenticator.tsx aws-amplify-react/src/Auth/SignUp.tsx aws-amplify-react/src/Auth/Greetings.tsx aws-amplify-react/src/Auth/AuthPiece.tsx aws-amplify-react/src/Auth/SignOut.tsx aws-amplify-react/src/Auth/FederatedSignIn.tsx aws-amplify-react/src/Auth/SignIn.tsx aws-amplify-react/src/Auth/VerifyContact.tsx aws-amplify-react/src/Auth/index.tsx aws-amplify-react/src/Auth/RequireNewPassword.tsx aws-amplify-react/src/Auth/ForgotPassword.tsx aws-amplify-react/src/Auth/ConfirmSignIn.tsx aws-amplify-react/src/Auth/ConfirmSignUp.tsx aws-amplify-react/src/Auth/TOTPSetup.tsx aws-amplify-react/src/Auth/PhoneField.tsx aws-amplify-react/src/Auth/Loading.tsx aws-amplify-react/src/Auth/common/types.ts aws-amplify-react/src/Auth/common/default-sign-up-fields.tsx aws-amplify-react/src/Auth/common/constants.tsx aws-amplify-react/src/Auth/common/country-dial-codes.tsx aws-amplify-react/src/Storage/S3Album.tsx aws-amplify-react/src/Storage/S3Image.tsx aws-amplify-react/src/Storage/S3Text.tsx aws-amplify-react/src/Storage/Common.tsx aws-amplify-react/src/Amplify-UI/Amplify-UI-Components-React.tsx aws-amplify-react/src/Amplify-UI/Amplify-UI-Theme.tsx aws-amplify-react/src/Amplify-UI/data-test-attributes.tsx aws-amplify-react/src/Widget/TOTPSetupComp.tsx aws-amplify-react/src/Widget/SelectMFAType.tsx aws-amplify-react/src/Widget/PhotoPicker.tsx aws-amplify-react/src/Widget/TextPicker.tsx aws-amplify-react/src/Widget/Picker.tsx aws-amplify-react/src/Widget/index.tsx aws-amplify-react/src/AmplifyUI.tsx aws-amplify-react/src/XR/SumerianScene.tsx aws-amplify-react/src/XR/IconButton.tsx aws-amplify-react/src/XR/Tooltip.tsx aws-amplify-react/src/XR/Loading.tsx aws-amplify-react/src/Analytics/trackLifecycle.tsx aws-amplify-react/src/AmplifyTheme.tsx aws-amplify-react/src/AmplifyMessageMap.tsx datastore/src/sync/processors/subscription.ts datastore/src/sync/processors/mutation.ts datastore/src/sync/processors/sync.ts datastore/src/sync/index.ts datastore/src/sync/utils.ts datastore/src/sync/outbox.ts datastore/src/sync/datastoreConnectivity.ts datastore/src/sync/merger.ts datastore/src/sync/datastoreReachability/index.ts datastore/src/storage/adapter/IndexedDBAdapter.ts datastore/src/storage/adapter/AsyncStorageAdapter.ts datastore/src/storage/adapter/AsyncStorageDatabase.ts datastore/src/storage/adapter/getDefaultAdapter/index.ts datastore/src/storage/storage.ts datastore/src/datastore/datastore.ts datastore/src/util.ts datastore/src/predicates/index.ts datastore/src/predicates/sort.ts datastore/src/types.ts datastore/src/index.ts datastore/__tests__/model.ts datastore/__tests__/schema.ts analytics/src/Providers/AWSPinpointProvider.ts analytics/src/Providers/AmazonPersonalizeProvider.ts analytics/src/Providers/AmazonPersonalizeHelper/MediaAutoTrack.ts analytics/src/Providers/AmazonPersonalizeHelper/SessionInfoManager.ts analytics/src/Providers/AmazonPersonalizeHelper/index.ts analytics/src/Providers/AWSKinesisProvider.ts analytics/src/Providers/EventBuffer.ts analytics/src/Providers/AWSKinesisFirehoseProvider.ts analytics/src/trackers/PageViewTracker.ts analytics/src/trackers/SessionTracker.ts analytics/src/trackers/SessionTracker-rn.ts analytics/src/trackers/EventTracker.ts analytics/src/trackers/index.ts analytics/src/Analytics.ts analytics/src/utils/MethodEmbed.ts analytics/src/utils/AppUtils.ts core/src/Credentials.ts core/src/Util/Retry.ts core/src/Util/Mutex.ts core/src/Util/Reachability.ts core/src/Util/Reachability.native.ts core/src/Util/DateUtils.ts core/src/Util/StringUtils.ts core/src/Util/index.ts core/src/Signer.ts core/src/JS.ts core/src/OAuthHelper/GoogleOAuth.ts core/src/OAuthHelper/FacebookOAuth.ts core/src/OAuthHelper/index.ts core/src/I18n/I18n.ts core/src/I18n/index.ts core/src/Hub.ts core/src/ServiceWorker/ServiceWorker.ts core/src/ServiceWorker/index.ts core/src/ClientDevice/browser.ts core/src/ClientDevice/index.ts core/src/Amplify.ts core/src/Logger/ConsoleLogger.ts core/src/Logger/index.ts core/src/UniversalStorage/index.ts core/src/StorageHelper/index.ts core/src/index.ts core/src/Parser.ts core/src/Platform/index.ts core/src/Platform/version.ts core/src/RNComponents/index.ts core/src/constants.ts core/src/Errors.ts auth/src/Auth.ts auth/src/OAuth/OAuth.ts auth/src/OAuth/oauthStorage.ts auth/src/OAuth/urlOpener.ts auth/src/types/Auth.ts auth/src/types/index.ts auth/src/Errors.ts auth/src/common/AuthErrorStrings.ts auth/src/urlListener.ts predictions/src/Providers/AmazonAIConvertPredictionsProvider.ts predictions/src/Providers/AmazonAIIdentifyPredictionsProvider.ts predictions/src/Providers/AmazonAIInterpretPredictionsProvider.ts predictions/src/Providers/IdentifyTextUtils.ts predictions/src/Providers/AmazonAIPredictionsProvider.ts predictions/src/Providers/Utils.ts predictions/src/Providers/index.ts predictions/src/types/Providers/AbstractIdentifyPredictionsProvider.ts predictions/src/types/Providers/AbstractConvertPredictionsProvider.ts predictions/src/types/Providers/AbstractInterpretPredictionsProvider.ts predictions/src/types/Providers/AbstractPredictionsProvider.ts predictions/src/types/Providers/index.ts predictions/src/types/Predictions.ts predictions/src/types/index.ts predictions/src/Predictions.ts predictions/src/index.ts aws-amplify-vue/src/components/authenticator/SignUp.vue aws-amplify-vue/src/components/authenticator/SignIn.vue aws-amplify-vue/src/components/authenticator/SetMFA.vue aws-amplify-vue/src/components/authenticator/ForgotPassword.vue aws-amplify-vue/src/components/authenticator/Authenticator.vue aws-amplify-vue/src/components/authenticator/RequireNewPassword.vue aws-amplify-vue/src/components/authenticator/ConfirmSignUp.vue aws-amplify-vue/src/components/authenticator/UsernameField.vue aws-amplify-vue/src/components/authenticator/ConfirmSignIn.vue aws-amplify-vue/src/components/authenticator/SignOut.vue aws-amplify-vue/src/components/authenticator/PhoneField.vue aws-amplify-vue/src/components/authenticator/index.js aws-amplify-vue/src/components/authenticator/common.js aws-amplify-vue/src/components/interactions/Chatbot.vue aws-amplify-vue/src/components/interactions/index.js aws-amplify-vue/src/components/storage/PhotoPicker.vue aws-amplify-vue/src/components/storage/S3Album.vue aws-amplify-vue/src/components/storage/S3Image.vue aws-amplify-vue/src/components/storage/index.js aws-amplify-vue/src/components/api/graphql/Connect.vue aws-amplify-vue/src/components/api/index.js aws-amplify-vue/src/components/xr/SumerianScene.vue aws-amplify-vue/src/components/xr/index.js aws-amplify-vue/src/assets/data-test-attributes.js aws-amplify-vue/src/assets/default-sign-up-fields.js aws-amplify-vue/src/assets/countries.js aws-amplify-vue/src/plugins/AmplifyPlugin.js aws-amplify-vue/src/services/getUser.js aws-amplify-vue/src/Amplify.vue aws-amplify-vue/src/events/AmplifyEventBus.js aws-amplify-vue/test_setup/setup-jest.ts pubsub/src/Providers/AWSAppSyncRealTimeProvider.ts pubsub/src/Providers/MqttOverWSProvider.ts pubsub/src/Providers/AWSAppSyncProvider.ts pubsub/src/Providers/AWSIotProvider.ts pubsub/src/Providers/PubSubProvider.ts pubsub/src/Providers/index.ts pubsub/src/PubSub.ts pubsub/src/index.ts storage/src/providers/AWSS3Provider.ts storage/src/providers/AWSS3ProviderManagedUpload.ts storage/src/providers/axios-http-handler.ts storage/src/providers/index.ts storage/src/Storage.ts storage/src/index.ts cache/src/BrowserStorageCache.ts cache/src/InMemoryCache.ts cache/src/Utils/CacheList.ts cache/src/Utils/CacheUtils.ts cache/src/Utils/index.ts cache/src/StorageCache.ts api-rest/src/RestClient.ts api-rest/src/RestAPI.ts api-rest/src/index.ts xr/src/Providers/SumerianProvider.ts xr/src/Providers/XRProvider.ts xr/src/XR.ts xr/src/Errors.ts pushnotification/src/PushNotification.ts pushnotification/src/index.ts api-graphql/src/GraphQLAPI.ts api-graphql/src/index.ts api-graphql/src/types/index.ts interactions/src/Providers/AWSLexProvider.ts interactions/src/Providers/InteractionsProvider.ts interactions/src/Providers/AWSLexProviderHelper/convert.ts interactions/src/Providers/index.ts interactions/src/Interactions.ts aws-amplify/src/index.ts aws-amplify/src/withSSRContext.ts api/src/API.ts

No flags found

Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.

e.g., #unittest #integration

#production #enterprise

#frontend #backend

Learn more about Codecov Flags here.


@@ -4,9 +4,16 @@
Loading
4 4
	InternalSchema,
5 5
	ModelInstanceMetadata,
6 6
	SchemaModel,
7 +
	ModelPredicate,
8 +
	PredicatesGroup,
9 +
	GraphQLFilter,
7 10
} from '../../types';
8 -
import { buildGraphQLOperation } from '../utils';
9 -
import { jitteredExponentialRetry, ConsoleLogger as Logger } from '@aws-amplify/core';
11 +
import { buildGraphQLOperation, predicateToGraphQLFilter } from '../utils';
12 +
import {
13 +
	jitteredExponentialRetry,
14 +
	ConsoleLogger as Logger,
15 +
} from '@aws-amplify/core';
16 +
import { ModelPredicateCreator } from '../../predicates';
10 17
11 18
const logger = new Logger('DataStore');
12 19
@@ -19,7 +26,8 @@
Loading
19 26
	constructor(
20 27
		private readonly schema: InternalSchema,
21 28
		private readonly maxRecordsToSync: number = DEFAULT_MAX_RECORDS_TO_SYNC,
22 -
		private readonly syncPageSize: number = DEFAULT_PAGINATION_LIMIT
29 +
		private readonly syncPageSize: number = DEFAULT_PAGINATION_LIMIT,
30 +
		private readonly syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>
23 31
	) {
24 32
		this.generateQueries();
25 33
	}
@@ -40,20 +48,38 @@
Loading
40 48
		});
41 49
	}
42 50
51 +
	private graphqlFilterFromPredicate(model: SchemaModel): GraphQLFilter {
52 +
		if (!this.syncPredicates) {
53 +
			return null;
54 +
		}
55 +
		const predicatesGroup: PredicatesGroup<any> = ModelPredicateCreator.getPredicates(
56 +
			this.syncPredicates.get(model),
57 +
			false
58 +
		);
59 +
60 +
		if (!predicatesGroup) {
61 +
			return null;
62 +
		}
63 +
64 +
		return predicateToGraphQLFilter(predicatesGroup);
65 +
	}
66 +
43 67
	private async retrievePage<
44 68
		T extends ModelInstanceMetadata = ModelInstanceMetadata
45 69
	>(
46 70
		modelDefinition: SchemaModel,
47 71
		lastSync: number,
48 72
		nextToken: string,
49 -
		limit: number = null
73 +
		limit: number = null,
74 +
		filter: GraphQLFilter
50 75
	): Promise<{ nextToken: string; startedAt: number; items: T[] }> {
51 76
		const [opName, query] = this.typeQuery.get(modelDefinition);
52 77
53 78
		const variables = {
54 79
			limit,
55 80
			nextToken,
56 81
			lastSync,
82 +
			filter,
57 83
		};
58 84
59 85
		const { data } = <
@@ -95,11 +121,18 @@
Loading
95 121
					});
96 122
				} catch (error) {
97 123
					// If the error is unauthorized, filter out unauthorized items and return accessible items
98 -
					const unauthorized = (error.errors as [any]).some(err => err.errorType === 'Unauthorized');
124 +
					const unauthorized = (error.errors as [any]).some(
125 +
						err => err.errorType === 'Unauthorized'
126 +
					);
99 127
					if (unauthorized) {
100 128
						const result = error;
101 -
						result.data[opName].items = result.data[opName].items.filter(item => item !== null);
102 -
						logger.warn('queryError', 'User is unauthorized, some items could not be returned.');
129 +
						result.data[opName].items = result.data[opName].items.filter(
130 +
							item => item !== null
131 +
						);
132 +
						logger.warn(
133 +
							'queryError',
134 +
							'User is unauthorized, some items could not be returned.'
135 +
						);
103 136
						return result;
104 137
					} else {
105 138
						throw error;
@@ -150,6 +183,7 @@
Loading
150 183
					let items: ModelInstanceMetadata[] = null;
151 184
152 185
					let recordsReceived = 0;
186 +
					const filter = this.graphqlFilterFromPredicate(modelDefinition);
153 187
154 188
					const parents = this.schema.namespaces[
155 189
						namespace
@@ -175,7 +209,8 @@
Loading
175 209
								modelDefinition,
176 210
								lastSync,
177 211
								nextToken,
178 -
								limit
212 +
								limit,
213 +
								filter
179 214
							));
180 215
181 216
							recordsReceived += items.length;

@@ -38,6 +38,7 @@
Loading
38 38
	SyncError,
39 39
	TypeConstructorMap,
40 40
	ErrorHandler,
41 +
	SyncExpression,
41 42
} from '../types';
42 43
import {
43 44
	DATASTORE,
@@ -545,6 +546,9 @@
Loading
545 546
	private storage: Storage;
546 547
	private sync: SyncEngine;
547 548
	private syncPageSize: number;
549 +
	private syncExpressions: SyncExpression<any>[];
550 +
	private syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>;
551 +
	private syncModelsUpdated: Set<string> = new Set<string>();
548 552
549 553
	getModuleName() {
550 554
		return 'DataStore';
@@ -579,6 +583,8 @@
Loading
579 583
		if (aws_appsync_graphqlEndpoint) {
580 584
			logger.debug('GraphQL endpoint available', aws_appsync_graphqlEndpoint);
581 585
586 +
			this.syncPredicates = await this.processSyncExpressions();
587 +
582 588
			this.sync = new SyncEngine(
583 589
				schema,
584 590
				namespaceResolver,
@@ -589,7 +595,9 @@
Loading
589 595
				this.maxRecordsToSync,
590 596
				this.syncPageSize,
591 597
				this.conflictHandler,
592 -
				this.errorHandler
598 +
				this.errorHandler,
599 +
				this.syncPredicates,
600 +
				this.syncModelsUpdated
593 601
			);
594 602
595 603
			// tslint:disable-next-line:max-line-length
@@ -681,8 +689,6 @@
Loading
681 689
					modelDefinition,
682 690
					idOrCriteria
683 691
				);
684 -
685 -
				logger.debug('after createFromExisting - predicate', predicate);
686 692
			}
687 693
		}
688 694
@@ -981,6 +987,7 @@
Loading
981 987
			maxRecordsToSync: configMaxRecordsToSync,
982 988
			syncPageSize: configSyncPageSize,
983 989
			fullSyncInterval: configFullSyncInterval,
990 +
			syncExpressions: configSyncExpressions,
984 991
			...configFromAmplify
985 992
		} = config;
986 993
@@ -989,20 +996,25 @@
Loading
989 996
		this.conflictHandler = this.setConflictHandler(config);
990 997
		this.errorHandler = this.setErrorHandler(config);
991 998
999 +
		this.syncExpressions =
1000 +
			(configDataStore && configDataStore.syncExpressions) ||
1001 +
			this.syncExpressions ||
1002 +
			configSyncExpressions;
1003 +
992 1004
		this.maxRecordsToSync =
993 1005
			(configDataStore && configDataStore.maxRecordsToSync) ||
994 1006
			this.maxRecordsToSync ||
995 -
			config.maxRecordsToSync;
1007 +
			configMaxRecordsToSync;
996 1008
997 1009
		this.syncPageSize =
998 1010
			(configDataStore && configDataStore.syncPageSize) ||
999 1011
			this.syncPageSize ||
1000 -
			config.syncPageSize;
1012 +
			configSyncPageSize;
1001 1013
1002 1014
		this.fullSyncInterval =
1003 1015
			(configDataStore && configDataStore.fullSyncInterval) ||
1016 +
			this.fullSyncInterval ||
1004 1017
			configFullSyncInterval ||
1005 -
			config.fullSyncInterval ||
1006 1018
			24 * 60; // 1 day
1007 1019
	};
1008 1020
@@ -1020,6 +1032,20 @@
Loading
1020 1032
		this.initialized = undefined; // Should re-initialize when start() is called.
1021 1033
		this.storage = undefined;
1022 1034
		this.sync = undefined;
1035 +
		this.syncPredicates = undefined;
1036 +
	};
1037 +
1038 +
	stop = async function stop() {
1039 +
		if (this.initialized !== undefined) {
1040 +
			await this.start();
1041 +
		}
1042 +
1043 +
		if (syncSubscription && !syncSubscription.closed) {
1044 +
			syncSubscription.unsubscribe();
1045 +
		}
1046 +
1047 +
		this.initialized = undefined; // Should re-initialize when start() is called.
1048 +
		this.sync = undefined;
1023 1049
	};
1024 1050
1025 1051
	private processPagination<T extends PersistentModel>(
@@ -1066,6 +1092,113 @@
Loading
1066 1092
			sort: sortPredicate,
1067 1093
		};
1068 1094
	}
1095 +
1096 +
	private async processSyncExpressions(): Promise<
1097 +
		WeakMap<SchemaModel, ModelPredicate<any>>
1098 +
	> {
1099 +
		if (!this.syncExpressions || !this.syncExpressions.length) {
1100 +
			return;
1101 +
		}
1102 +
1103 +
		const syncPredicates = await Promise.all(
1104 +
			this.syncExpressions.map(
1105 +
				async <T extends PersistentModel>(
1106 +
					syncExpression: SyncExpression<T>
1107 +
				): Promise<[SchemaModel, ModelPredicate<any>]> => {
1108 +
					const { modelConstructor, conditionProducer } = await syncExpression;
1109 +
					const modelDefinition = getModelDefinition(modelConstructor);
1110 +
1111 +
					// conditionProducer is either a predicate, e.g. (c) => c.field('eq', 1)
1112 +
					// OR a function/promise that returns a predicate
1113 +
					const condition = await this.unwrapPromise(conditionProducer);
1114 +
					const predicate = this.createFromCondition(
1115 +
						modelDefinition,
1116 +
						condition
1117 +
					);
1118 +
1119 +
					return [modelDefinition, predicate];
1120 +
				}
1121 +
			)
1122 +
		);
1123 +
1124 +
		this.compareSyncPredicates(syncPredicates);
1125 +
1126 +
		return this.weakMapFromEntries(syncPredicates);
1127 +
	}
1128 +
1129 +
	private compareSyncPredicates(
1130 +
		syncPredicates: [SchemaModel, ModelPredicate<any>][]
1131 +
	) {
1132 +
		if (!this.syncPredicates) {
1133 +
			return;
1134 +
		}
1135 +
1136 +
		this.syncModelsUpdated = new Set<string>();
1137 +
1138 +
		syncPredicates.forEach(([modelDefinition, predicate]) => {
1139 +
			const previousPredicate = ModelPredicateCreator.getPredicates(
1140 +
				this.syncPredicates.get(modelDefinition),
1141 +
				false
1142 +
			);
1143 +
1144 +
			const newPredicate = ModelPredicateCreator.getPredicates(
1145 +
				predicate,
1146 +
				false
1147 +
			);
1148 +
1149 +
			const predicateChanged =
1150 +
				JSON.stringify(previousPredicate) !== JSON.stringify(newPredicate);
1151 +
1152 +
			predicateChanged && this.syncModelsUpdated.add(modelDefinition.name);
1153 +
		});
1154 +
	}
1155 +
1156 +
	private createFromCondition(
1157 +
		modelDefinition: SchemaModel,
1158 +
		condition: ProducerModelPredicate<PersistentModel>
1159 +
	) {
1160 +
		try {
1161 +
			return ModelPredicateCreator.createFromExisting(
1162 +
				modelDefinition,
1163 +
				condition
1164 +
			);
1165 +
		} catch (error) {
1166 +
			logger.error('Error creating Sync Predicate');
1167 +
			throw error;
1168 +
		}
1169 +
	}
1170 +
1171 +
	private async unwrapPromise<T extends PersistentModel>(
1172 +
		conditionProducer
1173 +
	): Promise<ProducerModelPredicate<T>> {
1174 +
		try {
1175 +
			const condition = await conditionProducer();
1176 +
			return condition;
1177 +
		} catch (error) {
1178 +
			if (error instanceof TypeError) {
1179 +
				return conditionProducer;
1180 +
			}
1181 +
			throw error;
1182 +
		}
1183 +
	}
1184 +
1185 +
	private weakMapFromEntries(
1186 +
		entries: [SchemaModel, ModelPredicate<any>][]
1187 +
	): WeakMap<SchemaModel, ModelPredicate<any>> {
1188 +
		return entries.reduce((map, [modelDefinition, predicate]) => {
1189 +
			if (map.has(modelDefinition)) {
1190 +
				const { name } = modelDefinition;
1191 +
				logger.warn(
1192 +
					`You can only utilize one Sync Expression per model. 
1193 +
					Subsequent sync expressions for the ${name} model will be ignored.`
1194 +
				);
1195 +
				return map;
1196 +
			}
1197 +
1198 +
			map.set(modelDefinition, predicate);
1199 +
			return map;
1200 +
		}, new WeakMap<SchemaModel, ModelPredicate<any>>());
1201 +
	}
1069 1202
}
1070 1203
1071 1204
const instance = new DataStore();

@@ -2,6 +2,8 @@
Loading
2 2
import {
3 3
	AuthorizationRule,
4 4
	GraphQLCondition,
5 +
	GraphQLFilter,
6 +
	GraphQLField,
5 7
	isEnumFieldType,
6 8
	isGraphQLScalarType,
7 9
	isPredicateObj,
@@ -271,9 +273,9 @@
Loading
271 273
	switch (graphQLOpType) {
272 274
		case 'LIST':
273 275
			operation = `sync${pluralTypeName}`;
274 -
			documentArgs = `($limit: Int, $nextToken: String, $lastSync: AWSTimestamp)`;
276 +
			documentArgs = `($limit: Int, $nextToken: String, $lastSync: AWSTimestamp, $filter: Model${typeName}FilterInput)`;
275 277
			operationArgs =
276 -
				'(limit: $limit, nextToken: $nextToken, lastSync: $lastSync)';
278 +
				'(limit: $limit, nextToken: $nextToken, lastSync: $lastSync, filter: $filter)';
277 279
			selectionSet = `items {
278 280
							${selectionSet}
279 281
						}
@@ -388,3 +390,38 @@
Loading
388 390
389 391
	return result;
390 392
}
393 +
394 +
export function predicateToGraphQLFilter(
395 +
	predicatesGroup: PredicatesGroup<any>
396 +
): GraphQLFilter {
397 +
	const result: GraphQLFilter = {};
398 +
399 +
	if (!predicatesGroup || !Array.isArray(predicatesGroup.predicates)) {
400 +
		return result;
401 +
	}
402 +
403 +
	const { type, predicates } = predicatesGroup;
404 +
	const isList = type === 'and' || type === 'or';
405 +
406 +
	result[type] = isList ? [] : {};
407 +
408 +
	const appendToFilter = value =>
409 +
		isList ? result[type].push(value) : (result[type] = value);
410 +
411 +
	predicates.forEach(predicate => {
412 +
		if (isPredicateObj(predicate)) {
413 +
			const { field, operator, operand } = predicate;
414 +
415 +
			const gqlField: GraphQLField = {
416 +
				[field]: { [operator]: operand },
417 +
			};
418 +
419 +
			appendToFilter(gqlField);
420 +
			return;
421 +
		}
422 +
423 +
		appendToFilter(predicateToGraphQLFilter(predicate));
424 +
	});
425 +
426 +
	return result;
427 +
}

@@ -15,24 +15,22 @@
Loading
15 15
			return Observable.from([{ online: true }]);
16 16
		}
17 17
18 -
		return new Observable(observer => {
19 -
			const online = isWebWorker()
20 -
				? self.navigator.onLine
21 -
				: window.navigator.onLine;
18 +
		const globalObj = isWebWorker() ? self : window;
22 19
23 -
			observer.next({ online });
20 +
		return new Observable(observer => {
21 +
			observer.next({ online: globalObj.navigator.onLine });
24 22
25 23
			const notifyOnline = () => observer.next({ online: true });
26 24
			const notifyOffline = () => observer.next({ online: false });
27 25
28 -
			window.addEventListener('online', notifyOnline);
29 -
			window.addEventListener('offline', notifyOffline);
26 +
			globalObj.addEventListener('online', notifyOnline);
27 +
			globalObj.addEventListener('offline', notifyOffline);
30 28
31 29
			ReachabilityNavigator._observers.push(observer);
32 30
33 31
			return () => {
34 -
				window.removeEventListener('online', notifyOnline);
35 -
				window.removeEventListener('offline', notifyOffline);
32 +
				globalObj.removeEventListener('online', notifyOnline);
33 +
				globalObj.removeEventListener('offline', notifyOffline);
36 34
37 35
				ReachabilityNavigator._observers = ReachabilityNavigator._observers.filter(
38 36
					_observer => _observer !== observer

@@ -5,10 +5,10 @@
Loading
5 5
	const { isBrowser } = browserOrNode();
6 6
7 7
	if ((isBrowser && window.indexedDB) || (isWebWorker() && self.indexedDB)) {
8 -
		return require('../indexeddb').default;
8 +
		return require('../IndexedDBAdapter').default;
9 9
	}
10 10
11 -
	const { AsyncStorageAdapter } = require('../asyncstorage');
11 +
	const { AsyncStorageAdapter } = require('../AsyncStorageAdapter');
12 12
13 13
	return new AsyncStorageAdapter();
14 14
};

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Learn more Showing 2 files with coverage changes found.

New file packages/datastore/src/storage/adapter/IndexedDBAdapter.ts
New
Loading file...
New file packages/datastore/src/storage/adapter/AsyncStorageAdapter.ts
New
Loading file...
Files Coverage
packages -0.31% 72.97%
Project Totals (213 files) 72.97%
Loading