@aws-amplify/datastore 4.0.12 → 4.0.13-push-notification-dryrun.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/datastore/datastore.d.ts +29 -3
- package/lib/datastore/datastore.js +308 -147
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/predicates/index.d.ts +77 -7
- package/lib/predicates/index.js +142 -122
- package/lib/predicates/index.js.map +1 -1
- package/lib/predicates/next.d.ts +51 -10
- package/lib/predicates/next.js +111 -91
- package/lib/predicates/next.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
- package/lib/storage/adapter/AsyncStorageAdapter.js +135 -532
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +28 -29
- package/lib/storage/adapter/IndexedDBAdapter.js +490 -885
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/StorageAdapterBase.d.ts +134 -0
- package/lib/storage/adapter/StorageAdapterBase.js +439 -0
- package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/lib/storage/relationship.d.ts +9 -0
- package/lib/storage/relationship.js +9 -0
- package/lib/storage/relationship.js.map +1 -1
- package/lib/storage/storage.d.ts +1 -1
- package/lib/storage/storage.js +4 -3
- package/lib/storage/storage.js.map +1 -1
- package/lib/sync/index.d.ts +15 -1
- package/lib/sync/index.js +80 -13
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/outbox.js +14 -7
- package/lib/sync/outbox.js.map +1 -1
- package/lib/sync/processors/mutation.d.ts +10 -1
- package/lib/sync/processors/mutation.js +33 -12
- package/lib/sync/processors/mutation.js.map +1 -1
- package/lib/sync/processors/subscription.d.ts +7 -1
- package/lib/sync/processors/subscription.js +196 -135
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.d.ts +1 -1
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/sync/utils.d.ts +66 -2
- package/lib/sync/utils.js +264 -16
- package/lib/sync/utils.js.map +1 -1
- package/lib/types.d.ts +9 -1
- package/lib/types.js.map +1 -1
- package/lib/util.d.ts +16 -0
- package/lib/util.js +31 -2
- package/lib/util.js.map +1 -1
- package/lib-esm/datastore/datastore.d.ts +29 -3
- package/lib-esm/datastore/datastore.js +310 -149
- package/lib-esm/datastore/datastore.js.map +1 -1
- package/lib-esm/predicates/index.d.ts +77 -7
- package/lib-esm/predicates/index.js +143 -123
- package/lib-esm/predicates/index.js.map +1 -1
- package/lib-esm/predicates/next.d.ts +51 -10
- package/lib-esm/predicates/next.js +111 -91
- package/lib-esm/predicates/next.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +138 -535
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +28 -29
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +489 -884
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +134 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js +437 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/lib-esm/storage/relationship.d.ts +9 -0
- package/lib-esm/storage/relationship.js +9 -0
- package/lib-esm/storage/relationship.js.map +1 -1
- package/lib-esm/storage/storage.d.ts +1 -1
- package/lib-esm/storage/storage.js +4 -3
- package/lib-esm/storage/storage.js.map +1 -1
- package/lib-esm/sync/index.d.ts +15 -1
- package/lib-esm/sync/index.js +82 -15
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/outbox.js +14 -7
- package/lib-esm/sync/outbox.js.map +1 -1
- package/lib-esm/sync/processors/mutation.d.ts +10 -1
- package/lib-esm/sync/processors/mutation.js +33 -12
- package/lib-esm/sync/processors/mutation.js.map +1 -1
- package/lib-esm/sync/processors/subscription.d.ts +7 -1
- package/lib-esm/sync/processors/subscription.js +197 -136
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.d.ts +1 -1
- package/lib-esm/sync/processors/sync.js.map +1 -1
- package/lib-esm/sync/utils.d.ts +66 -2
- package/lib-esm/sync/utils.js +261 -18
- package/lib-esm/sync/utils.js.map +1 -1
- package/lib-esm/types.d.ts +9 -1
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +16 -0
- package/lib-esm/util.js +32 -3
- package/lib-esm/util.js.map +1 -1
- package/package.json +12 -11
- package/src/datastore/datastore.ts +288 -159
- package/src/predicates/index.ts +145 -175
- package/src/predicates/next.ts +114 -81
- package/src/storage/adapter/AsyncStorageAdapter.ts +97 -563
- package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
- package/src/storage/adapter/IndexedDBAdapter.ts +318 -770
- package/src/storage/adapter/StorageAdapterBase.ts +545 -0
- package/src/storage/relationship.ts +9 -0
- package/src/storage/storage.ts +12 -9
- package/src/sync/index.ts +108 -20
- package/src/sync/outbox.ts +17 -11
- package/src/sync/processors/mutation.ts +35 -4
- package/src/sync/processors/subscription.ts +124 -10
- package/src/sync/processors/sync.ts +4 -1
- package/src/sync/utils.ts +285 -15
- package/src/types.ts +15 -2
- package/src/util.ts +40 -1
- package/CHANGELOG.md +0 -904
package/src/sync/index.ts
CHANGED
|
@@ -2,8 +2,13 @@ import {
|
|
|
2
2
|
browserOrNode,
|
|
3
3
|
ConsoleLogger as Logger,
|
|
4
4
|
BackgroundProcessManager,
|
|
5
|
+
Hub,
|
|
5
6
|
} from '@aws-amplify/core';
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CONTROL_MSG as PUBSUB_CONTROL_MSG,
|
|
9
|
+
CONNECTION_STATE_CHANGE as PUBSUB_CONNECTION_STATE_CHANGE,
|
|
10
|
+
ConnectionState,
|
|
11
|
+
} from '@aws-amplify/pubsub';
|
|
7
12
|
import Observable, { ZenObservable } from 'zen-observable-ts';
|
|
8
13
|
import { ModelInstanceCreator } from '../datastore/datastore';
|
|
9
14
|
import { ModelPredicateCreator } from '../predicates';
|
|
@@ -116,6 +121,13 @@ export class SyncEngine {
|
|
|
116
121
|
PersistentModelConstructor<any>,
|
|
117
122
|
boolean
|
|
118
123
|
> = new WeakMap();
|
|
124
|
+
private unsleepSyncQueriesObservable: (() => void) | null;
|
|
125
|
+
private waitForSleepState: Promise<void>;
|
|
126
|
+
private syncQueriesObservableStartSleeping: (
|
|
127
|
+
value?: void | PromiseLike<void>
|
|
128
|
+
) => void;
|
|
129
|
+
private stopDisruptionListener: () => void;
|
|
130
|
+
private connectionDisrupted = false;
|
|
119
131
|
|
|
120
132
|
private runningProcesses: BackgroundProcessManager;
|
|
121
133
|
|
|
@@ -134,13 +146,19 @@ export class SyncEngine {
|
|
|
134
146
|
private readonly modelInstanceCreator: ModelInstanceCreator,
|
|
135
147
|
conflictHandler: ConflictHandler,
|
|
136
148
|
errorHandler: ErrorHandler,
|
|
137
|
-
private readonly syncPredicates: WeakMap<
|
|
149
|
+
private readonly syncPredicates: WeakMap<
|
|
150
|
+
SchemaModel,
|
|
151
|
+
ModelPredicate<any> | null
|
|
152
|
+
>,
|
|
138
153
|
private readonly amplifyConfig: Record<string, any> = {},
|
|
139
154
|
private readonly authModeStrategy: AuthModeStrategy,
|
|
140
155
|
private readonly amplifyContext: AmplifyContext,
|
|
141
156
|
private readonly connectivityMonitor?: DataStoreConnectivity
|
|
142
157
|
) {
|
|
143
158
|
this.runningProcesses = new BackgroundProcessManager();
|
|
159
|
+
this.waitForSleepState = new Promise(resolve => {
|
|
160
|
+
this.syncQueriesObservableStartSleeping = resolve;
|
|
161
|
+
});
|
|
144
162
|
|
|
145
163
|
const MutationEvent = this.modelClasses[
|
|
146
164
|
'MutationEvent'
|
|
@@ -234,6 +252,8 @@ export class SyncEngine {
|
|
|
234
252
|
'Realtime disabled when in a server-side environment'
|
|
235
253
|
);
|
|
236
254
|
} else {
|
|
255
|
+
this.stopDisruptionListener =
|
|
256
|
+
this.startDisruptionListener();
|
|
237
257
|
//#region GraphQL Subscriptions
|
|
238
258
|
[ctlSubsObservable, dataSubsObservable] =
|
|
239
259
|
this.subscriptionsProcessor.start();
|
|
@@ -448,6 +468,7 @@ export class SyncEngine {
|
|
|
448
468
|
|
|
449
469
|
await startPromise;
|
|
450
470
|
|
|
471
|
+
// Set by the this.datastoreConnectivity.status().subscribe() loop
|
|
451
472
|
if (this.online) {
|
|
452
473
|
this.mutationsProcessor.resume();
|
|
453
474
|
}
|
|
@@ -540,12 +561,12 @@ export class SyncEngine {
|
|
|
540
561
|
);
|
|
541
562
|
const paginatingModels = new Set(modelLastSync.keys());
|
|
542
563
|
|
|
543
|
-
let
|
|
544
|
-
let
|
|
564
|
+
let lastFullSyncStartedAt: number;
|
|
565
|
+
let syncInterval: number;
|
|
545
566
|
|
|
546
567
|
let start: number;
|
|
547
|
-
let
|
|
548
|
-
let
|
|
568
|
+
let syncDuration: number;
|
|
569
|
+
let lastStartedAt: number;
|
|
549
570
|
await new Promise((resolve, reject) => {
|
|
550
571
|
if (!this.runningProcesses.isOpen) resolve();
|
|
551
572
|
onTerminate.then(() => resolve());
|
|
@@ -572,10 +593,10 @@ export class SyncEngine {
|
|
|
572
593
|
});
|
|
573
594
|
|
|
574
595
|
start = getNow();
|
|
575
|
-
|
|
576
|
-
|
|
596
|
+
lastStartedAt =
|
|
597
|
+
lastStartedAt === undefined
|
|
577
598
|
? startedAt
|
|
578
|
-
: Math.max(
|
|
599
|
+
: Math.max(lastStartedAt, startedAt);
|
|
579
600
|
}
|
|
580
601
|
|
|
581
602
|
/**
|
|
@@ -655,13 +676,13 @@ export class SyncEngine {
|
|
|
655
676
|
|
|
656
677
|
const { lastFullSync, fullSyncInterval } = modelMetadata;
|
|
657
678
|
|
|
658
|
-
|
|
679
|
+
syncInterval = fullSyncInterval;
|
|
659
680
|
|
|
660
|
-
|
|
661
|
-
|
|
681
|
+
lastFullSyncStartedAt =
|
|
682
|
+
lastFullSyncStartedAt === undefined
|
|
662
683
|
? lastFullSync!
|
|
663
684
|
: Math.max(
|
|
664
|
-
|
|
685
|
+
lastFullSyncStartedAt,
|
|
665
686
|
isFullSync ? startedAt : lastFullSync!
|
|
666
687
|
);
|
|
667
688
|
|
|
@@ -699,7 +720,7 @@ export class SyncEngine {
|
|
|
699
720
|
paginatingModels.delete(modelDefinition);
|
|
700
721
|
|
|
701
722
|
if (paginatingModels.size === 0) {
|
|
702
|
-
|
|
723
|
+
syncDuration = getNow() - start;
|
|
703
724
|
resolve();
|
|
704
725
|
observer.next({
|
|
705
726
|
type: ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY,
|
|
@@ -721,10 +742,19 @@ export class SyncEngine {
|
|
|
721
742
|
});
|
|
722
743
|
});
|
|
723
744
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
745
|
+
// null is cast to 0 resulting in unexpected behavior.
|
|
746
|
+
// undefined in arithmetic operations results in NaN also resulting in unexpected behavior.
|
|
747
|
+
// If lastFullSyncStartedAt is null this is the first sync.
|
|
748
|
+
// Assume lastStartedAt is is also newest full sync.
|
|
749
|
+
let msNextFullSync;
|
|
750
|
+
if (!lastFullSyncStartedAt!) {
|
|
751
|
+
msNextFullSync = syncInterval! - syncDuration!;
|
|
752
|
+
} else {
|
|
753
|
+
msNextFullSync =
|
|
754
|
+
lastFullSyncStartedAt! +
|
|
755
|
+
syncInterval! -
|
|
756
|
+
(lastStartedAt! + syncDuration!);
|
|
757
|
+
}
|
|
728
758
|
|
|
729
759
|
logger.debug(
|
|
730
760
|
`Next fullSync in ${msNextFullSync / 1000} seconds. (${new Date(
|
|
@@ -756,11 +786,19 @@ export class SyncEngine {
|
|
|
756
786
|
|
|
757
787
|
onTerminate.then(() => {
|
|
758
788
|
terminated = true;
|
|
789
|
+
this.syncQueriesObservableStartSleeping();
|
|
759
790
|
unsleep();
|
|
760
791
|
});
|
|
761
792
|
|
|
793
|
+
this.unsleepSyncQueriesObservable = unsleep;
|
|
794
|
+
this.syncQueriesObservableStartSleeping();
|
|
762
795
|
return sleep;
|
|
763
796
|
}, 'syncQueriesObservable sleep');
|
|
797
|
+
|
|
798
|
+
this.unsleepSyncQueriesObservable = null;
|
|
799
|
+
this.waitForSleepState = new Promise(resolve => {
|
|
800
|
+
this.syncQueriesObservableStartSleeping = resolve;
|
|
801
|
+
});
|
|
764
802
|
}
|
|
765
803
|
}, 'syncQueriesObservable main');
|
|
766
804
|
});
|
|
@@ -795,6 +833,11 @@ export class SyncEngine {
|
|
|
795
833
|
*/
|
|
796
834
|
this.unsubscribeConnectivity();
|
|
797
835
|
|
|
836
|
+
/**
|
|
837
|
+
* Stop listening for websocket connection disruption
|
|
838
|
+
*/
|
|
839
|
+
this.stopDisruptionListener && this.stopDisruptionListener();
|
|
840
|
+
|
|
798
841
|
/**
|
|
799
842
|
* aggressively shut down any lingering background processes.
|
|
800
843
|
* some of this might be semi-redundant with unsubscribing. however,
|
|
@@ -906,9 +949,9 @@ export class SyncEngine {
|
|
|
906
949
|
const ModelMetadata = this.modelClasses
|
|
907
950
|
.ModelMetadata as PersistentModelConstructor<ModelMetadata>;
|
|
908
951
|
|
|
909
|
-
const predicate = ModelPredicateCreator.
|
|
952
|
+
const predicate = ModelPredicateCreator.createFromAST<ModelMetadata>(
|
|
910
953
|
this.schema.namespaces[SYNC].models[ModelMetadata.name],
|
|
911
|
-
|
|
954
|
+
{ and: [{ namespace: { eq: namespace } }, { model: { eq: model } }] }
|
|
912
955
|
);
|
|
913
956
|
|
|
914
957
|
const [modelMetadata] = await this.storage.query(ModelMetadata, predicate, {
|
|
@@ -1040,4 +1083,49 @@ export class SyncEngine {
|
|
|
1040
1083
|
};
|
|
1041
1084
|
return namespace;
|
|
1042
1085
|
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* listen for websocket connection disruption
|
|
1089
|
+
*
|
|
1090
|
+
* May indicate there was a period of time where messages
|
|
1091
|
+
* from AppSync were missed. A sync needs to be triggered to
|
|
1092
|
+
* retrieve the missed data.
|
|
1093
|
+
*/
|
|
1094
|
+
private startDisruptionListener() {
|
|
1095
|
+
return Hub.listen('api', (data: any) => {
|
|
1096
|
+
if (
|
|
1097
|
+
data.source === 'PubSub' &&
|
|
1098
|
+
data.payload.event === PUBSUB_CONNECTION_STATE_CHANGE
|
|
1099
|
+
) {
|
|
1100
|
+
const connectionState = data.payload.data
|
|
1101
|
+
.connectionState as ConnectionState;
|
|
1102
|
+
|
|
1103
|
+
switch (connectionState) {
|
|
1104
|
+
// Do not need to listen for ConnectionDisruptedPendingNetwork
|
|
1105
|
+
// Normal network reconnection logic will handle the sync
|
|
1106
|
+
case ConnectionState.ConnectionDisrupted:
|
|
1107
|
+
this.connectionDisrupted = true;
|
|
1108
|
+
break;
|
|
1109
|
+
|
|
1110
|
+
case ConnectionState.Connected:
|
|
1111
|
+
if (this.connectionDisrupted) {
|
|
1112
|
+
this.scheduleSync();
|
|
1113
|
+
}
|
|
1114
|
+
this.connectionDisrupted = false;
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/*
|
|
1122
|
+
* Schedule a sync to start when syncQueriesObservable enters sleep state
|
|
1123
|
+
* Start sync immediately if syncQueriesObservable is already in sleep state
|
|
1124
|
+
*/
|
|
1125
|
+
private scheduleSync(): Promise<void> {
|
|
1126
|
+
return this.waitForSleepState.then(() => {
|
|
1127
|
+
// unsleepSyncQueriesObservable will be set if waitForSleepState has resolved
|
|
1128
|
+
this.unsleepSyncQueriesObservable!();
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1043
1131
|
}
|
package/src/sync/outbox.ts
CHANGED
|
@@ -38,12 +38,14 @@ class MutationEventOutbox {
|
|
|
38
38
|
|
|
39
39
|
// `id` is the key for the record in the mutationEvent;
|
|
40
40
|
// `modelId` is the key for the actual record that was mutated
|
|
41
|
-
const predicate = ModelPredicateCreator.
|
|
41
|
+
const predicate = ModelPredicateCreator.createFromAST<MutationEvent>(
|
|
42
42
|
mutationEventModelDefinition,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
{
|
|
44
|
+
and: [
|
|
45
|
+
{ modelId: { eq: mutationEvent.modelId } },
|
|
46
|
+
{ id: { ne: this.inProgressMutationEventId } },
|
|
47
|
+
],
|
|
48
|
+
}
|
|
47
49
|
);
|
|
48
50
|
|
|
49
51
|
// Check if there are any other records with same id
|
|
@@ -138,10 +140,9 @@ class MutationEventOutbox {
|
|
|
138
140
|
|
|
139
141
|
const mutationEvents = await storage.query(
|
|
140
142
|
this.MutationEvent,
|
|
141
|
-
ModelPredicateCreator.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
)
|
|
143
|
+
ModelPredicateCreator.createFromAST(mutationEventModelDefinition, {
|
|
144
|
+
and: { modelId: { eq: modelId } },
|
|
145
|
+
})
|
|
145
146
|
);
|
|
146
147
|
|
|
147
148
|
return mutationEvents;
|
|
@@ -201,9 +202,14 @@ class MutationEventOutbox {
|
|
|
201
202
|
|
|
202
203
|
const recordId = getIdentifierValue(userModelDefinition, record);
|
|
203
204
|
|
|
204
|
-
const predicate = ModelPredicateCreator.
|
|
205
|
+
const predicate = ModelPredicateCreator.createFromAST<MutationEvent>(
|
|
205
206
|
mutationEventModelDefinition,
|
|
206
|
-
|
|
207
|
+
{
|
|
208
|
+
and: [
|
|
209
|
+
{ modelId: { eq: recordId } },
|
|
210
|
+
{ id: { ne: this.inProgressMutationEventId } },
|
|
211
|
+
],
|
|
212
|
+
}
|
|
207
213
|
);
|
|
208
214
|
|
|
209
215
|
const outdatedMutations = await storage.query(
|
|
@@ -56,7 +56,15 @@ type MutationProcessorEvent = {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
class MutationProcessor {
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The observer that receives messages when mutations are successfully completed
|
|
61
|
+
* against cloud storage.
|
|
62
|
+
*
|
|
63
|
+
* A value of `undefined` signals that the sync has either been stopped or has not
|
|
64
|
+
* yet started. In this case, `isReady()` will be `false` and `resume()` will exit
|
|
65
|
+
* early.
|
|
66
|
+
*/
|
|
67
|
+
private observer?: ZenObservable.Observer<MutationProcessorEvent>;
|
|
60
68
|
private readonly typeQuery = new WeakMap<
|
|
61
69
|
SchemaModel,
|
|
62
70
|
[TransformerMutationType, string, string][]
|
|
@@ -130,6 +138,8 @@ class MutationProcessor {
|
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
return this.runningProcesses.addCleaner(async () => {
|
|
141
|
+
// The observer has unsubscribed and/or `stop()` has been called.
|
|
142
|
+
this.removeObserver();
|
|
133
143
|
this.pause();
|
|
134
144
|
});
|
|
135
145
|
});
|
|
@@ -138,10 +148,16 @@ class MutationProcessor {
|
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
public async stop() {
|
|
151
|
+
this.removeObserver();
|
|
141
152
|
await this.runningProcesses.close();
|
|
142
153
|
await this.runningProcesses.open();
|
|
143
154
|
}
|
|
144
155
|
|
|
156
|
+
public removeObserver() {
|
|
157
|
+
this.observer?.complete?.();
|
|
158
|
+
this.observer = undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
145
161
|
public async resume(): Promise<void> {
|
|
146
162
|
await (this.runningProcesses.isOpen &&
|
|
147
163
|
this.runningProcesses.add(async onTerminate => {
|
|
@@ -195,7 +211,7 @@ class MutationProcessor {
|
|
|
195
211
|
operation,
|
|
196
212
|
data,
|
|
197
213
|
condition,
|
|
198
|
-
modelConstructor
|
|
214
|
+
modelConstructor,
|
|
199
215
|
this.MutationEvent,
|
|
200
216
|
head,
|
|
201
217
|
operationAuthModes[authModeAttempts],
|
|
@@ -256,7 +272,7 @@ class MutationProcessor {
|
|
|
256
272
|
hasMore = (await this.outbox.peek(storage)) !== undefined;
|
|
257
273
|
});
|
|
258
274
|
|
|
259
|
-
this.observer
|
|
275
|
+
this.observer?.next?.({
|
|
260
276
|
operation,
|
|
261
277
|
modelDefinition,
|
|
262
278
|
model: record,
|
|
@@ -484,6 +500,11 @@ class MutationProcessor {
|
|
|
484
500
|
const modelDefinition = this.schema.namespaces[namespaceName].models[model];
|
|
485
501
|
const { primaryKey } = this.schema.namespaces[namespaceName].keys![model];
|
|
486
502
|
|
|
503
|
+
const auth = modelDefinition.attributes?.find(a => a.type === 'auth');
|
|
504
|
+
const ownerFields: string[] = auth?.properties?.rules
|
|
505
|
+
.map(rule => rule.ownerField)
|
|
506
|
+
.filter(f => f) || ['owner'];
|
|
507
|
+
|
|
487
508
|
const queriesTuples = this.typeQuery.get(modelDefinition);
|
|
488
509
|
|
|
489
510
|
const [, opName, query] = queriesTuples!.find(
|
|
@@ -512,7 +533,17 @@ class MutationProcessor {
|
|
|
512
533
|
mutationInput = {};
|
|
513
534
|
const modelFields = Object.values(modelDefinition.fields);
|
|
514
535
|
|
|
515
|
-
for (const { name, type, association } of modelFields) {
|
|
536
|
+
for (const { name, type, association, isReadOnly } of modelFields) {
|
|
537
|
+
// omit readonly fields. cloud storage doesn't need them and won't take them!
|
|
538
|
+
if (isReadOnly) {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// omit owner fields if it's `null`. cloud storage doesn't allow it.
|
|
543
|
+
if (ownerFields.includes(name) && parsedData[name] === null) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
516
547
|
// model fields should be stripped out from the input
|
|
517
548
|
if (isModelFieldType(type)) {
|
|
518
549
|
// except for belongs to relations - we need to replace them with the correct foreign key(s)
|
|
@@ -28,6 +28,13 @@ import {
|
|
|
28
28
|
getUserGroupsFromToken,
|
|
29
29
|
TransformerMutationType,
|
|
30
30
|
getTokenForCustomAuth,
|
|
31
|
+
predicateToGraphQLFilter,
|
|
32
|
+
dynamicAuthFields,
|
|
33
|
+
filterFields,
|
|
34
|
+
repeatedFieldInGroup,
|
|
35
|
+
countFilterCombinations,
|
|
36
|
+
RTFError,
|
|
37
|
+
generateRTFRemediation,
|
|
31
38
|
} from '../utils';
|
|
32
39
|
import { ModelPredicateCreator } from '../../predicates';
|
|
33
40
|
import { validatePredicate, USER_AGENT_SUFFIX_DATASTORE } from '../../util';
|
|
@@ -65,7 +72,10 @@ class SubscriptionProcessor {
|
|
|
65
72
|
|
|
66
73
|
constructor(
|
|
67
74
|
private readonly schema: InternalSchema,
|
|
68
|
-
private readonly syncPredicates: WeakMap<
|
|
75
|
+
private readonly syncPredicates: WeakMap<
|
|
76
|
+
SchemaModel,
|
|
77
|
+
ModelPredicate<any> | null
|
|
78
|
+
>,
|
|
69
79
|
private readonly amplifyConfig: Record<string, any> = {},
|
|
70
80
|
private readonly authModeStrategy: AuthModeStrategy,
|
|
71
81
|
private readonly errorHandler: ErrorHandler,
|
|
@@ -79,7 +89,8 @@ class SubscriptionProcessor {
|
|
|
79
89
|
userCredentials: USER_CREDENTIALS,
|
|
80
90
|
cognitoTokenPayload: { [field: string]: any } | undefined,
|
|
81
91
|
oidcTokenPayload: { [field: string]: any } | undefined,
|
|
82
|
-
authMode: GRAPHQL_AUTH_MODE
|
|
92
|
+
authMode: GRAPHQL_AUTH_MODE,
|
|
93
|
+
filterArg: boolean = false
|
|
83
94
|
): {
|
|
84
95
|
opType: TransformerMutationType;
|
|
85
96
|
opName: string;
|
|
@@ -105,7 +116,8 @@ class SubscriptionProcessor {
|
|
|
105
116
|
model,
|
|
106
117
|
transformerMutationType,
|
|
107
118
|
isOwner,
|
|
108
|
-
ownerField
|
|
119
|
+
ownerField!,
|
|
120
|
+
filterArg
|
|
109
121
|
);
|
|
110
122
|
return { authMode, opType, opName, query, isOwner, ownerField, ownerValue };
|
|
111
123
|
}
|
|
@@ -182,10 +194,18 @@ class SubscriptionProcessor {
|
|
|
182
194
|
cognitoOwnerAuthRules.forEach(ownerAuthRule => {
|
|
183
195
|
const ownerValue = cognitoTokenPayload[ownerAuthRule.identityClaim];
|
|
184
196
|
|
|
197
|
+
// AuthZ for "list of owners" is handled dynamically in the subscription auth request
|
|
198
|
+
// resolver. It doesn't rely on a subscription arg.
|
|
199
|
+
// Only pass a subscription arg for single owner auth
|
|
200
|
+
const singleOwner =
|
|
201
|
+
model.fields[ownerAuthRule.ownerField]?.isArray !== true;
|
|
202
|
+
const isOwnerArgRequired =
|
|
203
|
+
singleOwner && !ownerAuthRule.areSubscriptionsPublic;
|
|
204
|
+
|
|
185
205
|
if (ownerValue) {
|
|
186
206
|
ownerAuthInfo = {
|
|
187
207
|
authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS,
|
|
188
|
-
isOwner:
|
|
208
|
+
isOwner: isOwnerArgRequired,
|
|
189
209
|
ownerField: ownerAuthRule.ownerField,
|
|
190
210
|
ownerValue,
|
|
191
211
|
};
|
|
@@ -209,10 +229,15 @@ class SubscriptionProcessor {
|
|
|
209
229
|
oidcOwnerAuthRules.forEach(ownerAuthRule => {
|
|
210
230
|
const ownerValue = oidcTokenPayload[ownerAuthRule.identityClaim];
|
|
211
231
|
|
|
232
|
+
const singleOwner =
|
|
233
|
+
model.fields[ownerAuthRule.ownerField]?.isArray !== true;
|
|
234
|
+
const isOwnerArgRequired =
|
|
235
|
+
singleOwner && !ownerAuthRule.areSubscriptionsPublic;
|
|
236
|
+
|
|
212
237
|
if (ownerValue) {
|
|
213
238
|
ownerAuthInfo = {
|
|
214
239
|
authMode: GRAPHQL_AUTH_MODE.OPENID_CONNECT,
|
|
215
|
-
isOwner:
|
|
240
|
+
isOwner: isOwnerArgRequired,
|
|
216
241
|
ownerField: ownerAuthRule.ownerField,
|
|
217
242
|
ownerValue,
|
|
218
243
|
};
|
|
@@ -356,8 +381,20 @@ class SubscriptionProcessor {
|
|
|
356
381
|
[TransformerMutationType.DELETE]: 0,
|
|
357
382
|
};
|
|
358
383
|
|
|
359
|
-
|
|
360
|
-
|
|
384
|
+
const predicatesGroup = ModelPredicateCreator.getPredicates(
|
|
385
|
+
this.syncPredicates.get(modelDefinition)!,
|
|
386
|
+
false
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const addFilterArg = predicatesGroup !== undefined;
|
|
390
|
+
|
|
391
|
+
// Retry subscriptions that failed for one of the following reasons:
|
|
392
|
+
// 1. unauthorized - retry with next auth mode (if available)
|
|
393
|
+
// 2. RTF error - retry without sending filter arg. (filtering will fall back to clientside)
|
|
394
|
+
const subscriptionRetry = async (
|
|
395
|
+
operation,
|
|
396
|
+
addFilter = addFilterArg
|
|
397
|
+
) => {
|
|
361
398
|
const {
|
|
362
399
|
opType: transformerMutationType,
|
|
363
400
|
opName,
|
|
@@ -373,7 +410,8 @@ class SubscriptionProcessor {
|
|
|
373
410
|
userCredentials,
|
|
374
411
|
cognitoTokenPayload,
|
|
375
412
|
oidcTokenPayload,
|
|
376
|
-
readAuthModes[operationAuthModeAttempts[operation]]
|
|
413
|
+
readAuthModes[operationAuthModeAttempts[operation]],
|
|
414
|
+
addFilter
|
|
377
415
|
);
|
|
378
416
|
|
|
379
417
|
const authToken = await getTokenForCustomAuth(
|
|
@@ -383,6 +421,11 @@ class SubscriptionProcessor {
|
|
|
383
421
|
|
|
384
422
|
const variables = {};
|
|
385
423
|
|
|
424
|
+
if (addFilter && predicatesGroup) {
|
|
425
|
+
variables['filter'] =
|
|
426
|
+
predicateToGraphQLFilter(predicatesGroup);
|
|
427
|
+
}
|
|
428
|
+
|
|
386
429
|
if (isOwner) {
|
|
387
430
|
if (!ownerValue) {
|
|
388
431
|
observer.error(
|
|
@@ -478,6 +521,33 @@ class SubscriptionProcessor {
|
|
|
478
521
|
},
|
|
479
522
|
} = subscriptionError;
|
|
480
523
|
|
|
524
|
+
const isRTFError =
|
|
525
|
+
// only attempt catch if a filter variable was added to the subscription query
|
|
526
|
+
addFilter &&
|
|
527
|
+
this.catchRTFError(
|
|
528
|
+
message,
|
|
529
|
+
modelDefinition,
|
|
530
|
+
predicatesGroup
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
// Catch RTF errors
|
|
534
|
+
if (isRTFError) {
|
|
535
|
+
// Unsubscribe and clear subscription array for model/operation
|
|
536
|
+
subscriptions[modelDefinition.name][
|
|
537
|
+
transformerMutationType
|
|
538
|
+
].forEach(subscription =>
|
|
539
|
+
subscription.unsubscribe()
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
subscriptions[modelDefinition.name][
|
|
543
|
+
transformerMutationType
|
|
544
|
+
] = [];
|
|
545
|
+
|
|
546
|
+
// retry subscription connection without filter
|
|
547
|
+
subscriptionRetry(operation, false);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
481
551
|
if (
|
|
482
552
|
message.includes(
|
|
483
553
|
PUBSUB_CONTROL_MSG.REALTIME_SUBSCRIPTION_INIT_ERROR
|
|
@@ -523,10 +593,11 @@ class SubscriptionProcessor {
|
|
|
523
593
|
]
|
|
524
594
|
}`
|
|
525
595
|
);
|
|
526
|
-
|
|
596
|
+
subscriptionRetry(operation);
|
|
527
597
|
return;
|
|
528
598
|
}
|
|
529
599
|
}
|
|
600
|
+
|
|
530
601
|
logger.warn('subscriptionError', message);
|
|
531
602
|
|
|
532
603
|
try {
|
|
@@ -586,7 +657,7 @@ class SubscriptionProcessor {
|
|
|
586
657
|
);
|
|
587
658
|
};
|
|
588
659
|
|
|
589
|
-
operations.forEach(op =>
|
|
660
|
+
operations.forEach(op => subscriptionRetry(op));
|
|
590
661
|
})
|
|
591
662
|
);
|
|
592
663
|
});
|
|
@@ -660,6 +731,49 @@ class SubscriptionProcessor {
|
|
|
660
731
|
this.buffer = [];
|
|
661
732
|
}
|
|
662
733
|
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* @returns true if the service returned an RTF subscription error
|
|
737
|
+
* @remarks logs a warning with remediation instructions
|
|
738
|
+
*
|
|
739
|
+
*/
|
|
740
|
+
private catchRTFError(
|
|
741
|
+
message: string,
|
|
742
|
+
modelDefinition: SchemaModel,
|
|
743
|
+
predicatesGroup: PredicatesGroup<any> | undefined
|
|
744
|
+
): boolean {
|
|
745
|
+
const header =
|
|
746
|
+
'Backend subscriptions filtering error.\n' +
|
|
747
|
+
'Subscriptions filtering will be applied clientside.\n';
|
|
748
|
+
|
|
749
|
+
const messageErrorTypeMap = {
|
|
750
|
+
'UnknownArgument: Unknown field argument filter': RTFError.UnknownField,
|
|
751
|
+
'Filters exceed maximum attributes limit': RTFError.MaxAttributes,
|
|
752
|
+
'Filters combination exceed maximum limit': RTFError.MaxCombinations,
|
|
753
|
+
'filter uses same fieldName multiple time': RTFError.RepeatedFieldname,
|
|
754
|
+
"The variables input contains a field name 'not'": RTFError.NotGroup,
|
|
755
|
+
'The variables input contains a field that is not defined for input object type':
|
|
756
|
+
RTFError.FieldNotInType,
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const [_errorMsg, errorType] =
|
|
760
|
+
Object.entries(messageErrorTypeMap).find(([errorMsg]) =>
|
|
761
|
+
message.includes(errorMsg)
|
|
762
|
+
) || [];
|
|
763
|
+
|
|
764
|
+
if (errorType !== undefined) {
|
|
765
|
+
const remediationMessage = generateRTFRemediation(
|
|
766
|
+
errorType,
|
|
767
|
+
modelDefinition,
|
|
768
|
+
predicatesGroup
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
logger.warn(`${header}\n${message}\n${remediationMessage}`);
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
663
777
|
}
|
|
664
778
|
|
|
665
779
|
export { SubscriptionProcessor };
|
|
@@ -45,7 +45,10 @@ class SyncProcessor {
|
|
|
45
45
|
|
|
46
46
|
constructor(
|
|
47
47
|
private readonly schema: InternalSchema,
|
|
48
|
-
private readonly syncPredicates: WeakMap<
|
|
48
|
+
private readonly syncPredicates: WeakMap<
|
|
49
|
+
SchemaModel,
|
|
50
|
+
ModelPredicate<any> | null
|
|
51
|
+
>,
|
|
49
52
|
private readonly amplifyConfig: Record<string, any> = {},
|
|
50
53
|
private readonly authModeStrategy: AuthModeStrategy,
|
|
51
54
|
private readonly errorHandler: ErrorHandler,
|