@aws-amplify/datastore 3.14.0 → 3.14.1-unstable.10

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 (110) hide show
  1. package/dist/aws-amplify-datastore.js +2799 -1459
  2. package/dist/aws-amplify-datastore.js.map +1 -1
  3. package/dist/aws-amplify-datastore.min.js +10 -10
  4. package/dist/aws-amplify-datastore.min.js.map +1 -1
  5. package/lib/authModeStrategies/multiAuthStrategy.js +11 -0
  6. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  7. package/lib/datastore/datastore.js +524 -323
  8. package/lib/datastore/datastore.js.map +1 -1
  9. package/lib/storage/adapter/IndexedDBAdapter.js +76 -25
  10. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  11. package/lib/storage/storage.js +2 -2
  12. package/lib/storage/storage.js.map +1 -1
  13. package/lib/sync/datastoreConnectivity.js +45 -0
  14. package/lib/sync/datastoreConnectivity.js.map +1 -1
  15. package/lib/sync/index.js +518 -395
  16. package/lib/sync/index.js.map +1 -1
  17. package/lib/sync/merger.js +6 -0
  18. package/lib/sync/merger.js.map +1 -1
  19. package/lib/sync/outbox.js +66 -62
  20. package/lib/sync/outbox.js.map +1 -1
  21. package/lib/sync/processors/mutation.js +207 -165
  22. package/lib/sync/processors/mutation.js.map +1 -1
  23. package/lib/sync/processors/subscription.js +210 -175
  24. package/lib/sync/processors/subscription.js.map +1 -1
  25. package/lib/sync/processors/sync.js +95 -72
  26. package/lib/sync/processors/sync.js.map +1 -1
  27. package/lib/sync/utils.js +1 -3
  28. package/lib/sync/utils.js.map +1 -1
  29. package/lib/util.js +89 -0
  30. package/lib/util.js.map +1 -1
  31. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +11 -0
  32. package/lib-esm/authModeStrategies/multiAuthStrategy.js +11 -0
  33. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  34. package/lib-esm/datastore/datastore.d.ts +95 -2
  35. package/lib-esm/datastore/datastore.js +524 -323
  36. package/lib-esm/datastore/datastore.js.map +1 -1
  37. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +21 -0
  38. package/lib-esm/storage/adapter/IndexedDBAdapter.js +77 -26
  39. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  40. package/lib-esm/storage/storage.js +2 -2
  41. package/lib-esm/storage/storage.js.map +1 -1
  42. package/lib-esm/sync/datastoreConnectivity.d.ts +1 -0
  43. package/lib-esm/sync/datastoreConnectivity.js +45 -0
  44. package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
  45. package/lib-esm/sync/index.d.ts +9 -1
  46. package/lib-esm/sync/index.js +519 -396
  47. package/lib-esm/sync/index.js.map +1 -1
  48. package/lib-esm/sync/merger.d.ts +6 -0
  49. package/lib-esm/sync/merger.js +6 -0
  50. package/lib-esm/sync/merger.js.map +1 -1
  51. package/lib-esm/sync/outbox.js +66 -62
  52. package/lib-esm/sync/outbox.js.map +1 -1
  53. package/lib-esm/sync/processors/mutation.d.ts +2 -0
  54. package/lib-esm/sync/processors/mutation.js +208 -166
  55. package/lib-esm/sync/processors/mutation.js.map +1 -1
  56. package/lib-esm/sync/processors/subscription.d.ts +2 -0
  57. package/lib-esm/sync/processors/subscription.js +211 -176
  58. package/lib-esm/sync/processors/subscription.js.map +1 -1
  59. package/lib-esm/sync/processors/sync.d.ts +2 -0
  60. package/lib-esm/sync/processors/sync.js +96 -73
  61. package/lib-esm/sync/processors/sync.js.map +1 -1
  62. package/lib-esm/sync/utils.js +1 -3
  63. package/lib-esm/sync/utils.js.map +1 -1
  64. package/lib-esm/util.d.ts +11 -0
  65. package/lib-esm/util.js +89 -0
  66. package/lib-esm/util.js.map +1 -1
  67. package/package.json +7 -7
  68. package/src/authModeStrategies/multiAuthStrategy.ts +11 -0
  69. package/src/datastore/datastore.ts +572 -366
  70. package/src/storage/adapter/IndexedDBAdapter.ts +50 -9
  71. package/src/storage/storage.ts +2 -2
  72. package/src/sync/datastoreConnectivity.ts +6 -0
  73. package/src/sync/index.ts +492 -400
  74. package/src/sync/merger.ts +6 -0
  75. package/src/sync/outbox.ts +1 -1
  76. package/src/sync/processors/mutation.ts +139 -104
  77. package/src/sync/processors/subscription.ts +287 -250
  78. package/src/sync/processors/sync.ts +88 -60
  79. package/src/sync/utils.ts +1 -3
  80. package/src/util.ts +92 -2
  81. package/lib/authModeStrategies/defaultAuthStrategy.d.ts +0 -2
  82. package/lib/authModeStrategies/index.d.ts +0 -2
  83. package/lib/authModeStrategies/multiAuthStrategy.d.ts +0 -2
  84. package/lib/datastore/datastore.d.ts +0 -63
  85. package/lib/index.d.ts +0 -15
  86. package/lib/predicates/index.d.ts +0 -16
  87. package/lib/predicates/sort.d.ts +0 -8
  88. package/lib/ssr/index.d.ts +0 -3
  89. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +0 -41
  90. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +0 -39
  91. package/lib/storage/adapter/InMemoryStore.d.ts +0 -11
  92. package/lib/storage/adapter/InMemoryStore.native.d.ts +0 -1
  93. package/lib/storage/adapter/IndexedDBAdapter.d.ts +0 -38
  94. package/lib/storage/adapter/getDefaultAdapter/index.d.ts +0 -3
  95. package/lib/storage/adapter/getDefaultAdapter/index.native.d.ts +0 -3
  96. package/lib/storage/adapter/index.d.ts +0 -9
  97. package/lib/storage/storage.d.ts +0 -49
  98. package/lib/sync/datastoreConnectivity.d.ts +0 -15
  99. package/lib/sync/datastoreReachability/index.d.ts +0 -3
  100. package/lib/sync/datastoreReachability/index.native.d.ts +0 -3
  101. package/lib/sync/index.d.ts +0 -81
  102. package/lib/sync/merger.d.ts +0 -11
  103. package/lib/sync/outbox.d.ts +0 -27
  104. package/lib/sync/processors/errorMaps.d.ts +0 -17
  105. package/lib/sync/processors/mutation.d.ts +0 -56
  106. package/lib/sync/processors/subscription.d.ts +0 -31
  107. package/lib/sync/processors/sync.d.ts +0 -26
  108. package/lib/sync/utils.d.ts +0 -42
  109. package/lib/types.d.ts +0 -501
  110. package/lib/util.d.ts +0 -145
@@ -1,7 +1,13 @@
1
1
  import API from '@aws-amplify/api';
2
- import { Amplify, ConsoleLogger as Logger, Hub, JS } from '@aws-amplify/core';
3
2
  import { Auth } from '@aws-amplify/auth';
4
3
  import Cache from '@aws-amplify/cache';
4
+ import {
5
+ Amplify,
6
+ ConsoleLogger as Logger,
7
+ Hub,
8
+ JS,
9
+ BackgroundProcessManager,
10
+ } from '@aws-amplify/core';
5
11
  import {
6
12
  Draft,
7
13
  immerable,
@@ -90,6 +96,7 @@ import {
90
96
  mergePatches,
91
97
  } from '../util';
92
98
  import { getIdentifierValue } from '../sync/utils';
99
+ import DataStoreConnectivity from '../sync/datastoreConnectivity';
93
100
 
94
101
  setAutoFreeze(true);
95
102
  enablePatches();
@@ -247,10 +254,14 @@ const initSchema = (userSchema: Schema) => {
247
254
  return userClasses;
248
255
  };
249
256
 
250
- /* Checks if the schema has been initialized by initSchema().
257
+ /**
258
+ * Throws an exception if the schema has *not* been initialized
259
+ * by `initSchema()`.
260
+ *
261
+ * **To be called before trying to access schema.**
251
262
  *
252
- * Call this function before accessing schema.
253
- * Currently this only needs to be called in start() and clear() because all other functions will call start first.
263
+ * Currently this only needs to be called in `start()` and `clear()` because
264
+ * all other functions will call start first.
254
265
  */
255
266
  const checkSchemaInitialized = () => {
256
267
  if (schema === undefined) {
@@ -283,8 +294,22 @@ const createTypeClasses: (
283
294
  return classes;
284
295
  };
285
296
 
297
+ /**
298
+ * Constructs a model and records it with its metadata in a weakset. Allows for
299
+ * the separate storage of core model fields and Amplify/DataStore metadata
300
+ * fields that the customer app does not want exposed.
301
+ *
302
+ * @param modelConstructor The model constructor.
303
+ * @param init Init data that would normally be passed to the constructor.
304
+ * @returns The initialized model.
305
+ */
286
306
  export declare type ModelInstanceCreator = typeof modelInstanceCreator;
287
307
 
308
+ /**
309
+ * Collection of instantiated models to allow storage of metadata apart from
310
+ * the model visible to the consuming app -- in case the app doesn't have
311
+ * metadata fields (_version, _deleted, etc.) exposed on the model itself.
312
+ */
288
313
  const instancesMetadata = new WeakSet<ModelInit<unknown, unknown>>();
289
314
 
290
315
  function modelInstanceCreator<T extends PersistentModel>(
@@ -729,6 +754,18 @@ function getModelConstructorByModelName(
729
754
  }
730
755
  }
731
756
 
757
+ /**
758
+ * Queries the DataStore metadata tables to see if they are the expected
759
+ * version. If not, clobbers the whole DB. If so, leaves them alone.
760
+ * Otherwise, simply writes the schema version.
761
+ *
762
+ * SIDE EFFECT:
763
+ * 1. Creates a transaction
764
+ * 1. Updates data.
765
+ *
766
+ * @param storage Storage adapter containing the metadata.
767
+ * @param version The expected schema version.
768
+ */
732
769
  async function checkSchemaVersion(
733
770
  storage: Storage,
734
771
  version: string
@@ -807,6 +844,14 @@ function getNamespace(): SchemaNamespace {
807
844
  return namespace;
808
845
  }
809
846
 
847
+ enum DataStoreState {
848
+ NotRunning = 'Not Running',
849
+ Starting = 'Starting',
850
+ Running = 'Running',
851
+ Stopping = 'Stopping',
852
+ Clearing = 'Clearing',
853
+ }
854
+
810
855
  class DataStore {
811
856
  // reference to configured category instances. Used for preserving SSR context
812
857
  private Auth = Auth;
@@ -836,98 +881,188 @@ class DataStore {
836
881
  API: this.API,
837
882
  Cache: this.Cache,
838
883
  };
884
+ private connectivityMonitor?: DataStoreConnectivity;
885
+
886
+ /**
887
+ * **IMPORTANT!**
888
+ *
889
+ * Accumulator for background things that can **and MUST** be called when
890
+ * DataStore stops.
891
+ *
892
+ * These jobs **MUST** be *idempotent promises* that resolve ONLY
893
+ * once the intended jobs are completely finished and/or otherwise destroyed
894
+ * and cleaned up with ZERO outstanding:
895
+ *
896
+ * 1. side effects (e.g., state changes)
897
+ * 1. callbacks
898
+ * 1. subscriptions
899
+ * 1. calls to storage
900
+ * 1. *etc.*
901
+ *
902
+ * Methods that create pending promises, subscriptions, callbacks, or any
903
+ * type of side effect **MUST** be registered with the manager. And, a new
904
+ * manager must be created after each `exit()`.
905
+ *
906
+ * Failure to comply will put DataStore into a highly unpredictable state
907
+ * when it needs to stop or clear -- which occurs when restarting with new
908
+ * sync expressions, during testing, and potentially during app code
909
+ * recovery handling, etc..
910
+ *
911
+ * It is up to the discretion of each disposer whether to wait for job
912
+ * completion or to cancel operations and issue failures *as long as the
913
+ * disposer returns in a reasonable amount of time.*
914
+ *
915
+ * (Reasonable = *seconds*, not minutes.)
916
+ */
917
+ private runningProcesses = new BackgroundProcessManager();
918
+
919
+ /**
920
+ * Indicates what state DataStore is in.
921
+ *
922
+ * Not [yet?] used for actual state management; but for messaging
923
+ * when errors occur, to help troubleshoot.
924
+ */
925
+ private state: DataStoreState = DataStoreState.NotRunning;
839
926
 
840
927
  getModuleName() {
841
928
  return 'DataStore';
842
929
  }
843
930
 
844
- start = async (): Promise<void> => {
845
- if (this.initialized === undefined) {
846
- logger.debug('Starting DataStore');
847
- this.initialized = new Promise((res, rej) => {
848
- this.initResolve = res;
849
- this.initReject = rej;
850
- });
851
- } else {
852
- await this.initialized;
931
+ /**
932
+ * Builds a function to capture `BackgroundManagerNotOpenError`'s to produce friendlier,
933
+ * more instructive errors for customers.
934
+ *
935
+ * @param operation The name of the operation (usually a Datastore method) the customer
936
+ * tried to call.
937
+ */
938
+ handleAddProcError(operation: string) {
939
+ /**
940
+ * If the tested error is a `BackgroundManagerNotOpenError`, it will be captured
941
+ * and replaced with a friendlier message that instructs the App Developer.
942
+ *
943
+ * @param err An error to test.
944
+ */
945
+ const handler = (err: Error) => {
946
+ if (err.message.startsWith('BackgroundManagerNotOpenError')) {
947
+ throw new Error(
948
+ [
949
+ `DataStoreStateError: Tried to execute \`${operation}\` while DataStore was "${this.state}".`,
950
+ `This can only be done while DataStore is "Started" or "Stopped". To remedy:`,
951
+ 'Ensure all calls to `stop()` and `clear()` have completed first.',
952
+ 'If this is not possible, retry the operation until it succeeds.',
953
+ ].join('\n')
954
+ );
955
+ } else {
956
+ throw err;
957
+ }
958
+ };
853
959
 
854
- return;
855
- }
960
+ return handler;
961
+ }
856
962
 
857
- this.storage = new Storage(
858
- schema,
859
- namespaceResolver,
860
- getModelConstructorByModelName,
861
- modelInstanceCreator,
862
- this.storageAdapter,
863
- this.sessionId
864
- );
963
+ /**
964
+ * If not already done:
965
+ * 1. Attaches and initializes storage.
966
+ * 1. Loads the schema and records metadata.
967
+ * 1. If `this.amplifyConfig.aws_appsync_graphqlEndpoint` contains a URL,
968
+ * attaches a sync engine, starts it, and subscribes.
969
+ */
970
+ start = async (): Promise<void> => {
971
+ return this.runningProcesses
972
+ .add(async () => {
973
+ this.state = DataStoreState.Starting;
974
+ if (this.initialized === undefined) {
975
+ logger.debug('Starting DataStore');
976
+ this.initialized = new Promise((res, rej) => {
977
+ this.initResolve = res;
978
+ this.initReject = rej;
979
+ });
980
+ } else {
981
+ await this.initialized;
982
+ return;
983
+ }
865
984
 
866
- await this.storage.init();
985
+ this.storage = new Storage(
986
+ schema,
987
+ namespaceResolver,
988
+ getModelConstructorByModelName,
989
+ modelInstanceCreator,
990
+ this.storageAdapter,
991
+ this.sessionId
992
+ );
867
993
 
868
- checkSchemaInitialized();
869
- await checkSchemaVersion(this.storage, schema.version);
994
+ await this.storage.init();
870
995
 
871
- const { aws_appsync_graphqlEndpoint } = this.amplifyConfig;
996
+ checkSchemaInitialized();
997
+ await checkSchemaVersion(this.storage, schema.version);
872
998
 
873
- if (aws_appsync_graphqlEndpoint) {
874
- logger.debug('GraphQL endpoint available', aws_appsync_graphqlEndpoint);
999
+ const { aws_appsync_graphqlEndpoint } = this.amplifyConfig;
875
1000
 
876
- this.syncPredicates = await this.processSyncExpressions();
1001
+ if (aws_appsync_graphqlEndpoint) {
1002
+ logger.debug(
1003
+ 'GraphQL endpoint available',
1004
+ aws_appsync_graphqlEndpoint
1005
+ );
877
1006
 
878
- this.sync = new SyncEngine(
879
- schema,
880
- namespaceResolver,
881
- syncClasses,
882
- userClasses,
883
- this.storage,
884
- modelInstanceCreator,
885
- this.conflictHandler,
886
- this.errorHandler,
887
- this.syncPredicates,
888
- this.amplifyConfig,
889
- this.authModeStrategy,
890
- this.amplifyContext
891
- );
1007
+ this.syncPredicates = await this.processSyncExpressions();
1008
+
1009
+ this.sync = new SyncEngine(
1010
+ schema,
1011
+ namespaceResolver,
1012
+ syncClasses,
1013
+ userClasses,
1014
+ this.storage,
1015
+ modelInstanceCreator,
1016
+ this.conflictHandler,
1017
+ this.errorHandler,
1018
+ this.syncPredicates,
1019
+ this.amplifyConfig,
1020
+ this.authModeStrategy,
1021
+ this.amplifyContext,
1022
+ this.connectivityMonitor
1023
+ );
892
1024
 
893
- // tslint:disable-next-line:max-line-length
894
- const fullSyncIntervalInMilliseconds = this.fullSyncInterval * 1000 * 60; // fullSyncInterval from param is in minutes
895
- syncSubscription = this.sync
896
- .start({ fullSyncInterval: fullSyncIntervalInMilliseconds })
897
- .subscribe({
898
- next: ({ type, data }) => {
899
- // In Node, we need to wait for queries to be synced to prevent returning empty arrays.
900
- // In the Browser, we can begin returning data once subscriptions are in place.
901
- const readyType = isNode
902
- ? ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
903
- : ControlMessage.SYNC_ENGINE_STORAGE_SUBSCRIBED;
904
-
905
- if (type === readyType) {
906
- this.initResolve();
907
- }
1025
+ const fullSyncIntervalInMilliseconds =
1026
+ this.fullSyncInterval * 1000 * 60; // fullSyncInterval from param is in minutes
1027
+ syncSubscription = this.sync
1028
+ .start({ fullSyncInterval: fullSyncIntervalInMilliseconds })
1029
+ .subscribe({
1030
+ next: ({ type, data }) => {
1031
+ // In Node, we need to wait for queries to be synced to prevent returning empty arrays.
1032
+ // In the Browser, we can begin returning data once subscriptions are in place.
1033
+ const readyType = isNode
1034
+ ? ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
1035
+ : ControlMessage.SYNC_ENGINE_STORAGE_SUBSCRIBED;
1036
+
1037
+ if (type === readyType) {
1038
+ this.initResolve();
1039
+ }
908
1040
 
909
- Hub.dispatch('datastore', {
910
- event: type,
911
- data,
1041
+ Hub.dispatch('datastore', {
1042
+ event: type,
1043
+ data,
1044
+ });
1045
+ },
1046
+ error: err => {
1047
+ logger.warn('Sync error', err);
1048
+ this.initReject();
1049
+ },
912
1050
  });
913
- },
914
- error: err => {
915
- logger.warn('Sync error', err);
916
- this.initReject();
917
- },
918
- });
919
- } else {
920
- logger.warn(
921
- "Data won't be synchronized. No GraphQL endpoint configured. Did you forget `Amplify.configure(awsconfig)`?",
922
- {
923
- config: this.amplifyConfig,
924
- }
925
- );
1051
+ } else {
1052
+ logger.warn(
1053
+ "Data won't be synchronized. No GraphQL endpoint configured. Did you forget `Amplify.configure(awsconfig)`?",
1054
+ {
1055
+ config: this.amplifyConfig,
1056
+ }
1057
+ );
926
1058
 
927
- this.initResolve();
928
- }
1059
+ this.initResolve();
1060
+ }
929
1061
 
930
- await this.initialized;
1062
+ await this.initialized;
1063
+ this.state = DataStoreState.Running;
1064
+ }, 'datastore start')
1065
+ .catch(this.handleAddProcError('DataStore.start()'));
931
1066
  };
932
1067
 
933
1068
  query: {
@@ -951,129 +1086,136 @@ class DataStore {
951
1086
  | typeof PredicateAll,
952
1087
  paginationProducer?: ProducerPaginationInput<T>
953
1088
  ): Promise<T | T[] | undefined> => {
954
- await this.start();
1089
+ return this.runningProcesses
1090
+ .add(async () => {
1091
+ await this.start();
955
1092
 
956
- //#region Input validation
1093
+ //#region Input validation
957
1094
 
958
- if (!isValidModelConstructor(modelConstructor)) {
959
- const msg = 'Constructor is not for a valid model';
960
- logger.error(msg, { modelConstructor });
1095
+ if (!isValidModelConstructor(modelConstructor)) {
1096
+ const msg = 'Constructor is not for a valid model';
1097
+ logger.error(msg, { modelConstructor });
961
1098
 
962
- throw new Error(msg);
963
- }
1099
+ throw new Error(msg);
1100
+ }
964
1101
 
965
- if (typeof identifierOrCriteria === 'string') {
966
- if (paginationProducer !== undefined) {
967
- logger.warn('Pagination is ignored when querying by id');
968
- }
969
- }
1102
+ if (typeof identifierOrCriteria === 'string') {
1103
+ if (paginationProducer !== undefined) {
1104
+ logger.warn('Pagination is ignored when querying by id');
1105
+ }
1106
+ }
970
1107
 
971
- const modelDefinition = getModelDefinition(modelConstructor);
972
- const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1108
+ const modelDefinition = getModelDefinition(modelConstructor);
1109
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
973
1110
 
974
- let predicate: ModelPredicate<T>;
1111
+ let predicate: ModelPredicate<T>;
975
1112
 
976
- if (isQueryOne(identifierOrCriteria)) {
977
- if (keyFields.length > 1) {
978
- const msg = errorMessages.queryByPkWithCompositeKeyPresent;
979
- logger.error(msg, { keyFields });
1113
+ if (isQueryOne(identifierOrCriteria)) {
1114
+ if (keyFields.length > 1) {
1115
+ const msg = errorMessages.queryByPkWithCompositeKeyPresent;
1116
+ logger.error(msg, { keyFields });
980
1117
 
981
- throw new Error(msg);
982
- }
1118
+ throw new Error(msg);
1119
+ }
983
1120
 
984
- predicate = ModelPredicateCreator.createForSingleField<T>(
985
- modelDefinition,
986
- keyFields[0],
987
- identifierOrCriteria
988
- );
989
- } else {
990
- // Object is being queried using object literal syntax
991
- if (isIdentifierObject(<T>identifierOrCriteria, modelDefinition)) {
992
- predicate = ModelPredicateCreator.createForPk<T>(
993
- modelDefinition,
994
- <T>identifierOrCriteria
995
- );
996
- } else if (isPredicatesAll(identifierOrCriteria)) {
997
- // Predicates.ALL means "all records", so no predicate (undefined)
998
- predicate = undefined;
999
- } else {
1000
- predicate = ModelPredicateCreator.createFromExisting(
1121
+ predicate = ModelPredicateCreator.createForSingleField<T>(
1122
+ modelDefinition,
1123
+ keyFields[0],
1124
+ identifierOrCriteria
1125
+ );
1126
+ } else {
1127
+ // Object is being queried using object literal syntax
1128
+ if (isIdentifierObject(<T>identifierOrCriteria, modelDefinition)) {
1129
+ predicate = ModelPredicateCreator.createForPk<T>(
1130
+ modelDefinition,
1131
+ <T>identifierOrCriteria
1132
+ );
1133
+ } else if (isPredicatesAll(identifierOrCriteria)) {
1134
+ // Predicates.ALL means "all records", so no predicate (undefined)
1135
+ predicate = undefined;
1136
+ } else {
1137
+ predicate = ModelPredicateCreator.createFromExisting(
1138
+ modelDefinition,
1139
+ <any>identifierOrCriteria
1140
+ );
1141
+ }
1142
+ }
1143
+
1144
+ const pagination = this.processPagination(
1001
1145
  modelDefinition,
1002
- <any>identifierOrCriteria
1146
+ paginationProducer
1003
1147
  );
1004
- }
1005
- }
1006
-
1007
- const pagination = this.processPagination(
1008
- modelDefinition,
1009
- paginationProducer
1010
- );
1011
1148
 
1012
- //#endregion
1013
-
1014
- logger.debug('params ready', {
1015
- modelConstructor,
1016
- predicate: ModelPredicateCreator.getPredicates(predicate, false),
1017
- pagination: {
1018
- ...pagination,
1019
- sort: ModelSortPredicateCreator.getPredicates(
1020
- pagination && pagination.sort,
1021
- false
1022
- ),
1023
- },
1024
- });
1149
+ //#endregion
1150
+
1151
+ logger.debug('params ready', {
1152
+ modelConstructor,
1153
+ predicate: ModelPredicateCreator.getPredicates(predicate, false),
1154
+ pagination: {
1155
+ ...pagination,
1156
+ sort: ModelSortPredicateCreator.getPredicates(
1157
+ pagination && pagination.sort,
1158
+ false
1159
+ ),
1160
+ },
1161
+ });
1025
1162
 
1026
- const result = await this.storage.query(
1027
- modelConstructor,
1028
- predicate,
1029
- pagination
1030
- );
1163
+ const result = await this.storage.query(
1164
+ modelConstructor,
1165
+ predicate,
1166
+ pagination
1167
+ );
1031
1168
 
1032
- const returnOne =
1033
- isQueryOne(identifierOrCriteria) ||
1034
- isIdentifierObject(identifierOrCriteria, modelDefinition);
1169
+ const returnOne =
1170
+ isQueryOne(identifierOrCriteria) ||
1171
+ isIdentifierObject(identifierOrCriteria, modelDefinition);
1035
1172
 
1036
- return returnOne ? result[0] : result;
1173
+ return returnOne ? result[0] : result;
1174
+ }, 'datastore query')
1175
+ .catch(this.handleAddProcError('DataStore.query()'));
1037
1176
  };
1038
1177
 
1039
1178
  save = async <T extends PersistentModel>(
1040
1179
  model: T,
1041
1180
  condition?: ProducerModelPredicate<T>
1042
1181
  ): Promise<T> => {
1043
- await this.start();
1182
+ return this.runningProcesses
1183
+ .add(async () => {
1184
+ await this.start();
1044
1185
 
1045
- // Immer patches for constructing a correct update mutation input
1046
- // Allows us to only include changed fields for updates
1047
- const patchesTuple = modelPatchesMap.get(model);
1186
+ // Immer patches for constructing a correct update mutation input
1187
+ // Allows us to only include changed fields for updates
1188
+ const patchesTuple = modelPatchesMap.get(model);
1048
1189
 
1049
- const modelConstructor: PersistentModelConstructor<T> | undefined = model
1050
- ? <PersistentModelConstructor<T>>model.constructor
1051
- : undefined;
1190
+ const modelConstructor: PersistentModelConstructor<T> | undefined =
1191
+ model ? <PersistentModelConstructor<T>>model.constructor : undefined;
1052
1192
 
1053
- if (!isValidModelConstructor(modelConstructor)) {
1054
- const msg = 'Object is not an instance of a valid model';
1055
- logger.error(msg, { model });
1193
+ if (!isValidModelConstructor(modelConstructor)) {
1194
+ const msg = 'Object is not an instance of a valid model';
1195
+ logger.error(msg, { model });
1056
1196
 
1057
- throw new Error(msg);
1058
- }
1197
+ throw new Error(msg);
1198
+ }
1059
1199
 
1060
- const modelDefinition = getModelDefinition(modelConstructor);
1200
+ const modelDefinition = getModelDefinition(modelConstructor);
1061
1201
 
1062
- const producedCondition = ModelPredicateCreator.createFromExisting(
1063
- modelDefinition,
1064
- condition!
1065
- );
1202
+ const producedCondition = ModelPredicateCreator.createFromExisting(
1203
+ modelDefinition,
1204
+ condition!
1205
+ );
1066
1206
 
1067
- const [savedModel] = await this.storage.runExclusive(async s => {
1068
- await s.save(model, producedCondition, undefined, patchesTuple);
1207
+ const [savedModel] = await this.storage.runExclusive(async s => {
1208
+ await s.save(model, producedCondition, undefined, patchesTuple);
1069
1209
 
1070
- return s.query<T>(
1071
- modelConstructor,
1072
- ModelPredicateCreator.createForPk(modelDefinition, model)
1073
- );
1074
- });
1210
+ return s.query<T>(
1211
+ modelConstructor,
1212
+ ModelPredicateCreator.createForPk(modelDefinition, model)
1213
+ );
1214
+ });
1075
1215
 
1076
- return savedModel;
1216
+ return savedModel;
1217
+ }, 'datastore save')
1218
+ .catch(this.handleAddProcError('DataStore.save()'));
1077
1219
  };
1078
1220
 
1079
1221
  setConflictHandler = (config: DataStoreConfig): ConflictHandler => {
@@ -1131,112 +1273,122 @@ class DataStore {
1131
1273
  | ProducerModelPredicate<T>
1132
1274
  | typeof PredicateAll
1133
1275
  ): Promise<T | T[]> => {
1134
- await this.start();
1276
+ return this.runningProcesses
1277
+ .add(async () => {
1278
+ await this.start();
1135
1279
 
1136
- let condition: ModelPredicate<T>;
1280
+ let condition: ModelPredicate<T>;
1137
1281
 
1138
- if (!modelOrConstructor) {
1139
- const msg = 'Model or Model Constructor required';
1140
- logger.error(msg, { modelOrConstructor });
1282
+ if (!modelOrConstructor) {
1283
+ const msg = 'Model or Model Constructor required';
1284
+ logger.error(msg, { modelOrConstructor });
1141
1285
 
1142
- throw new Error(msg);
1143
- }
1286
+ throw new Error(msg);
1287
+ }
1144
1288
 
1145
- if (isValidModelConstructor<T>(modelOrConstructor)) {
1146
- const modelConstructor = modelOrConstructor;
1289
+ if (isValidModelConstructor<T>(modelOrConstructor)) {
1290
+ const modelConstructor = modelOrConstructor;
1147
1291
 
1148
- if (!identifierOrCriteria) {
1149
- const msg =
1150
- 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL';
1151
- logger.error(msg, { identifierOrCriteria });
1292
+ if (!identifierOrCriteria) {
1293
+ const msg =
1294
+ 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL';
1295
+ logger.error(msg, { identifierOrCriteria });
1152
1296
 
1153
- throw new Error(msg);
1154
- }
1297
+ throw new Error(msg);
1298
+ }
1155
1299
 
1156
- const modelDefinition = getModelDefinition(modelConstructor);
1300
+ const modelDefinition = getModelDefinition(modelConstructor);
1157
1301
 
1158
- if (typeof identifierOrCriteria === 'string') {
1159
- const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1302
+ if (typeof identifierOrCriteria === 'string') {
1303
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1160
1304
 
1161
- if (keyFields.length > 1) {
1162
- const msg = errorMessages.deleteByPkWithCompositeKeyPresent;
1163
- logger.error(msg, { keyFields });
1305
+ if (keyFields.length > 1) {
1306
+ const msg = errorMessages.deleteByPkWithCompositeKeyPresent;
1307
+ logger.error(msg, { keyFields });
1164
1308
 
1165
- throw new Error(msg);
1166
- }
1309
+ throw new Error(msg);
1310
+ }
1167
1311
 
1168
- condition = ModelPredicateCreator.createForSingleField<T>(
1169
- getModelDefinition(modelConstructor),
1170
- keyFields[0],
1171
- identifierOrCriteria
1172
- );
1173
- } else {
1174
- if (isIdentifierObject(identifierOrCriteria, modelDefinition)) {
1175
- condition = ModelPredicateCreator.createForPk<T>(
1176
- modelDefinition,
1177
- <T>identifierOrCriteria
1178
- );
1179
- } else {
1180
- condition = ModelPredicateCreator.createFromExisting(
1181
- modelDefinition,
1182
- /**
1183
- * idOrCriteria is always a ProducerModelPredicate<T>, never a symbol.
1184
- * The symbol is used only for typing purposes. e.g. see Predicates.ALL
1185
- */
1186
- identifierOrCriteria as ProducerModelPredicate<T>
1187
- );
1188
- }
1312
+ condition = ModelPredicateCreator.createForSingleField<T>(
1313
+ getModelDefinition(modelConstructor),
1314
+ keyFields[0],
1315
+ identifierOrCriteria
1316
+ );
1317
+ } else {
1318
+ if (isIdentifierObject(identifierOrCriteria, modelDefinition)) {
1319
+ condition = ModelPredicateCreator.createForPk<T>(
1320
+ modelDefinition,
1321
+ <T>identifierOrCriteria
1322
+ );
1323
+ } else {
1324
+ condition = ModelPredicateCreator.createFromExisting(
1325
+ modelDefinition,
1326
+ /**
1327
+ * idOrCriteria is always a ProducerModelPredicate<T>, never a symbol.
1328
+ * The symbol is used only for typing purposes. e.g. see Predicates.ALL
1329
+ */
1330
+ identifierOrCriteria as ProducerModelPredicate<T>
1331
+ );
1332
+ }
1189
1333
 
1190
- if (!condition || !ModelPredicateCreator.isValidPredicate(condition)) {
1191
- const msg =
1192
- 'Criteria required. Do you want to delete all? Pass Predicates.ALL';
1193
- logger.error(msg, { condition });
1334
+ if (
1335
+ !condition ||
1336
+ !ModelPredicateCreator.isValidPredicate(condition)
1337
+ ) {
1338
+ const msg =
1339
+ 'Criteria required. Do you want to delete all? Pass Predicates.ALL';
1340
+ logger.error(msg, { condition });
1194
1341
 
1195
- throw new Error(msg);
1196
- }
1197
- }
1342
+ throw new Error(msg);
1343
+ }
1344
+ }
1198
1345
 
1199
- const [deleted] = await this.storage.delete(modelConstructor, condition);
1346
+ const [deleted] = await this.storage.delete(
1347
+ modelConstructor,
1348
+ condition
1349
+ );
1200
1350
 
1201
- return deleted;
1202
- } else {
1203
- const model = modelOrConstructor;
1204
- const modelConstructor = Object.getPrototypeOf(model || {})
1205
- .constructor as PersistentModelConstructor<T>;
1351
+ return deleted;
1352
+ } else {
1353
+ const model = modelOrConstructor;
1354
+ const modelConstructor = Object.getPrototypeOf(model || {})
1355
+ .constructor as PersistentModelConstructor<T>;
1206
1356
 
1207
- if (!isValidModelConstructor(modelConstructor)) {
1208
- const msg = 'Object is not an instance of a valid model';
1209
- logger.error(msg, { model });
1357
+ if (!isValidModelConstructor(modelConstructor)) {
1358
+ const msg = 'Object is not an instance of a valid model';
1359
+ logger.error(msg, { model });
1210
1360
 
1211
- throw new Error(msg);
1212
- }
1361
+ throw new Error(msg);
1362
+ }
1213
1363
 
1214
- const modelDefinition = getModelDefinition(modelConstructor);
1364
+ const modelDefinition = getModelDefinition(modelConstructor);
1215
1365
 
1216
- const pkPredicate = ModelPredicateCreator.createForPk<T>(
1217
- modelDefinition,
1218
- model
1219
- );
1366
+ const pkPredicate = ModelPredicateCreator.createForPk<T>(
1367
+ modelDefinition,
1368
+ model
1369
+ );
1220
1370
 
1221
- if (identifierOrCriteria) {
1222
- if (typeof identifierOrCriteria !== 'function') {
1223
- const msg = 'Invalid criteria';
1224
- logger.error(msg, { identifierOrCriteria });
1371
+ if (identifierOrCriteria) {
1372
+ if (typeof identifierOrCriteria !== 'function') {
1373
+ const msg = 'Invalid criteria';
1374
+ logger.error(msg, { identifierOrCriteria });
1225
1375
 
1226
- throw new Error(msg);
1227
- }
1376
+ throw new Error(msg);
1377
+ }
1228
1378
 
1229
- condition = (<ProducerModelPredicate<T>>identifierOrCriteria)(
1230
- pkPredicate
1231
- );
1232
- } else {
1233
- condition = pkPredicate;
1234
- }
1379
+ condition = (<ProducerModelPredicate<T>>identifierOrCriteria)(
1380
+ pkPredicate
1381
+ );
1382
+ } else {
1383
+ condition = pkPredicate;
1384
+ }
1235
1385
 
1236
- const [[deleted]] = await this.storage.delete(model, condition);
1386
+ const [[deleted]] = await this.storage.delete(model, condition);
1237
1387
 
1238
- return deleted;
1239
- }
1388
+ return deleted;
1389
+ }
1390
+ }, 'datastore delete')
1391
+ .catch(this.handleAddProcError('DataStore.delete()'));
1240
1392
  };
1241
1393
 
1242
1394
  observe: {
@@ -1343,53 +1495,64 @@ class DataStore {
1343
1495
  return new Observable<SubscriptionMessage<T>>(observer => {
1344
1496
  let handle: ZenObservable.Subscription;
1345
1497
 
1346
- (async () => {
1347
- await this.start();
1348
-
1349
- // Filter the events returned by Storage according to namespace,
1350
- // append original element data, and subscribe to the observable
1351
- handle = this.storage
1352
- .observe(modelConstructor, predicate)
1353
- .filter(({ model }) => namespaceResolver(model) === USER)
1354
- .subscribe({
1355
- next: async item => {
1356
- // the `element` doesn't necessarily contain all item details or
1357
- // have related records attached consistently with that of a query()
1358
- // result item. for consistency, we attach them here.
1359
-
1360
- let message = item;
1361
-
1362
- // as long as we're not dealing with a DELETE, we need to fetch a fresh
1363
- // item from storage to ensure it's fully populated.
1364
- if (item.opType !== 'DELETE') {
1365
- const modelDefinition = getModelDefinition(item.model);
1366
- const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1367
- const primaryKeysAndValues = extractPrimaryKeysAndValues(
1368
- item.element,
1369
- keyFields
1370
- );
1371
- const freshElement = await this.query(
1372
- item.model,
1373
- primaryKeysAndValues
1374
- );
1375
- message = {
1376
- ...message,
1377
- element: freshElement as T,
1378
- };
1379
- }
1380
-
1381
- observer.next(message as SubscriptionMessage<T>);
1382
- },
1383
- error: err => observer.error(err),
1384
- complete: () => observer.complete(),
1385
- });
1386
- })();
1498
+ this.runningProcesses
1499
+ .add(async () => {
1500
+ await this.start();
1501
+
1502
+ // Filter the events returned by Storage according to namespace,
1503
+ // append original element data, and subscribe to the observable
1504
+ handle = this.storage
1505
+ .observe(modelConstructor, predicate)
1506
+ .filter(({ model }) => namespaceResolver(model) === USER)
1507
+ .subscribe({
1508
+ next: item =>
1509
+ this.runningProcesses.isOpen &&
1510
+ this.runningProcesses.add(async () => {
1511
+ // the `element` doesn't necessarily contain all item details or
1512
+ // have related records attached consistently with that of a query()
1513
+ // result item. for consistency, we attach them here.
1514
+
1515
+ let message = item;
1516
+
1517
+ // as long as we're not dealing with a DELETE, we need to fetch a fresh
1518
+ // item from storage to ensure it's fully populated.
1519
+ if (item.opType !== 'DELETE') {
1520
+ const modelDefinition = getModelDefinition(item.model);
1521
+ const keyFields =
1522
+ extractPrimaryKeyFieldNames(modelDefinition);
1523
+ const primaryKeysAndValues = extractPrimaryKeysAndValues(
1524
+ item.element,
1525
+ keyFields
1526
+ );
1527
+ const freshElement = await this.query(
1528
+ item.model,
1529
+ primaryKeysAndValues
1530
+ );
1531
+ message = {
1532
+ ...message,
1533
+ element: freshElement as T,
1534
+ };
1535
+ }
1536
+
1537
+ observer.next(message as SubscriptionMessage<T>);
1538
+ }, 'datastore observe message handler'),
1539
+ error: err => observer.error(err),
1540
+ complete: () => observer.complete(),
1541
+ });
1542
+ }, 'datastore observe observable initialization')
1543
+ .catch(this.handleAddProcError('DataStore.observe()'))
1544
+ .catch(error => {
1545
+ observer.error(error);
1546
+ });
1387
1547
 
1388
- return () => {
1548
+ // better than no cleaner, but if the subscriber is handling the
1549
+ // complete() message async and not registering with the context,
1550
+ // this will still be problematic.
1551
+ return this.runningProcesses.addCleaner(async () => {
1389
1552
  if (handle) {
1390
1553
  handle.unsubscribe();
1391
1554
  }
1392
- };
1555
+ }, 'DataStore.observe() cleanup');
1393
1556
  });
1394
1557
  };
1395
1558
 
@@ -1462,72 +1625,79 @@ class DataStore {
1462
1625
  ModelPredicateCreator.getPredicates(predicate, false) || {};
1463
1626
  const hasPredicate = !!predicates;
1464
1627
 
1465
- (async () => {
1466
- try {
1467
- // first, query and return any locally-available records
1468
- (await this.query(model, criteria, sortOptions)).forEach(item => {
1469
- const itemModelDefinition = getModelDefinition(model);
1470
- const idOrPk = getIdentifierValue(itemModelDefinition, item);
1471
- items.set(idOrPk, item);
1472
- });
1473
-
1474
- // Observe the model and send a stream of updates (debounced).
1475
- // We need to post-filter results instead of passing criteria through
1476
- // to have visibility into items that move from in-set to out-of-set.
1477
- // We need to explicitly remove those items from the existing snapshot.
1478
- handle = this.observe(model).subscribe(
1479
- ({ element, model, opType }) => {
1628
+ this.runningProcesses
1629
+ .add(async () => {
1630
+ try {
1631
+ // first, query and return any locally-available records
1632
+ (await this.query(model, criteria, sortOptions)).forEach(item => {
1480
1633
  const itemModelDefinition = getModelDefinition(model);
1481
- const idOrPk = getIdentifierValue(itemModelDefinition, element);
1482
- if (
1483
- hasPredicate &&
1484
- !validatePredicate(element, predicateGroupType, predicates)
1485
- ) {
1634
+ const idOrPk = getIdentifierValue(itemModelDefinition, item);
1635
+ items.set(idOrPk, item);
1636
+ });
1637
+
1638
+ // Observe the model and send a stream of updates (debounced).
1639
+ // We need to post-filter results instead of passing criteria through
1640
+ // to have visibility into items that move from in-set to out-of-set.
1641
+ // We need to explicitly remove those items from the existing snapshot.
1642
+ handle = this.observe(model).subscribe(
1643
+ ({ element, model, opType }) => {
1644
+ const itemModelDefinition = getModelDefinition(model);
1645
+ const idOrPk = getIdentifierValue(itemModelDefinition, element);
1486
1646
  if (
1487
- opType === 'UPDATE' &&
1488
- (items.has(idOrPk) || itemsChanged.has(idOrPk))
1647
+ hasPredicate &&
1648
+ !validatePredicate(element, predicateGroupType, predicates)
1489
1649
  ) {
1490
- // tracking as a "deleted item" will include the item in
1491
- // page limit calculations and ensure it is removed from the
1492
- // final items collection, regardless of which collection(s)
1493
- // it is currently in. (I mean, it could be in both, right!?)
1650
+ if (
1651
+ opType === 'UPDATE' &&
1652
+ (items.has(idOrPk) || itemsChanged.has(idOrPk))
1653
+ ) {
1654
+ // tracking as a "deleted item" will include the item in
1655
+ // page limit calculations and ensure it is removed from the
1656
+ // final items collection, regardless of which collection(s)
1657
+ // it is currently in. (I mean, it could be in both, right!?)
1658
+ deletedItemIds.push(idOrPk);
1659
+ } else {
1660
+ // ignore updates for irrelevant/filtered items.
1661
+ return;
1662
+ }
1663
+ }
1664
+
1665
+ // Flag items which have been recently deleted
1666
+ // NOTE: Merging of separate operations to the same model instance is handled upstream
1667
+ // in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
1668
+ // depends on the LATEST record (for a given id).
1669
+ if (opType === 'DELETE') {
1494
1670
  deletedItemIds.push(idOrPk);
1495
1671
  } else {
1496
- // ignore updates for irrelevant/filtered items.
1497
- return;
1672
+ itemsChanged.set(idOrPk, element);
1498
1673
  }
1499
- }
1500
1674
 
1501
- // Flag items which have been recently deleted
1502
- // NOTE: Merging of separate operations to the same model instance is handled upstream
1503
- // in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
1504
- // depends on the LATEST record (for a given id).
1505
- if (opType === 'DELETE') {
1506
- deletedItemIds.push(idOrPk);
1507
- } else {
1508
- itemsChanged.set(idOrPk, element);
1509
- }
1675
+ const isSynced =
1676
+ this.sync?.getModelSyncedStatus(model) ?? false;
1510
1677
 
1511
- const isSynced = this.sync?.getModelSyncedStatus(model) ?? false;
1678
+ const limit =
1679
+ itemsChanged.size - deletedItemIds.length >=
1680
+ this.syncPageSize;
1512
1681
 
1513
- const limit =
1514
- itemsChanged.size - deletedItemIds.length >= this.syncPageSize;
1682
+ if (limit || isSynced) {
1683
+ limitTimerRace.resolve();
1684
+ }
1515
1685
 
1516
- if (limit || isSynced) {
1517
- limitTimerRace.resolve();
1686
+ // kicks off every subsequent race as results sync down
1687
+ limitTimerRace.start();
1518
1688
  }
1689
+ );
1519
1690
 
1520
- // kicks off every subsequent race as results sync down
1521
- limitTimerRace.start();
1522
- }
1523
- );
1524
-
1525
- // returns a set of initial/locally-available results
1526
- generateAndEmitSnapshot();
1527
- } catch (err) {
1528
- observer.error(err);
1529
- }
1530
- })();
1691
+ // returns a set of initial/locally-available results
1692
+ generateAndEmitSnapshot();
1693
+ } catch (err) {
1694
+ observer.error(err);
1695
+ }
1696
+ }, 'datastore observequery startup')
1697
+ .catch(this.handleAddProcError('DataStore.observeQuery()'))
1698
+ .catch(error => {
1699
+ observer.error(error);
1700
+ });
1531
1701
 
1532
1702
  /**
1533
1703
  * Combines the `items`, `itemsChanged`, and `deletedItemIds` collections into
@@ -1571,7 +1741,8 @@ class DataStore {
1571
1741
  * @param snapshot The generated items data to emit.
1572
1742
  */
1573
1743
  const emitSnapshot = (snapshot: DataStoreSnapshot<T>): void => {
1574
- // send the generated snapshot to the primary subscription
1744
+ // send the generated snapshot to the primary subscription.
1745
+ // NOTE: This observer's handler *could* be async ...
1575
1746
  observer.next(snapshot);
1576
1747
 
1577
1748
  // reset the changed items sets
@@ -1614,16 +1785,16 @@ class DataStore {
1614
1785
  data?.model?.name === model.name
1615
1786
  ) {
1616
1787
  generateAndEmitSnapshot();
1617
- Hub.remove('api', hubCallback);
1788
+ Hub.remove('datastore', hubCallback);
1618
1789
  }
1619
1790
  };
1620
1791
  Hub.listen('datastore', hubCallback);
1621
1792
 
1622
- return () => {
1793
+ return this.runningProcesses.addCleaner(async () => {
1623
1794
  if (handle) {
1624
1795
  handle.unsubscribe();
1625
1796
  }
1626
- };
1797
+ }, 'datastore observequery cleaner');
1627
1798
  });
1628
1799
  };
1629
1800
 
@@ -1713,8 +1884,18 @@ class DataStore {
1713
1884
  this.sessionId = this.retrieveSessionId()!;
1714
1885
  };
1715
1886
 
1716
- clear = async function clear() {
1887
+ /**
1888
+ * Clears all data from storage and removes all data, schema info, other
1889
+ * initialization details, and then stops DataStore.
1890
+ *
1891
+ * That said, reinitialization is required after clearing. This can be done
1892
+ * by explicitiliy calling `start()` or any method that implicitly starts
1893
+ * DataStore, such as `query()`, `save()`, or `delete()`.
1894
+ */
1895
+ async clear() {
1717
1896
  checkSchemaInitialized();
1897
+ this.state = DataStoreState.Clearing;
1898
+ await this.runningProcesses.close();
1718
1899
  if (this.storage === undefined) {
1719
1900
  // connect to storage so that it can be cleared without fully starting DataStore
1720
1901
  this.storage = new Storage(
@@ -1732,35 +1913,53 @@ class DataStore {
1732
1913
  syncSubscription.unsubscribe();
1733
1914
  }
1734
1915
 
1735
- await this.storage.clear();
1736
-
1737
1916
  if (this.sync) {
1738
- this.sync.unsubscribeConnectivity();
1917
+ await this.sync.stop();
1739
1918
  }
1740
1919
 
1920
+ await this.storage!.clear();
1921
+
1741
1922
  this.initialized = undefined; // Should re-initialize when start() is called.
1742
1923
  this.storage = undefined;
1743
1924
  this.sync = undefined;
1744
1925
  this.syncPredicates = new WeakMap<SchemaModel, ModelPredicate<any>>();
1745
- };
1746
1926
 
1747
- stop = async function stop(this: InstanceType<typeof DataStore>) {
1748
- if (this.initialized !== undefined) {
1749
- await this.start();
1750
- }
1927
+ await this.runningProcesses.open();
1928
+ this.state = DataStoreState.NotRunning;
1929
+ }
1930
+
1931
+ /**
1932
+ * Stops all DataStore sync activities.
1933
+ *
1934
+ * TODO: "Waits for graceful termination of
1935
+ * running queries and terminates subscriptions."
1936
+ */
1937
+ async stop(this: InstanceType<typeof DataStore>) {
1938
+ this.state = DataStoreState.Stopping;
1939
+
1940
+ await this.runningProcesses.close();
1751
1941
 
1752
1942
  if (syncSubscription && !syncSubscription.closed) {
1753
1943
  syncSubscription.unsubscribe();
1754
1944
  }
1755
1945
 
1756
1946
  if (this.sync) {
1757
- this.sync.unsubscribeConnectivity();
1947
+ await this.sync.stop();
1758
1948
  }
1759
1949
 
1760
1950
  this.initialized = undefined; // Should re-initialize when start() is called.
1761
1951
  this.sync = undefined;
1762
- };
1952
+ await this.runningProcesses.open();
1953
+ this.state = DataStoreState.NotRunning;
1954
+ }
1763
1955
 
1956
+ /**
1957
+ * Validates given pagination input from a query and creates a pagination
1958
+ * argument for use against the storage layer.
1959
+ *
1960
+ * @param modelDefinition
1961
+ * @param paginationProducer
1962
+ */
1764
1963
  private processPagination<T extends PersistentModel>(
1765
1964
  modelDefinition: SchemaModel,
1766
1965
  paginationProducer: ProducerPaginationInput<T>
@@ -1810,6 +2009,10 @@ class DataStore {
1810
2009
  };
1811
2010
  }
1812
2011
 
2012
+ /**
2013
+ * Examines the configured `syncExpressions` and produces a WeakMap of
2014
+ * SchemaModel -> predicate to use during sync.
2015
+ */
1813
2016
  private async processSyncExpressions(): Promise<
1814
2017
  WeakMap<SchemaModel, ModelPredicate<any>>
1815
2018
  > {
@@ -1895,7 +2098,10 @@ class DataStore {
1895
2098
  }, new WeakMap<SchemaModel, ModelPredicate<any>>());
1896
2099
  }
1897
2100
 
1898
- // database separation for Amplify Console. Not a public API
2101
+ /**
2102
+ * A session ID to allow CMS to open databases against multiple apps.
2103
+ * This session ID is only expected be set by AWS Amplify Studio.
2104
+ */
1899
2105
  private retrieveSessionId(): string | undefined {
1900
2106
  try {
1901
2107
  const sessionId = sessionStorage.getItem('datastoreSessionId');