@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.
Files changed (137) hide show
  1. package/CHANGELOG.md +144 -17
  2. package/README.md +154 -0
  3. package/dist/aws-amplify-datastore.js +3740 -1599
  4. package/dist/aws-amplify-datastore.js.map +1 -1
  5. package/dist/aws-amplify-datastore.min.js +25 -95
  6. package/dist/aws-amplify-datastore.min.js.map +1 -1
  7. package/lib/authModeStrategies/multiAuthStrategy.d.ts +2 -2
  8. package/lib/authModeStrategies/multiAuthStrategy.js +32 -26
  9. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  10. package/lib/datastore/datastore.d.ts +17 -16
  11. package/lib/datastore/datastore.js +291 -83
  12. package/lib/datastore/datastore.js.map +1 -1
  13. package/lib/index.d.ts +3 -19
  14. package/lib/predicates/index.d.ts +3 -2
  15. package/lib/predicates/index.js +12 -2
  16. package/lib/predicates/index.js.map +1 -1
  17. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
  18. package/lib/storage/adapter/AsyncStorageAdapter.js +361 -193
  19. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  20. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
  21. package/lib/storage/adapter/AsyncStorageDatabase.js +59 -28
  22. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  23. package/lib/storage/adapter/InMemoryStore.js +2 -2
  24. package/lib/storage/adapter/InMemoryStore.js.map +1 -1
  25. package/lib/storage/adapter/IndexedDBAdapter.d.ts +7 -2
  26. package/lib/storage/adapter/IndexedDBAdapter.js +454 -252
  27. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  28. package/lib/storage/adapter/index.d.ts +1 -1
  29. package/lib/storage/storage.d.ts +3 -3
  30. package/lib/storage/storage.js +106 -28
  31. package/lib/storage/storage.js.map +1 -1
  32. package/lib/sync/index.d.ts +23 -7
  33. package/lib/sync/index.js +18 -17
  34. package/lib/sync/index.js.map +1 -1
  35. package/lib/sync/merger.d.ts +3 -3
  36. package/lib/sync/merger.js +8 -6
  37. package/lib/sync/merger.js.map +1 -1
  38. package/lib/sync/outbox.d.ts +2 -2
  39. package/lib/sync/outbox.js +12 -9
  40. package/lib/sync/outbox.js.map +1 -1
  41. package/lib/sync/processors/errorMaps.d.ts +17 -0
  42. package/lib/sync/processors/errorMaps.js +125 -0
  43. package/lib/sync/processors/errorMaps.js.map +1 -0
  44. package/lib/sync/processors/mutation.d.ts +22 -4
  45. package/lib/sync/processors/mutation.js +119 -51
  46. package/lib/sync/processors/mutation.js.map +1 -1
  47. package/lib/sync/processors/subscription.d.ts +4 -2
  48. package/lib/sync/processors/subscription.js +76 -41
  49. package/lib/sync/processors/subscription.js.map +1 -1
  50. package/lib/sync/processors/sync.d.ts +4 -2
  51. package/lib/sync/processors/sync.js +62 -27
  52. package/lib/sync/processors/sync.js.map +1 -1
  53. package/lib/sync/utils.d.ts +3 -2
  54. package/lib/sync/utils.js +58 -7
  55. package/lib/sync/utils.js.map +1 -1
  56. package/lib/types.d.ts +90 -34
  57. package/lib/types.js +31 -2
  58. package/lib/types.js.map +1 -1
  59. package/lib/util.d.ts +48 -33
  60. package/lib/util.js +219 -57
  61. package/lib/util.js.map +1 -1
  62. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +2 -2
  63. package/lib-esm/authModeStrategies/multiAuthStrategy.js +32 -26
  64. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  65. package/lib-esm/datastore/datastore.d.ts +17 -16
  66. package/lib-esm/datastore/datastore.js +293 -85
  67. package/lib-esm/datastore/datastore.js.map +1 -1
  68. package/lib-esm/index.d.ts +3 -19
  69. package/lib-esm/predicates/index.d.ts +3 -2
  70. package/lib-esm/predicates/index.js +13 -3
  71. package/lib-esm/predicates/index.js.map +1 -1
  72. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
  73. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +361 -193
  74. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  75. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
  76. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +60 -29
  77. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  78. package/lib-esm/storage/adapter/InMemoryStore.js +2 -2
  79. package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
  80. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +7 -2
  81. package/lib-esm/storage/adapter/IndexedDBAdapter.js +454 -252
  82. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  83. package/lib-esm/storage/adapter/index.d.ts +1 -1
  84. package/lib-esm/storage/storage.d.ts +3 -3
  85. package/lib-esm/storage/storage.js +106 -28
  86. package/lib-esm/storage/storage.js.map +1 -1
  87. package/lib-esm/sync/index.d.ts +23 -7
  88. package/lib-esm/sync/index.js +20 -19
  89. package/lib-esm/sync/index.js.map +1 -1
  90. package/lib-esm/sync/merger.d.ts +3 -3
  91. package/lib-esm/sync/merger.js +8 -6
  92. package/lib-esm/sync/merger.js.map +1 -1
  93. package/lib-esm/sync/outbox.d.ts +2 -2
  94. package/lib-esm/sync/outbox.js +13 -10
  95. package/lib-esm/sync/outbox.js.map +1 -1
  96. package/lib-esm/sync/processors/errorMaps.d.ts +17 -0
  97. package/lib-esm/sync/processors/errorMaps.js +119 -0
  98. package/lib-esm/sync/processors/errorMaps.js.map +1 -0
  99. package/lib-esm/sync/processors/mutation.d.ts +22 -4
  100. package/lib-esm/sync/processors/mutation.js +122 -54
  101. package/lib-esm/sync/processors/mutation.js.map +1 -1
  102. package/lib-esm/sync/processors/subscription.d.ts +4 -2
  103. package/lib-esm/sync/processors/subscription.js +77 -42
  104. package/lib-esm/sync/processors/subscription.js.map +1 -1
  105. package/lib-esm/sync/processors/sync.d.ts +4 -2
  106. package/lib-esm/sync/processors/sync.js +62 -27
  107. package/lib-esm/sync/processors/sync.js.map +1 -1
  108. package/lib-esm/sync/utils.d.ts +3 -2
  109. package/lib-esm/sync/utils.js +58 -8
  110. package/lib-esm/sync/utils.js.map +1 -1
  111. package/lib-esm/types.d.ts +90 -34
  112. package/lib-esm/types.js +29 -3
  113. package/lib-esm/types.js.map +1 -1
  114. package/lib-esm/util.d.ts +48 -33
  115. package/lib-esm/util.js +207 -54
  116. package/lib-esm/util.js.map +1 -1
  117. package/package.json +10 -9
  118. package/src/authModeStrategies/multiAuthStrategy.ts +24 -21
  119. package/src/datastore/datastore.ts +448 -148
  120. package/src/predicates/index.ts +32 -10
  121. package/src/storage/adapter/AsyncStorageAdapter.ts +305 -78
  122. package/src/storage/adapter/AsyncStorageDatabase.ts +65 -26
  123. package/src/storage/adapter/InMemoryStore.ts +2 -2
  124. package/src/storage/adapter/IndexedDBAdapter.ts +344 -101
  125. package/src/storage/adapter/index.ts +1 -1
  126. package/src/storage/storage.ts +85 -24
  127. package/src/sync/index.ts +74 -58
  128. package/src/sync/merger.ts +16 -4
  129. package/src/sync/outbox.ts +22 -8
  130. package/src/sync/processors/errorMaps.ts +93 -0
  131. package/src/sync/processors/mutation.ts +143 -84
  132. package/src/sync/processors/subscription.ts +67 -36
  133. package/src/sync/processors/sync.ts +37 -12
  134. package/src/sync/utils.ts +67 -16
  135. package/src/types.ts +252 -50
  136. package/src/util.ts +256 -42
  137. 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
- jitteredExponentialRetry,
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 { exhaustiveCheck, USER } from '../../util';
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 conflictHandler?: ConflictHandler,
66
- private readonly errorHandler?: ErrorHandler
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<PersistentModel>,
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 jitteredExponentialRetry(
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
- query,
256
- variables,
257
- graphQLCondition,
258
- opName,
259
- modelDefinition,
260
- ] = this.createQueryVariables(
261
- namespaceName,
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 = { query, variables, authMode, authToken };
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 = createMutationInstanceFromModelOperation(
362
- namespace.relationships,
363
- modelDefinition,
364
- opType,
365
- modelConstructor,
366
- retryWith,
367
- graphQLCondition,
368
- MutationEvent,
369
- this.modelInstanceCreator,
370
- mutationEvent.id
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
- localModel: this.modelInstanceCreator(
380
- modelConstructor,
381
- variables.input
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.errorType,
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('failed to execute errorHandler', err);
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 && primaryKey.length) {
460
+ if (primaryKey?.length) {
445
461
  for (const pkField of primaryKey) {
446
462
  deleteInput[pkField] = parsedData[pkField];
447
463
  }
448
464
  } else {
449
- deleteInput['id'] = parsedData.id;
465
+ // CPK TODO:
466
+ // Previous comment from Manuel: what if it was renamed ?
467
+ deleteInput['id'] = (<any>parsedData).id;
450
468
  }
451
469
 
452
- const filteredData =
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
- if (operation === TransformerMutationType.UPDATE) {
472
- // this limits the update mutation input to changed fields only
473
- return parsedData.hasOwnProperty(name);
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
- // scalars and non-model types
477
- return true;
478
- })
479
- .map(({ name, type, association }) => {
480
- let fieldName = name;
481
- let val = parsedData[name];
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
- return [fieldName, val];
492
- })
493
- .reduce((acc, [k, v]) => {
494
- acc[k] = v;
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
- ...filteredData,
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
- TransformerMutationType,
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
- // TODO Should this use `this.amplify.Auth` for SSR?
256
- const credentials = await Auth.currentCredentials();
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
- // TODO Should this use `this.amplify.Auth` for SSR?
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('federatedInfo');
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 = await Auth.currentAuthenticatedUser();
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: this.amplifyConfig
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 }) => 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 = ModelPredicateCreator.getPredicates(
417
- this.syncPredicates.get(modelDefinition),
418
- false
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
- TransformerMutationType.CREATE
539
- ].forEach(subscription => subscription.unsubscribe());
540
- subscriptions[modelName][
541
- TransformerMutationType.UPDATE
542
- ].forEach(subscription => subscription.unsubscribe());
543
- subscriptions[modelName][
544
- TransformerMutationType.DELETE
545
- ].forEach(subscription => subscription.unsubscribe());
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
  });