@aws-amplify/datastore 3.7.3 → 3.7.4-custom-pk.86
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 +144 -17
- package/README.md +154 -0
- package/dist/aws-amplify-datastore.js +3740 -1599
- package/dist/aws-amplify-datastore.js.map +1 -1
- package/dist/aws-amplify-datastore.min.js +25 -95
- package/dist/aws-amplify-datastore.min.js.map +1 -1
- package/lib/authModeStrategies/multiAuthStrategy.d.ts +2 -2
- package/lib/authModeStrategies/multiAuthStrategy.js +32 -26
- package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib/datastore/datastore.d.ts +17 -16
- package/lib/datastore/datastore.js +291 -83
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/index.d.ts +3 -19
- package/lib/predicates/index.d.ts +3 -2
- package/lib/predicates/index.js +12 -2
- package/lib/predicates/index.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
- package/lib/storage/adapter/AsyncStorageAdapter.js +361 -193
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
- package/lib/storage/adapter/AsyncStorageDatabase.js +59 -28
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/InMemoryStore.js +2 -2
- package/lib/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +7 -2
- package/lib/storage/adapter/IndexedDBAdapter.js +454 -252
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/index.d.ts +1 -1
- package/lib/storage/storage.d.ts +3 -3
- package/lib/storage/storage.js +106 -28
- package/lib/storage/storage.js.map +1 -1
- package/lib/sync/index.d.ts +23 -7
- package/lib/sync/index.js +18 -17
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/merger.d.ts +3 -3
- package/lib/sync/merger.js +8 -6
- package/lib/sync/merger.js.map +1 -1
- package/lib/sync/outbox.d.ts +2 -2
- package/lib/sync/outbox.js +12 -9
- package/lib/sync/outbox.js.map +1 -1
- package/lib/sync/processors/errorMaps.d.ts +17 -0
- package/lib/sync/processors/errorMaps.js +125 -0
- package/lib/sync/processors/errorMaps.js.map +1 -0
- package/lib/sync/processors/mutation.d.ts +22 -4
- package/lib/sync/processors/mutation.js +119 -51
- package/lib/sync/processors/mutation.js.map +1 -1
- package/lib/sync/processors/subscription.d.ts +4 -2
- package/lib/sync/processors/subscription.js +76 -41
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.d.ts +4 -2
- package/lib/sync/processors/sync.js +62 -27
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/sync/utils.d.ts +3 -2
- package/lib/sync/utils.js +58 -7
- package/lib/sync/utils.js.map +1 -1
- package/lib/types.d.ts +90 -34
- package/lib/types.js +31 -2
- package/lib/types.js.map +1 -1
- package/lib/util.d.ts +48 -33
- package/lib/util.js +219 -57
- package/lib/util.js.map +1 -1
- package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +2 -2
- package/lib-esm/authModeStrategies/multiAuthStrategy.js +32 -26
- package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib-esm/datastore/datastore.d.ts +17 -16
- package/lib-esm/datastore/datastore.js +293 -85
- 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 +6 -2
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +361 -193
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js +60 -29
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/InMemoryStore.js +2 -2
- package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +7 -2
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +454 -252
- 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 +3 -3
- package/lib-esm/storage/storage.js +106 -28
- package/lib-esm/storage/storage.js.map +1 -1
- package/lib-esm/sync/index.d.ts +23 -7
- package/lib-esm/sync/index.js +20 -19
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/merger.d.ts +3 -3
- package/lib-esm/sync/merger.js +8 -6
- package/lib-esm/sync/merger.js.map +1 -1
- package/lib-esm/sync/outbox.d.ts +2 -2
- package/lib-esm/sync/outbox.js +13 -10
- package/lib-esm/sync/outbox.js.map +1 -1
- package/lib-esm/sync/processors/errorMaps.d.ts +17 -0
- package/lib-esm/sync/processors/errorMaps.js +119 -0
- package/lib-esm/sync/processors/errorMaps.js.map +1 -0
- package/lib-esm/sync/processors/mutation.d.ts +22 -4
- package/lib-esm/sync/processors/mutation.js +122 -54
- package/lib-esm/sync/processors/mutation.js.map +1 -1
- package/lib-esm/sync/processors/subscription.d.ts +4 -2
- package/lib-esm/sync/processors/subscription.js +77 -42
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.d.ts +4 -2
- package/lib-esm/sync/processors/sync.js +62 -27
- 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 +58 -8
- package/lib-esm/sync/utils.js.map +1 -1
- package/lib-esm/types.d.ts +90 -34
- package/lib-esm/types.js +29 -3
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +48 -33
- package/lib-esm/util.js +207 -54
- package/lib-esm/util.js.map +1 -1
- package/package.json +10 -9
- package/src/authModeStrategies/multiAuthStrategy.ts +24 -21
- package/src/datastore/datastore.ts +448 -148
- package/src/predicates/index.ts +32 -10
- package/src/storage/adapter/AsyncStorageAdapter.ts +305 -78
- package/src/storage/adapter/AsyncStorageDatabase.ts +65 -26
- package/src/storage/adapter/InMemoryStore.ts +2 -2
- package/src/storage/adapter/IndexedDBAdapter.ts +344 -101
- package/src/storage/adapter/index.ts +1 -1
- package/src/storage/storage.ts +85 -24
- package/src/sync/index.ts +74 -58
- package/src/sync/merger.ts +16 -4
- package/src/sync/outbox.ts +22 -8
- package/src/sync/processors/errorMaps.ts +93 -0
- package/src/sync/processors/mutation.ts +143 -84
- package/src/sync/processors/subscription.ts +67 -36
- package/src/sync/processors/sync.ts +37 -12
- package/src/sync/utils.ts +67 -16
- package/src/types.ts +252 -50
- package/src/util.ts +256 -42
- package/ssr/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ErrorType } from '../../types';
|
|
2
|
+
|
|
3
|
+
export type ErrorMap = Partial<{
|
|
4
|
+
[key in ErrorType]: (error: Error) => boolean;
|
|
5
|
+
}>;
|
|
6
|
+
|
|
7
|
+
const connectionTimeout = error =>
|
|
8
|
+
/^Connection failed: Connection Timeout/.test(error.message);
|
|
9
|
+
|
|
10
|
+
const serverError = error =>
|
|
11
|
+
/^Error: Request failed with status code 5\d\d/.test(error.message);
|
|
12
|
+
|
|
13
|
+
export const mutationErrorMap: ErrorMap = {
|
|
14
|
+
BadModel: () => false,
|
|
15
|
+
BadRecord: error => {
|
|
16
|
+
const { message } = error;
|
|
17
|
+
return (
|
|
18
|
+
/^Cannot return \w+ for [\w-_]+ type/.test(message) ||
|
|
19
|
+
/^Variable '.+' has coerced Null value for NonNull type/.test(message)
|
|
20
|
+
); // newly required field, out of date client
|
|
21
|
+
},
|
|
22
|
+
ConfigError: () => false,
|
|
23
|
+
Transient: error => connectionTimeout(error) || serverError(error),
|
|
24
|
+
Unauthorized: error =>
|
|
25
|
+
/^Request failed with status code 401/.test(error.message),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const subscriptionErrorMap: ErrorMap = {
|
|
29
|
+
BadModel: () => false,
|
|
30
|
+
BadRecord: () => false,
|
|
31
|
+
ConfigError: () => false,
|
|
32
|
+
Transient: observableError => {
|
|
33
|
+
const error = unwrapObservableError(observableError);
|
|
34
|
+
return connectionTimeout(error) || serverError(error);
|
|
35
|
+
},
|
|
36
|
+
Unauthorized: observableError => {
|
|
37
|
+
const error = unwrapObservableError(observableError);
|
|
38
|
+
return /Connection failed.+Unauthorized/.test(error.message);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const syncErrorMap: ErrorMap = {
|
|
43
|
+
BadModel: () => false,
|
|
44
|
+
BadRecord: error => /^Cannot return \w+ for [\w-_]+ type/.test(error.message),
|
|
45
|
+
ConfigError: () => false,
|
|
46
|
+
Transient: error => connectionTimeout(error) || serverError(error),
|
|
47
|
+
Unauthorized: () => false,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the first error reason of an observable.
|
|
52
|
+
* Allows for error maps to be easily applied to observable errors
|
|
53
|
+
*
|
|
54
|
+
* @param observableError an error from ZenObservable subscribe error callback
|
|
55
|
+
*/
|
|
56
|
+
function unwrapObservableError(observableError: any) {
|
|
57
|
+
const {
|
|
58
|
+
error: { errors: [error] } = {
|
|
59
|
+
errors: [],
|
|
60
|
+
},
|
|
61
|
+
} = observableError;
|
|
62
|
+
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getMutationErrorType(error: Error): ErrorType {
|
|
67
|
+
return mapErrorToType(mutationErrorMap, error);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getSubscriptionErrorType(error: Error): ErrorType {
|
|
71
|
+
return mapErrorToType(subscriptionErrorMap, error);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getSyncErrorType(error: Error): ErrorType {
|
|
75
|
+
return mapErrorToType(syncErrorMap, error);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Categorizes an error with a broad error type, intended to make
|
|
80
|
+
* customer error handling code simpler.
|
|
81
|
+
* @param errorMap Error names and a list of patterns that indicate them (each pattern as a regex or function)
|
|
82
|
+
* @param error The underying error to categorize.
|
|
83
|
+
*/
|
|
84
|
+
export function mapErrorToType(errorMap: ErrorMap, error: Error): ErrorType {
|
|
85
|
+
const errorTypes = [...Object.keys(errorMap)] as ErrorType[];
|
|
86
|
+
for (const errorType of errorTypes) {
|
|
87
|
+
const matcher = errorMap[errorType];
|
|
88
|
+
if (matcher(error)) {
|
|
89
|
+
return errorType;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return 'Unknown';
|
|
93
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import API, { GraphQLResult, GRAPHQL_AUTH_MODE } from '@aws-amplify/api';
|
|
2
2
|
import {
|
|
3
3
|
ConsoleLogger as Logger,
|
|
4
|
-
|
|
4
|
+
jitteredBackoff,
|
|
5
5
|
NonRetryableError,
|
|
6
|
+
retry,
|
|
6
7
|
} from '@aws-amplify/core';
|
|
7
8
|
import Observable, { ZenObservable } from 'zen-observable-ts';
|
|
8
9
|
import { MutationEvent } from '../';
|
|
@@ -23,8 +24,15 @@ import {
|
|
|
23
24
|
PersistentModelConstructor,
|
|
24
25
|
SchemaModel,
|
|
25
26
|
TypeConstructorMap,
|
|
27
|
+
ProcessName,
|
|
28
|
+
AmplifyContext,
|
|
26
29
|
} from '../../types';
|
|
27
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
exhaustiveCheck,
|
|
32
|
+
extractTargetNamesFromSrc,
|
|
33
|
+
USER,
|
|
34
|
+
USER_AGENT_SUFFIX_DATASTORE,
|
|
35
|
+
} from '../../util';
|
|
28
36
|
import { MutationEventOutbox } from '../outbox';
|
|
29
37
|
import {
|
|
30
38
|
buildGraphQLOperation,
|
|
@@ -33,6 +41,7 @@ import {
|
|
|
33
41
|
TransformerMutationType,
|
|
34
42
|
getTokenForCustomAuth,
|
|
35
43
|
} from '../utils';
|
|
44
|
+
import { getMutationErrorType } from './errorMaps';
|
|
36
45
|
|
|
37
46
|
const MAX_ATTEMPTS = 10;
|
|
38
47
|
|
|
@@ -62,9 +71,11 @@ class MutationProcessor {
|
|
|
62
71
|
private readonly MutationEvent: PersistentModelConstructor<MutationEvent>,
|
|
63
72
|
private readonly amplifyConfig: Record<string, any> = {},
|
|
64
73
|
private readonly authModeStrategy: AuthModeStrategy,
|
|
65
|
-
private readonly
|
|
66
|
-
private readonly
|
|
74
|
+
private readonly errorHandler: ErrorHandler,
|
|
75
|
+
private readonly conflictHandler: ConflictHandler,
|
|
76
|
+
private readonly amplifyContext: AmplifyContext
|
|
67
77
|
) {
|
|
78
|
+
this.amplifyContext.API = this.amplifyContext.API || API;
|
|
68
79
|
this.generateQueries();
|
|
69
80
|
}
|
|
70
81
|
|
|
@@ -234,14 +245,14 @@ class MutationProcessor {
|
|
|
234
245
|
operation: TransformerMutationType,
|
|
235
246
|
data: string,
|
|
236
247
|
condition: string,
|
|
237
|
-
modelConstructor: PersistentModelConstructor<
|
|
248
|
+
modelConstructor: PersistentModelConstructor<unknown>,
|
|
238
249
|
MutationEvent: PersistentModelConstructor<MutationEvent>,
|
|
239
250
|
mutationEvent: MutationEvent,
|
|
240
251
|
authMode: GRAPHQL_AUTH_MODE
|
|
241
252
|
): Promise<
|
|
242
253
|
[GraphQLResult<Record<string, PersistentModel>>, string, SchemaModel]
|
|
243
254
|
> {
|
|
244
|
-
return await
|
|
255
|
+
return await retry(
|
|
245
256
|
async (
|
|
246
257
|
model: string,
|
|
247
258
|
operation: TransformerMutationType,
|
|
@@ -251,26 +262,27 @@ class MutationProcessor {
|
|
|
251
262
|
MutationEvent: PersistentModelConstructor<MutationEvent>,
|
|
252
263
|
mutationEvent: MutationEvent
|
|
253
264
|
) => {
|
|
254
|
-
const [
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
model,
|
|
263
|
-
operation,
|
|
264
|
-
data,
|
|
265
|
-
condition
|
|
266
|
-
);
|
|
265
|
+
const [query, variables, graphQLCondition, opName, modelDefinition] =
|
|
266
|
+
this.createQueryVariables(
|
|
267
|
+
namespaceName,
|
|
268
|
+
model,
|
|
269
|
+
operation,
|
|
270
|
+
data,
|
|
271
|
+
condition
|
|
272
|
+
);
|
|
267
273
|
|
|
268
274
|
const authToken = await getTokenForCustomAuth(
|
|
269
275
|
authMode,
|
|
270
276
|
this.amplifyConfig
|
|
271
277
|
);
|
|
272
278
|
|
|
273
|
-
const tryWith = {
|
|
279
|
+
const tryWith = {
|
|
280
|
+
query,
|
|
281
|
+
variables,
|
|
282
|
+
authMode,
|
|
283
|
+
authToken,
|
|
284
|
+
userAgentSuffix: USER_AGENT_SUFFIX_DATASTORE,
|
|
285
|
+
};
|
|
274
286
|
let attempt = 0;
|
|
275
287
|
|
|
276
288
|
const opType = this.opTypeFromTransformerOperation(operation);
|
|
@@ -278,7 +290,7 @@ class MutationProcessor {
|
|
|
278
290
|
do {
|
|
279
291
|
try {
|
|
280
292
|
const result = <GraphQLResult<Record<string, PersistentModel>>>(
|
|
281
|
-
await API.graphql(tryWith)
|
|
293
|
+
await this.amplifyContext.API.graphql(tryWith)
|
|
282
294
|
);
|
|
283
295
|
return [result, opName, modelDefinition];
|
|
284
296
|
} catch (err) {
|
|
@@ -345,11 +357,12 @@ class MutationProcessor {
|
|
|
345
357
|
|
|
346
358
|
const serverData = <
|
|
347
359
|
GraphQLResult<Record<string, PersistentModel>>
|
|
348
|
-
>await API.graphql({
|
|
360
|
+
>await this.amplifyContext.API.graphql({
|
|
349
361
|
query,
|
|
350
362
|
variables: { id: variables.input.id },
|
|
351
363
|
authMode,
|
|
352
364
|
authToken,
|
|
365
|
+
userAgentSuffix: USER_AGENT_SUFFIX_DATASTORE,
|
|
353
366
|
});
|
|
354
367
|
|
|
355
368
|
return [serverData, opName, modelDefinition];
|
|
@@ -358,17 +371,18 @@ class MutationProcessor {
|
|
|
358
371
|
const namespace = this.schema.namespaces[namespaceName];
|
|
359
372
|
|
|
360
373
|
// convert retry with to tryWith
|
|
361
|
-
const updatedMutation =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
374
|
+
const updatedMutation =
|
|
375
|
+
createMutationInstanceFromModelOperation(
|
|
376
|
+
namespace.relationships,
|
|
377
|
+
modelDefinition,
|
|
378
|
+
opType,
|
|
379
|
+
modelConstructor,
|
|
380
|
+
retryWith,
|
|
381
|
+
graphQLCondition,
|
|
382
|
+
MutationEvent,
|
|
383
|
+
this.modelInstanceCreator,
|
|
384
|
+
mutationEvent.id
|
|
385
|
+
);
|
|
372
386
|
|
|
373
387
|
await this.storage.save(updatedMutation);
|
|
374
388
|
|
|
@@ -376,20 +390,21 @@ class MutationProcessor {
|
|
|
376
390
|
} else {
|
|
377
391
|
try {
|
|
378
392
|
await this.errorHandler({
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
),
|
|
393
|
+
recoverySuggestion:
|
|
394
|
+
'Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues',
|
|
395
|
+
localModel: variables.input,
|
|
383
396
|
message: error.message,
|
|
384
397
|
operation,
|
|
385
|
-
errorType: error
|
|
398
|
+
errorType: getMutationErrorType(error),
|
|
386
399
|
errorInfo: error.errorInfo,
|
|
400
|
+
process: ProcessName.mutate,
|
|
401
|
+
cause: error,
|
|
387
402
|
remoteModel: error.data
|
|
388
403
|
? this.modelInstanceCreator(modelConstructor, error.data)
|
|
389
404
|
: null,
|
|
390
405
|
});
|
|
391
406
|
} catch (err) {
|
|
392
|
-
logger.warn('
|
|
407
|
+
logger.warn('Mutation error handler failed with:', err);
|
|
393
408
|
} finally {
|
|
394
409
|
// Return empty tuple, dequeues the mutation
|
|
395
410
|
return error.data
|
|
@@ -417,7 +432,8 @@ class MutationProcessor {
|
|
|
417
432
|
modelConstructor,
|
|
418
433
|
MutationEvent,
|
|
419
434
|
mutationEvent,
|
|
420
|
-
]
|
|
435
|
+
],
|
|
436
|
+
safeJitteredBackoff
|
|
421
437
|
);
|
|
422
438
|
}
|
|
423
439
|
|
|
@@ -441,63 +457,73 @@ class MutationProcessor {
|
|
|
441
457
|
|
|
442
458
|
// include all the fields that comprise a custom PK if one is specified
|
|
443
459
|
const deleteInput = {};
|
|
444
|
-
if (primaryKey
|
|
460
|
+
if (primaryKey?.length) {
|
|
445
461
|
for (const pkField of primaryKey) {
|
|
446
462
|
deleteInput[pkField] = parsedData[pkField];
|
|
447
463
|
}
|
|
448
464
|
} else {
|
|
449
|
-
|
|
465
|
+
// CPK TODO:
|
|
466
|
+
// Previous comment from Manuel: what if it was renamed ?
|
|
467
|
+
deleteInput['id'] = (<any>parsedData).id;
|
|
450
468
|
}
|
|
451
469
|
|
|
452
|
-
|
|
453
|
-
operation === TransformerMutationType.DELETE
|
|
454
|
-
? <ModelInstanceMetadata>deleteInput // For DELETE mutations, only PK is sent
|
|
455
|
-
: Object.values(modelDefinition.fields)
|
|
456
|
-
.filter(({ name, type, association }) => {
|
|
457
|
-
// connections
|
|
458
|
-
if (isModelFieldType(type)) {
|
|
459
|
-
// BELONGS_TO
|
|
460
|
-
if (
|
|
461
|
-
isTargetNameAssociation(association) &&
|
|
462
|
-
association.connectionType === 'BELONGS_TO'
|
|
463
|
-
) {
|
|
464
|
-
return true;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// All other connections
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
+
let mutationInput;
|
|
470
471
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
472
|
+
if (operation === TransformerMutationType.DELETE) {
|
|
473
|
+
// For DELETE mutations, only the key(s) are included in the input
|
|
474
|
+
mutationInput = <ModelInstanceMetadata>deleteInput;
|
|
475
|
+
} else {
|
|
476
|
+
// Otherwise, we construct the mutation input with the following logic
|
|
477
|
+
mutationInput = {};
|
|
478
|
+
const modelFields = Object.values(modelDefinition.fields);
|
|
479
|
+
|
|
480
|
+
for (const { name, type, association } of modelFields) {
|
|
481
|
+
// model fields should be stripped out from the input
|
|
482
|
+
if (isModelFieldType(type)) {
|
|
483
|
+
// except for belongs to relations - we need to replace them with the correct foreign key(s)
|
|
484
|
+
if (
|
|
485
|
+
isTargetNameAssociation(association) &&
|
|
486
|
+
association.connectionType === 'BELONGS_TO'
|
|
487
|
+
) {
|
|
488
|
+
const targetNames: string[] | undefined =
|
|
489
|
+
extractTargetNamesFromSrc(association);
|
|
490
|
+
|
|
491
|
+
if (targetNames) {
|
|
492
|
+
// instead of including the connected model itself, we add its key(s) to the mutation input
|
|
493
|
+
for (const targetName of targetNames) {
|
|
494
|
+
mutationInput[targetName] = parsedData[targetName];
|
|
474
495
|
}
|
|
496
|
+
// if (association?.targetNames?.length) {
|
|
497
|
+
// // instead of including the connected model itself, we add its key(s) to the mutation input
|
|
498
|
+
// for (const targetName of association.targetNames) {
|
|
499
|
+
// mutationInput[targetName] = parsedData[targetName];
|
|
500
|
+
// }
|
|
501
|
+
// } else if (association.targetName) {
|
|
502
|
+
// // backwards-compatability for schema generated prior to custom primary key support
|
|
503
|
+
// mutationInput[association.targetName] =
|
|
504
|
+
// parsedData[association.targetName];
|
|
505
|
+
// }
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
// scalar fields / non-model types
|
|
475
511
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (
|
|
484
|
-
isModelFieldType(type) &&
|
|
485
|
-
isTargetNameAssociation(association)
|
|
486
|
-
) {
|
|
487
|
-
fieldName = association.targetName;
|
|
488
|
-
val = parsedData[fieldName];
|
|
489
|
-
}
|
|
512
|
+
if (operation === TransformerMutationType.UPDATE) {
|
|
513
|
+
if (!parsedData.hasOwnProperty(name)) {
|
|
514
|
+
// for update mutations - strip out a field if it's unchanged
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
490
518
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
return acc;
|
|
496
|
-
}, <typeof parsedData>{});
|
|
519
|
+
// all other fields are added to the input object
|
|
520
|
+
mutationInput[name] = parsedData[name];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
497
523
|
|
|
498
524
|
// Build mutation variables input object
|
|
499
525
|
const input: ModelInstanceMetadata = {
|
|
500
|
-
...
|
|
526
|
+
...mutationInput,
|
|
501
527
|
_version,
|
|
502
528
|
};
|
|
503
529
|
|
|
@@ -539,4 +565,37 @@ class MutationProcessor {
|
|
|
539
565
|
}
|
|
540
566
|
}
|
|
541
567
|
|
|
568
|
+
const MAX_RETRY_DELAY_MS = 5 * 60 * 1000;
|
|
569
|
+
const originalJitteredBackoff = jitteredBackoff(MAX_RETRY_DELAY_MS);
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* @private
|
|
573
|
+
* Internal use of Amplify only.
|
|
574
|
+
*
|
|
575
|
+
* Wraps the jittered backoff calculation to retry Network Errors indefinitely.
|
|
576
|
+
* Backs off according to original jittered retry logic until the original retry
|
|
577
|
+
* logic hits its max. After this occurs, if the error is a Network Error, we
|
|
578
|
+
* ignore the attempt count and return MAX_RETRY_DELAY_MS to retry forever (until
|
|
579
|
+
* the request succeeds).
|
|
580
|
+
*
|
|
581
|
+
* @param attempt ignored
|
|
582
|
+
* @param _args ignored
|
|
583
|
+
* @param error tested to see if `.message` is 'Network Error'
|
|
584
|
+
* @returns number | false :
|
|
585
|
+
*/
|
|
586
|
+
export const safeJitteredBackoff: typeof originalJitteredBackoff = (
|
|
587
|
+
attempt,
|
|
588
|
+
_args,
|
|
589
|
+
error
|
|
590
|
+
) => {
|
|
591
|
+
const attemptResult = originalJitteredBackoff(attempt);
|
|
592
|
+
|
|
593
|
+
// If this is the last attempt and it is a network error, we retry indefinitively every 5 minutes
|
|
594
|
+
if (attemptResult === false && error?.message === 'Network Error') {
|
|
595
|
+
return MAX_RETRY_DELAY_MS;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return attemptResult;
|
|
599
|
+
};
|
|
600
|
+
|
|
542
601
|
export { MutationProcessor };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import API, { GraphQLResult, GRAPHQL_AUTH_MODE } from '@aws-amplify/api';
|
|
2
|
-
import Auth from '@aws-amplify/auth';
|
|
2
|
+
import { Auth } from '@aws-amplify/auth';
|
|
3
3
|
import Cache from '@aws-amplify/cache';
|
|
4
4
|
import { ConsoleLogger as Logger, Hub, HubCapsule } from '@aws-amplify/core';
|
|
5
5
|
import { CONTROL_MSG as PUBSUB_CONTROL_MSG } from '@aws-amplify/pubsub';
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
PredicatesGroup,
|
|
13
13
|
ModelPredicate,
|
|
14
14
|
AuthModeStrategy,
|
|
15
|
+
ErrorHandler,
|
|
16
|
+
ProcessName,
|
|
17
|
+
AmplifyContext,
|
|
15
18
|
} from '../../types';
|
|
16
19
|
import {
|
|
17
20
|
buildSubscriptionGraphQLOperation,
|
|
@@ -22,7 +25,8 @@ import {
|
|
|
22
25
|
getTokenForCustomAuth,
|
|
23
26
|
} from '../utils';
|
|
24
27
|
import { ModelPredicateCreator } from '../../predicates';
|
|
25
|
-
import { validatePredicate } from '../../util';
|
|
28
|
+
import { validatePredicate, USER_AGENT_SUFFIX_DATASTORE } from '../../util';
|
|
29
|
+
import { getSubscriptionErrorType } from './errorMaps';
|
|
26
30
|
|
|
27
31
|
const logger = new Logger('DataStore');
|
|
28
32
|
|
|
@@ -48,18 +52,17 @@ class SubscriptionProcessor {
|
|
|
48
52
|
SchemaModel,
|
|
49
53
|
[TransformerMutationType, string, string][]
|
|
50
54
|
>();
|
|
51
|
-
private buffer: [
|
|
52
|
-
|
|
53
|
-
SchemaModel,
|
|
54
|
-
PersistentModel
|
|
55
|
-
][] = [];
|
|
55
|
+
private buffer: [TransformerMutationType, SchemaModel, PersistentModel][] =
|
|
56
|
+
[];
|
|
56
57
|
private dataObserver: ZenObservable.Observer<any>;
|
|
57
58
|
|
|
58
59
|
constructor(
|
|
59
60
|
private readonly schema: InternalSchema,
|
|
60
61
|
private readonly syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>,
|
|
61
62
|
private readonly amplifyConfig: Record<string, any> = {},
|
|
62
|
-
private readonly authModeStrategy: AuthModeStrategy
|
|
63
|
+
private readonly authModeStrategy: AuthModeStrategy,
|
|
64
|
+
private readonly errorHandler: ErrorHandler,
|
|
65
|
+
private readonly amplifyContext: AmplifyContext = { Auth, API, Cache }
|
|
63
66
|
) {}
|
|
64
67
|
|
|
65
68
|
private buildSubscription(
|
|
@@ -252,8 +255,8 @@ class SubscriptionProcessor {
|
|
|
252
255
|
(async () => {
|
|
253
256
|
try {
|
|
254
257
|
// retrieving current AWS Credentials
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
const credentials =
|
|
259
|
+
await this.amplifyContext.Auth.currentCredentials();
|
|
257
260
|
userCredentials = credentials.authenticated
|
|
258
261
|
? USER_CREDENTIALS.auth
|
|
259
262
|
: USER_CREDENTIALS.unauth;
|
|
@@ -263,8 +266,7 @@ class SubscriptionProcessor {
|
|
|
263
266
|
|
|
264
267
|
try {
|
|
265
268
|
// retrieving current token info from Cognito UserPools
|
|
266
|
-
|
|
267
|
-
const session = await Auth.currentSession();
|
|
269
|
+
const session = await this.amplifyContext.Auth.currentSession();
|
|
268
270
|
cognitoTokenPayload = session.getIdToken().decodePayload();
|
|
269
271
|
} catch (err) {
|
|
270
272
|
// best effort to get jwt from Cognito
|
|
@@ -281,11 +283,14 @@ class SubscriptionProcessor {
|
|
|
281
283
|
|
|
282
284
|
let token;
|
|
283
285
|
// backwards compatibility
|
|
284
|
-
const federatedInfo = await Cache.getItem(
|
|
286
|
+
const federatedInfo = await this.amplifyContext.Cache.getItem(
|
|
287
|
+
'federatedInfo'
|
|
288
|
+
);
|
|
285
289
|
if (federatedInfo) {
|
|
286
290
|
token = federatedInfo.token;
|
|
287
291
|
} else {
|
|
288
|
-
const currentUser =
|
|
292
|
+
const currentUser =
|
|
293
|
+
await this.amplifyContext.Auth.currentAuthenticatedUser();
|
|
289
294
|
if (currentUser) {
|
|
290
295
|
token = currentUser.token;
|
|
291
296
|
}
|
|
@@ -308,8 +313,8 @@ class SubscriptionProcessor {
|
|
|
308
313
|
.forEach(async modelDefinition => {
|
|
309
314
|
const modelAuthModes = await getModelAuthModes({
|
|
310
315
|
authModeStrategy: this.authModeStrategy,
|
|
311
|
-
defaultAuthMode:
|
|
312
|
-
.aws_appsync_authenticationType,
|
|
316
|
+
defaultAuthMode:
|
|
317
|
+
this.amplifyConfig.aws_appsync_authenticationType,
|
|
313
318
|
modelName: modelDefinition.name,
|
|
314
319
|
schema: this.schema,
|
|
315
320
|
});
|
|
@@ -382,18 +387,23 @@ class SubscriptionProcessor {
|
|
|
382
387
|
}`
|
|
383
388
|
);
|
|
384
389
|
|
|
390
|
+
const userAgentSuffix = USER_AGENT_SUFFIX_DATASTORE;
|
|
391
|
+
|
|
385
392
|
const queryObservable = <
|
|
386
393
|
Observable<{
|
|
387
394
|
value: GraphQLResult<Record<string, PersistentModel>>;
|
|
388
395
|
}>
|
|
389
|
-
>(<unknown>API.graphql({ query, variables, ...{ authMode }, authToken }));
|
|
396
|
+
>(<unknown>this.amplifyContext.API.graphql({ query, variables, ...{ authMode }, authToken, userAgentSuffix }));
|
|
397
|
+
|
|
390
398
|
let subscriptionReadyCallback: () => void;
|
|
391
399
|
|
|
392
400
|
subscriptions[modelDefinition.name][
|
|
393
401
|
transformerMutationType
|
|
394
402
|
].push(
|
|
395
403
|
queryObservable
|
|
396
|
-
.map(({ value }) =>
|
|
404
|
+
.map(({ value }) => {
|
|
405
|
+
return value;
|
|
406
|
+
})
|
|
397
407
|
.subscribe({
|
|
398
408
|
next: ({ data, errors }) => {
|
|
399
409
|
if (Array.isArray(errors) && errors.length > 0) {
|
|
@@ -413,10 +423,11 @@ class SubscriptionProcessor {
|
|
|
413
423
|
return;
|
|
414
424
|
}
|
|
415
425
|
|
|
416
|
-
const predicatesGroup =
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
426
|
+
const predicatesGroup =
|
|
427
|
+
ModelPredicateCreator.getPredicates(
|
|
428
|
+
this.syncPredicates.get(modelDefinition),
|
|
429
|
+
false
|
|
430
|
+
);
|
|
420
431
|
|
|
421
432
|
const { [opName]: record } = data;
|
|
422
433
|
|
|
@@ -438,7 +449,7 @@ class SubscriptionProcessor {
|
|
|
438
449
|
}
|
|
439
450
|
this.drainBuffer();
|
|
440
451
|
},
|
|
441
|
-
error: subscriptionError => {
|
|
452
|
+
error: async subscriptionError => {
|
|
442
453
|
const {
|
|
443
454
|
error: { errors: [{ message = '' } = {}] } = {
|
|
444
455
|
errors: [],
|
|
@@ -464,6 +475,7 @@ class SubscriptionProcessor {
|
|
|
464
475
|
operationAuthModeAttempts[operation] >=
|
|
465
476
|
readAuthModes.length
|
|
466
477
|
) {
|
|
478
|
+
// last auth mode retry. Continue with error
|
|
467
479
|
logger.debug(
|
|
468
480
|
`${operation} subscription failed with authMode: ${
|
|
469
481
|
readAuthModes[
|
|
@@ -471,9 +483,9 @@ class SubscriptionProcessor {
|
|
|
471
483
|
]
|
|
472
484
|
}`
|
|
473
485
|
);
|
|
474
|
-
logger.warn('subscriptionError', message);
|
|
475
|
-
return;
|
|
476
486
|
} else {
|
|
487
|
+
// retry with different auth mode. Do not trigger
|
|
488
|
+
// observer error or error handler
|
|
477
489
|
logger.debug(
|
|
478
490
|
`${operation} subscription failed with authMode: ${
|
|
479
491
|
readAuthModes[
|
|
@@ -489,9 +501,29 @@ class SubscriptionProcessor {
|
|
|
489
501
|
return;
|
|
490
502
|
}
|
|
491
503
|
}
|
|
492
|
-
|
|
493
504
|
logger.warn('subscriptionError', message);
|
|
494
505
|
|
|
506
|
+
try {
|
|
507
|
+
await this.errorHandler({
|
|
508
|
+
recoverySuggestion:
|
|
509
|
+
'Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues',
|
|
510
|
+
localModel: null,
|
|
511
|
+
message,
|
|
512
|
+
model: modelDefinition.name,
|
|
513
|
+
operation,
|
|
514
|
+
errorType:
|
|
515
|
+
getSubscriptionErrorType(subscriptionError),
|
|
516
|
+
process: ProcessName.subscribe,
|
|
517
|
+
remoteModel: null,
|
|
518
|
+
cause: subscriptionError,
|
|
519
|
+
});
|
|
520
|
+
} catch (e) {
|
|
521
|
+
logger.error(
|
|
522
|
+
'Subscription error handler failed with:',
|
|
523
|
+
e
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
495
527
|
if (typeof subscriptionReadyCallback === 'function') {
|
|
496
528
|
subscriptionReadyCallback();
|
|
497
529
|
}
|
|
@@ -502,7 +534,6 @@ class SubscriptionProcessor {
|
|
|
502
534
|
) {
|
|
503
535
|
return;
|
|
504
536
|
}
|
|
505
|
-
|
|
506
537
|
observer.error(message);
|
|
507
538
|
},
|
|
508
539
|
})
|
|
@@ -534,15 +565,15 @@ class SubscriptionProcessor {
|
|
|
534
565
|
|
|
535
566
|
return () => {
|
|
536
567
|
Object.keys(subscriptions).forEach(modelName => {
|
|
537
|
-
subscriptions[modelName][
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
subscriptions[modelName][
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
subscriptions[modelName][
|
|
544
|
-
|
|
545
|
-
|
|
568
|
+
subscriptions[modelName][TransformerMutationType.CREATE].forEach(
|
|
569
|
+
subscription => subscription.unsubscribe()
|
|
570
|
+
);
|
|
571
|
+
subscriptions[modelName][TransformerMutationType.UPDATE].forEach(
|
|
572
|
+
subscription => subscription.unsubscribe()
|
|
573
|
+
);
|
|
574
|
+
subscriptions[modelName][TransformerMutationType.DELETE].forEach(
|
|
575
|
+
subscription => subscription.unsubscribe()
|
|
576
|
+
);
|
|
546
577
|
});
|
|
547
578
|
};
|
|
548
579
|
});
|