@aws-amplify/datastore 3.10.1-unstable.7 → 3.11.0

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 (49) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/aws-amplify-datastore.js +350 -109
  3. package/dist/aws-amplify-datastore.js.map +1 -1
  4. package/dist/aws-amplify-datastore.min.js +6 -6
  5. package/dist/aws-amplify-datastore.min.js.map +1 -1
  6. package/lib/datastore/datastore.js.map +1 -1
  7. package/lib/sync/index.js +3 -3
  8. package/lib/sync/index.js.map +1 -1
  9. package/lib/sync/processors/mutation.d.ts +2 -2
  10. package/lib/sync/processors/mutation.js +12 -5
  11. package/lib/sync/processors/mutation.js.map +1 -1
  12. package/lib/sync/processors/subscription.d.ts +3 -2
  13. package/lib/sync/processors/subscription.js +73 -32
  14. package/lib/sync/processors/subscription.js.map +1 -1
  15. package/lib/sync/processors/sync.d.ts +5 -2
  16. package/lib/sync/processors/sync.js +61 -26
  17. package/lib/sync/processors/sync.js.map +1 -1
  18. package/lib/sync/utils.d.ts +11 -1
  19. package/lib/sync/utils.js +59 -0
  20. package/lib/sync/utils.js.map +1 -1
  21. package/lib/types.d.ts +18 -8
  22. package/lib/types.js +6 -0
  23. package/lib/types.js.map +1 -1
  24. package/lib-esm/datastore/datastore.js.map +1 -1
  25. package/lib-esm/sync/index.js +3 -3
  26. package/lib-esm/sync/index.js.map +1 -1
  27. package/lib-esm/sync/processors/mutation.d.ts +2 -2
  28. package/lib-esm/sync/processors/mutation.js +14 -7
  29. package/lib-esm/sync/processors/mutation.js.map +1 -1
  30. package/lib-esm/sync/processors/subscription.d.ts +3 -2
  31. package/lib-esm/sync/processors/subscription.js +74 -33
  32. package/lib-esm/sync/processors/subscription.js.map +1 -1
  33. package/lib-esm/sync/processors/sync.d.ts +5 -2
  34. package/lib-esm/sync/processors/sync.js +62 -27
  35. package/lib-esm/sync/processors/sync.js.map +1 -1
  36. package/lib-esm/sync/utils.d.ts +11 -1
  37. package/lib-esm/sync/utils.js +58 -0
  38. package/lib-esm/sync/utils.js.map +1 -1
  39. package/lib-esm/types.d.ts +18 -8
  40. package/lib-esm/types.js +6 -0
  41. package/lib-esm/types.js.map +1 -1
  42. package/package.json +7 -7
  43. package/src/datastore/datastore.ts +2 -2
  44. package/src/sync/index.ts +6 -4
  45. package/src/sync/processors/mutation.ts +17 -8
  46. package/src/sync/processors/subscription.ts +39 -4
  47. package/src/sync/processors/sync.ts +33 -4
  48. package/src/sync/utils.ts +22 -0
  49. package/src/types.ts +25 -8
@@ -12,6 +12,8 @@ import {
12
12
  PredicatesGroup,
13
13
  ModelPredicate,
14
14
  AuthModeStrategy,
15
+ ErrorHandler,
16
+ ProcessName,
15
17
  } from '../../types';
16
18
  import {
17
19
  buildSubscriptionGraphQLOperation,
@@ -20,12 +22,27 @@ import {
20
22
  getUserGroupsFromToken,
21
23
  TransformerMutationType,
22
24
  getTokenForCustomAuth,
25
+ mapErrorToType,
26
+ ErrorMap,
23
27
  } from '../utils';
24
28
  import { ModelPredicateCreator } from '../../predicates';
25
29
  import { validatePredicate } from '../../util';
26
30
 
27
31
  const logger = new Logger('DataStore');
28
32
 
33
+ // TODO: add additional error maps
34
+ const errorMap = {
35
+ Unauthorized: (givenError: any) => {
36
+ const {
37
+ error: { errors: [{ message = '' } = {}] } = {
38
+ errors: [],
39
+ },
40
+ } = givenError;
41
+ const regex = /Connection failed.+Unauthorized/;
42
+ return regex.test(message);
43
+ },
44
+ } as ErrorMap;
45
+
29
46
  export enum CONTROL_MSG {
30
47
  CONNECTED = 'CONNECTED',
31
48
  }
@@ -56,7 +73,8 @@ class SubscriptionProcessor {
56
73
  private readonly schema: InternalSchema,
57
74
  private readonly syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>,
58
75
  private readonly amplifyConfig: Record<string, any> = {},
59
- private readonly authModeStrategy: AuthModeStrategy
76
+ private readonly authModeStrategy: AuthModeStrategy,
77
+ private readonly errorHandler: ErrorHandler
60
78
  ) {}
61
79
 
62
80
  private buildSubscription(
@@ -436,12 +454,31 @@ class SubscriptionProcessor {
436
454
  }
437
455
  this.drainBuffer();
438
456
  },
439
- error: subscriptionError => {
457
+ error: async subscriptionError => {
440
458
  const {
441
459
  error: { errors: [{ message = '' } = {}] } = {
442
460
  errors: [],
443
461
  },
444
462
  } = subscriptionError;
463
+ try {
464
+ await this.errorHandler({
465
+ recoverySuggestion:
466
+ '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',
467
+ localModel: null,
468
+ message,
469
+ model: modelDefinition.name,
470
+ operation,
471
+ errorType: mapErrorToType(
472
+ errorMap,
473
+ subscriptionError
474
+ ),
475
+ process: ProcessName.subscribe,
476
+ remoteModel: null,
477
+ cause: subscriptionError,
478
+ });
479
+ } catch (e) {
480
+ logger.error('Sync error handler failed with:', e);
481
+ }
445
482
 
446
483
  if (
447
484
  message.includes(
@@ -487,7 +524,6 @@ class SubscriptionProcessor {
487
524
  return;
488
525
  }
489
526
  }
490
-
491
527
  logger.warn('subscriptionError', message);
492
528
 
493
529
  if (typeof subscriptionReadyCallback === 'function') {
@@ -500,7 +536,6 @@ class SubscriptionProcessor {
500
536
  ) {
501
537
  return;
502
538
  }
503
-
504
539
  observer.error(message);
505
540
  },
506
541
  })
@@ -8,6 +8,8 @@ import {
8
8
  PredicatesGroup,
9
9
  GraphQLFilter,
10
10
  AuthModeStrategy,
11
+ ErrorHandler,
12
+ ProcessName,
11
13
  } from '../../types';
12
14
  import {
13
15
  buildGraphQLOperation,
@@ -16,6 +18,8 @@ import {
16
18
  getForbiddenError,
17
19
  predicateToGraphQLFilter,
18
20
  getTokenForCustomAuth,
21
+ mapErrorToType,
22
+ ErrorMap,
19
23
  } from '../utils';
20
24
  import {
21
25
  jitteredExponentialRetry,
@@ -24,7 +28,7 @@ import {
24
28
  NonRetryableError,
25
29
  } from '@aws-amplify/core';
26
30
  import { ModelPredicateCreator } from '../../predicates';
27
-
31
+ import { ModelInstanceCreator } from '../../datastore/datastore';
28
32
  const opResultDefaults = {
29
33
  items: [],
30
34
  nextToken: null,
@@ -33,6 +37,11 @@ const opResultDefaults = {
33
37
 
34
38
  const logger = new Logger('DataStore');
35
39
 
40
+ // TODO: add additional error maps
41
+ const errorMap = {
42
+ BadRecord: error => /^Cannot return \w+ for [\w-_]+ type/.test(error.message),
43
+ } as ErrorMap;
44
+
36
45
  class SyncProcessor {
37
46
  private readonly typeQuery = new WeakMap<SchemaModel, [string, string]>();
38
47
 
@@ -40,7 +49,9 @@ class SyncProcessor {
40
49
  private readonly schema: InternalSchema,
41
50
  private readonly syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>,
42
51
  private readonly amplifyConfig: Record<string, any> = {},
43
- private readonly authModeStrategy: AuthModeStrategy
52
+ private readonly authModeStrategy: AuthModeStrategy,
53
+ private readonly errorHandler: ErrorHandler,
54
+ private readonly modelInstanceCreator?: ModelInstanceCreator
44
55
  ) {
45
56
  this.generateQueries();
46
57
  }
@@ -223,15 +234,33 @@ class SyncProcessor {
223
234
  error.data[opName] &&
224
235
  error.data[opName].items
225
236
  );
226
-
227
237
  if (this.partialDataFeatureFlagEnabled()) {
228
238
  if (hasItems) {
229
239
  const result = error;
230
240
  result.data[opName].items = result.data[opName].items.filter(
231
241
  item => item !== null
232
242
  );
233
-
234
243
  if (error.errors) {
244
+ await Promise.all(
245
+ error.errors.map(async err => {
246
+ try {
247
+ await this.errorHandler({
248
+ recoverySuggestion:
249
+ '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',
250
+ localModel: null,
251
+ message: err.message,
252
+ model: modelDefinition.name,
253
+ operation: opName,
254
+ errorType: mapErrorToType(errorMap, err),
255
+ process: ProcessName.sync,
256
+ remoteModel: null,
257
+ cause: err,
258
+ });
259
+ } catch (e) {
260
+ logger.error('Sync error handler failed with:', e);
261
+ }
262
+ })
263
+ );
235
264
  Hub.dispatch('datastore', {
236
265
  event: 'syncQueriesPartialSyncError',
237
266
  data: {
package/src/sync/utils.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  ModelOperation,
27
27
  InternalSchema,
28
28
  AuthModeStrategy,
29
+ ErrorType,
29
30
  } from '../types';
30
31
  import { exhaustiveCheck } from '../util';
31
32
  import { MutationEvent } from './';
@@ -40,6 +41,27 @@ enum GraphQLOperationType {
40
41
  GET = 'query',
41
42
  }
42
43
 
44
+ export type ErrorMap = {
45
+ [key in ErrorType]: (error: Error) => boolean;
46
+ };
47
+
48
+ /**
49
+ * Categorizes an error with a broad error type, intended to make
50
+ * customer error handling code simpler.
51
+ * @param errorMap Error names and a list of patterns that indicate them (each pattern as a regex or function)
52
+ * @param error The underying error to categorize.
53
+ */
54
+ export function mapErrorToType(errorMap: ErrorMap, error: Error): ErrorType {
55
+ const errorTypes = [...Object.keys(errorMap)] as ErrorType[];
56
+ for (const errorType of errorTypes) {
57
+ const matcher = errorMap[errorType];
58
+ if (matcher(error)) {
59
+ return errorType;
60
+ }
61
+ }
62
+ return 'Unknown';
63
+ }
64
+
43
65
  export enum TransformerMutationType {
44
66
  CREATE = 'Create',
45
67
  UPDATE = 'Update',
package/src/types.ts CHANGED
@@ -658,7 +658,7 @@ export type DataStoreConfig = {
658
658
  DataStore?: {
659
659
  authModeStrategyType?: AuthModeStrategyType;
660
660
  conflictHandler?: ConflictHandler; // default : retry until client wins up to x times
661
- errorHandler?: (error: SyncError) => void; // default : logger.warn
661
+ errorHandler?: (error: SyncError<PersistentModel>) => void; // default : logger.warn
662
662
  maxRecordsToSync?: number; // merge
663
663
  syncPageSize?: number;
664
664
  fullSyncInterval?: number;
@@ -668,7 +668,7 @@ export type DataStoreConfig = {
668
668
  };
669
669
  authModeStrategyType?: AuthModeStrategyType;
670
670
  conflictHandler?: ConflictHandler; // default : retry until client wins up to x times
671
- errorHandler?: (error: SyncError) => void; // default : logger.warn
671
+ errorHandler?: (error: SyncError<PersistentModel>) => void; // default : logger.warn
672
672
  maxRecordsToSync?: number; // merge
673
673
  syncPageSize?: number;
674
674
  fullSyncInterval?: number;
@@ -775,15 +775,32 @@ export type SyncConflict = {
775
775
  attempts: number;
776
776
  };
777
777
 
778
- export type SyncError = {
778
+ export type SyncError<T extends PersistentModel> = {
779
779
  message: string;
780
- errorType: string;
781
- errorInfo: string;
782
- localModel: PersistentModel;
783
- remoteModel: PersistentModel;
780
+ errorType: ErrorType;
781
+ errorInfo?: string;
782
+ recoverySuggestion?: string;
783
+ model?: string;
784
+ localModel: T;
785
+ remoteModel: T;
786
+ process: ProcessName;
784
787
  operation: string;
788
+ cause?: Error;
785
789
  };
786
790
 
791
+ export type ErrorType =
792
+ | 'ConfigError'
793
+ | 'BadRecord'
794
+ | 'Unauthorized'
795
+ | 'Transient'
796
+ | 'Unknown';
797
+
798
+ export enum ProcessName {
799
+ 'sync' = 'sync',
800
+ 'mutate' = 'mutate',
801
+ 'subscribe' = 'subscribe',
802
+ }
803
+
787
804
  export const DISCARD = Symbol('DISCARD');
788
805
 
789
806
  export type ConflictHandler = (
@@ -792,7 +809,7 @@ export type ConflictHandler = (
792
809
  | Promise<PersistentModel | typeof DISCARD>
793
810
  | PersistentModel
794
811
  | typeof DISCARD;
795
- export type ErrorHandler = (error: SyncError) => void;
812
+ export type ErrorHandler = (error: SyncError<PersistentModel>) => void;
796
813
 
797
814
  export type DeferredCallbackResolverOptions = {
798
815
  callback: () => void;